]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
546b32b250c316dfd5dd682a6084c345b4a6d951
[ceph-ci.git] /
1 import {
2   ChangeDetectionStrategy,
3   ChangeDetectorRef,
4   Component,
5   OnDestroy,
6   OnInit,
7   TemplateRef,
8   ViewChild
9 } from '@angular/core';
10 import { Node } from 'carbon-components-angular/treeview/tree-node.types';
11 import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
12 import _ from 'lodash';
13
14 import { forkJoin, Subscription, timer as observableTimer } from 'rxjs';
15 import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
16 import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
17 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
18 import { CriticalConfirmationModalComponent } from '~/app/shared/components/critical-confirmation-modal/critical-confirmation-modal.component';
19 import { ActionLabelsI18n, TimerServiceInterval } from '~/app/shared/constants/app.constants';
20 import { Icons } from '~/app/shared/enum/icons.enum';
21 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
22 import { CdTableAction } from '~/app/shared/models/cd-table-action';
23 import { CdTableSelection } from '~/app/shared/models/cd-table-selection';
24 import { Permission } from '~/app/shared/models/permissions';
25 import { AuthStorageService } from '~/app/shared/services/auth-storage.service';
26 import { ModalService } from '~/app/shared/services/modal.service';
27 import { NotificationService } from '~/app/shared/services/notification.service';
28 import { TimerService } from '~/app/shared/services/timer.service';
29 import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
30 import { RgwMultisiteMigrateComponent } from '../rgw-multisite-migrate/rgw-multisite-migrate.component';
31 import { RgwMultisiteZoneDeletionFormComponent } from '../models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component';
32 import { RgwMultisiteZonegroupDeletionFormComponent } from '../models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component';
33 import { RgwMultisiteExportComponent } from '../rgw-multisite-export/rgw-multisite-export.component';
34 import { RgwMultisiteImportComponent } from '../rgw-multisite-import/rgw-multisite-import.component';
35 import { RgwMultisiteRealmFormComponent } from '../rgw-multisite-realm-form/rgw-multisite-realm-form.component';
36 import { RgwMultisiteZoneFormComponent } from '../rgw-multisite-zone-form/rgw-multisite-zone-form.component';
37 import { RgwMultisiteZonegroupFormComponent } from '../rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component';
38 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
39 import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
40 import { BlockUI, NgBlockUI } from 'ng-block-ui';
41 import { Router } from '@angular/router';
42 import { RgwMultisiteWizardComponent } from '../rgw-multisite-wizard/rgw-multisite-wizard.component';
43 import { RgwMultisiteSyncPolicyComponent } from '../rgw-multisite-sync-policy/rgw-multisite-sync-policy.component';
44 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
45 import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
46
47 const BASE_URL = 'rgw/multisite/configuration';
48
49 @Component({
50   selector: 'cd-rgw-multisite-details',
51   templateUrl: './rgw-multisite-details.component.html',
52   styleUrls: ['./rgw-multisite-details.component.scss'],
53   changeDetection: ChangeDetectionStrategy.OnPush
54 })
55 export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
56   private sub = new Subscription();
57   @ViewChild('treeNodeTemplate') labelTpl: TemplateRef<any>;
58   @ViewChild(RgwMultisiteSyncPolicyComponent) syncPolicyComp: RgwMultisiteSyncPolicyComponent;
59
60   messages = {
61     noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
62     noMasterZone: $localize`Please create a master zone for each zone group to enable this feature`,
63     noRealmExists: $localize`No realm exists`,
64     disableExport: $localize`Please create master zone group and master zone for each of the realms`
65   };
66
67   @BlockUI()
68   blockUI: NgBlockUI;
69
70   icons = Icons;
71   permission: Permission;
72   selection = new CdTableSelection();
73   createTableActions: CdTableAction[];
74   migrateTableAction: CdTableAction[];
75   importAction: CdTableAction[];
76   exportAction: CdTableAction[];
77   multisiteReplicationActions: CdTableAction[];
78   loadingIndicator = true;
79
80   toNode(values: any): Node[] {
81     return values.map((value: any) => ({
82       label: this.labelTpl,
83       labelContext: {
84         data: { ...value }
85       },
86       id: value.id,
87       value: { ...value },
88       expanded: true,
89       name: value.name,
90       children: value?.children ? this.toNode(value.children) : []
91     }));
92   }
93
94   set nodes(values: any) {
95     this._nodes = this.toNode(values);
96     this.changeDetectionRef.detectChanges();
97   }
98
99   get nodes() {
100     return this._nodes;
101   }
102
103   private _nodes: Node[] = [];
104
105   modalRef: NgbModalRef;
106
107   realms: RgwRealm[] = [];
108   zonegroups: RgwZonegroup[] = [];
109   zones: RgwZone[] = [];
110   metadata: any;
111   metadataTitle: string;
112   bsModalRef: NgbModalRef;
113   realmIds: string[] = [];
114   zoneIds: string[] = [];
115   defaultRealmId = '';
116   defaultZonegroupId = '';
117   defaultZoneId = '';
118   multisiteInfo: object[] = [];
119   defaultsInfo: string[] = [];
120   showMigrateAndReplicationActions = false;
121   editTitle: string = 'Edit';
122   deleteTitle: string = 'Delete';
123   disableExport = true;
124   rgwModuleStatus: boolean;
125   restartGatewayMessage = false;
126   rgwModuleData: string | any[] = [];
127   activeId: string;
128   activeNodeId?: string;
129
130   constructor(
131     private modalService: ModalService,
132     private timerService: TimerService,
133     private authStorageService: AuthStorageService,
134     public actionLabels: ActionLabelsI18n,
135     public timerServiceVariable: TimerServiceInterval,
136     public router: Router,
137     public rgwRealmService: RgwRealmService,
138     public rgwZonegroupService: RgwZonegroupService,
139     public rgwZoneService: RgwZoneService,
140     public rgwDaemonService: RgwDaemonService,
141     public mgrModuleService: MgrModuleService,
142     private notificationService: NotificationService,
143     private cdsModalService: ModalCdsService,
144     private rgwMultisiteService: RgwMultisiteService,
145     private changeDetectionRef: ChangeDetectorRef
146   ) {
147     this.permission = this.authStorageService.getPermissions().rgw;
148   }
149
150   openModal(entity: any | string, edit = false) {
151     const entityName = edit ? entity?.data?.type : entity;
152     const action = edit ? 'edit' : 'create';
153     const initialState = {
154       resource: entityName,
155       action: action,
156       info: entity,
157       defaultsInfo: this.defaultsInfo,
158       multisiteInfo: this.multisiteInfo
159     };
160     if (entityName === 'realm') {
161       this.bsModalRef = this.cdsModalService.show(RgwMultisiteRealmFormComponent, initialState);
162     } else if (entityName === 'zonegroup') {
163       this.bsModalRef = this.modalService.show(RgwMultisiteZonegroupFormComponent, initialState, {
164         size: 'lg'
165       });
166     } else {
167       this.bsModalRef = this.modalService.show(RgwMultisiteZoneFormComponent, initialState, {
168         size: 'lg'
169       });
170     }
171   }
172
173   openMultisiteSetupWizard() {
174     this.bsModalRef = this.cdsModalService.show(RgwMultisiteWizardComponent);
175   }
176
177   openMigrateModal() {
178     const initialState = {
179       multisiteInfo: this.multisiteInfo
180     };
181     this.bsModalRef = this.modalService.show(RgwMultisiteMigrateComponent, initialState, {
182       size: 'lg'
183     });
184   }
185
186   openImportModal() {
187     const initialState = {
188       multisiteInfo: this.multisiteInfo
189     };
190     this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
191       size: 'lg'
192     });
193   }
194
195   openExportModal() {
196     const initialState = {
197       defaultsInfo: this.defaultsInfo,
198       multisiteInfo: this.multisiteInfo
199     };
200     this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
201       size: 'lg'
202     });
203   }
204
205   getDisableExport() {
206     this.realms.forEach((realm: any) => {
207       this.zonegroups.forEach((zonegroup) => {
208         if (realm.id === zonegroup.realm_id) {
209           if (zonegroup.is_master && zonegroup.master_zone !== '') {
210             this.disableExport = false;
211           }
212         }
213       });
214     });
215     if (!this.rgwModuleStatus) {
216       return true;
217     }
218     if (this.realms.length < 1) {
219       return this.messages.noRealmExists;
220     } else if (this.disableExport) {
221       return this.messages.disableExport;
222     } else {
223       return false;
224     }
225   }
226
227   getDisableImport() {
228     if (!this.rgwModuleStatus) {
229       return true;
230     } else {
231       return false;
232     }
233   }
234
235   ngOnInit() {
236     this.createTableActions = [
237       {
238         permission: 'create',
239         icon: Icons.add,
240         name: this.actionLabels.CREATE + ' Realm',
241         click: () => this.openModal('realm')
242       },
243       {
244         permission: 'create',
245         icon: Icons.add,
246         name: this.actionLabels.CREATE + ' Zone Group',
247         click: () => this.openModal('zonegroup'),
248         disable: () => this.getDisable()
249       },
250       {
251         permission: 'create',
252         icon: Icons.add,
253         name: this.actionLabels.CREATE + ' Zone',
254         click: () => this.openModal('zone')
255       }
256     ];
257     this.migrateTableAction = [
258       {
259         permission: 'create',
260         icon: Icons.wrench,
261         name: this.actionLabels.MIGRATE,
262         click: () => this.openMigrateModal()
263       }
264     ];
265     this.importAction = [
266       {
267         permission: 'create',
268         icon: Icons.download,
269         name: this.actionLabels.IMPORT,
270         click: () => this.openImportModal(),
271         disable: () => this.getDisableImport()
272       }
273     ];
274     this.exportAction = [
275       {
276         permission: 'create',
277         icon: Icons.upload,
278         name: this.actionLabels.EXPORT,
279         click: () => this.openExportModal(),
280         disable: () => this.getDisableExport()
281       }
282     ];
283     this.multisiteReplicationActions = [
284       {
285         permission: 'create',
286         icon: Icons.wrench,
287         name: this.actionLabels.SETUP_MULTISITE_REPLICATION,
288         click: () =>
289           this.router.navigate([BASE_URL, { outlets: { modal: 'setup-multisite-replication' } }])
290       }
291     ];
292
293     const observables = [
294       this.rgwRealmService.getAllRealmsInfo(),
295       this.rgwZonegroupService.getAllZonegroupsInfo(),
296       this.rgwZoneService.getAllZonesInfo()
297     ];
298     this.sub = this.timerService
299       .get(() => forkJoin(observables), this.timerServiceVariable.TIMER_SERVICE_PERIOD * 2)
300       .subscribe(
301         (multisiteInfo: [object, object, object]) => {
302           this.multisiteInfo = multisiteInfo;
303           this.loadingIndicator = false;
304           this.nodes = this.abstractTreeData(multisiteInfo);
305         },
306         (_error) => {}
307       );
308     this.mgrModuleService.list().subscribe((moduleData: any) => {
309       this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
310       if (this.rgwModuleData.length > 0) {
311         this.rgwModuleStatus = this.rgwModuleData[0].enabled;
312       }
313     });
314   }
315   /* setConfigValues() {
316     this.rgwDaemonService
317       .setMultisiteConfig(
318         this.defaultsInfo['defaultRealmName'],
319         this.defaultsInfo['defaultZonegroupName'],
320         this.defaultsInfo['defaultZoneName']
321       )
322       .subscribe(() => {});
323   }*/
324
325   ngOnDestroy() {
326     this.sub.unsubscribe();
327   }
328
329   private abstractTreeData(multisiteInfo: [object, object, object]): any[] {
330     let allNodes: object[] = [];
331     let rootNodes = {};
332     let firstChildNodes = {};
333     let allFirstChildNodes = [];
334     let secondChildNodes = {};
335     let allSecondChildNodes: {}[] = [];
336     this.realms = multisiteInfo[0]['realms'];
337     this.zonegroups = multisiteInfo[1]['zonegroups'];
338     this.zones = multisiteInfo[2]['zones'];
339     this.defaultRealmId = multisiteInfo[0]['default_realm'];
340     this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
341     this.defaultZoneId = multisiteInfo[2]['default_zone'];
342     this.defaultsInfo = this.getDefaultsEntities(
343       this.defaultRealmId,
344       this.defaultZonegroupId,
345       this.defaultZoneId
346     );
347     if (this.realms.length > 0) {
348       // get tree for realm -> zonegroup -> zone
349       for (const realm of this.realms) {
350         const result = this.rgwRealmService.getRealmTree(realm, this.defaultRealmId);
351         rootNodes = result['nodes'];
352         this.realmIds = this.realmIds.concat(result['realmIds']);
353         for (const zonegroup of this.zonegroups) {
354           if (zonegroup.realm_id === realm.id) {
355             firstChildNodes = this.rgwZonegroupService.getZonegroupTree(
356               zonegroup,
357               this.defaultZonegroupId,
358               realm
359             );
360             for (const zone of zonegroup.zones) {
361               const zoneResult = this.rgwZoneService.getZoneTree(
362                 zone,
363                 this.defaultZoneId,
364                 this.zones,
365                 zonegroup,
366                 realm
367               );
368               secondChildNodes = zoneResult['nodes'];
369               this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
370               allSecondChildNodes.push(secondChildNodes);
371               secondChildNodes = {};
372             }
373             allSecondChildNodes = allSecondChildNodes.map((x) => ({
374               ...x,
375               parentNode: firstChildNodes
376             }));
377             firstChildNodes['children'] = allSecondChildNodes;
378             allSecondChildNodes = [];
379             allFirstChildNodes.push(firstChildNodes);
380             firstChildNodes = {};
381           }
382         }
383         allFirstChildNodes = allFirstChildNodes.map((x) => ({ ...x, parentNode: rootNodes }));
384         rootNodes['children'] = allFirstChildNodes;
385         allNodes.push({ ...rootNodes, label: rootNodes?.['name'] || rootNodes?.['id'] });
386         firstChildNodes = {};
387         secondChildNodes = {};
388         rootNodes = {};
389         allFirstChildNodes = [];
390         allSecondChildNodes = [];
391       }
392     }
393     if (this.zonegroups.length > 0) {
394       // get tree for zonegroup -> zone (standalone zonegroups that don't match a realm eg(initial default))
395       for (const zonegroup of this.zonegroups) {
396         if (!this.realmIds.includes(zonegroup.realm_id)) {
397           rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
398           for (const zone of zonegroup.zones) {
399             const zoneResult = this.rgwZoneService.getZoneTree(
400               zone,
401               this.defaultZoneId,
402               this.zones,
403               zonegroup
404             );
405             firstChildNodes = zoneResult['nodes'];
406             this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
407             allFirstChildNodes.push(firstChildNodes);
408             firstChildNodes = {};
409           }
410           allFirstChildNodes = allFirstChildNodes.map((x) => ({ ...x, parentNode: rootNodes }));
411           rootNodes['children'] = allFirstChildNodes;
412           allNodes.push({ ...rootNodes, label: rootNodes?.['name'] || rootNodes?.['id'] });
413           firstChildNodes = {};
414           rootNodes = {};
415           allFirstChildNodes = [];
416         }
417       }
418     }
419     if (this.zones.length > 0) {
420       // get tree for standalone zones(zones that do not belong to a zonegroup)
421       for (const zone of this.zones) {
422         if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
423           const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
424           rootNodes = zoneResult['nodes'];
425           allNodes.push({ ...rootNodes, label: rootNodes?.['name'] || rootNodes?.['id'] });
426           rootNodes = {};
427         }
428       }
429     }
430     if (this.realms.length < 1 && this.zonegroups.length < 1 && this.zones.length < 1) {
431       return [
432         {
433           name: 'No nodes!',
434           label: 'No nodes!'
435         }
436       ];
437     }
438     this.realmIds = [];
439     this.zoneIds = [];
440     this.evaluateMigrateAndReplicationActions();
441     this.rgwMultisiteService.restartGatewayMessage$.subscribe((value) => {
442       if (value !== null) {
443         this.restartGatewayMessage = value;
444       } else {
445         this.checkRestartGatewayMessage();
446       }
447     });
448     return allNodes;
449   }
450
451   checkRestartGatewayMessage() {
452     this.rgwDaemonService.list().subscribe((data: any) => {
453       const realmName = data.map((item: { [x: string]: any }) => item['realm_name']);
454       if (
455         this.defaultRealmId !== '' &&
456         this.defaultZonegroupId !== '' &&
457         this.defaultZoneId !== '' &&
458         realmName.includes('')
459       ) {
460         this.restartGatewayMessage = true;
461       } else {
462         this.restartGatewayMessage = false;
463       }
464     });
465   }
466
467   getDefaultsEntities(
468     defaultRealmId: string,
469     defaultZonegroupId: string,
470     defaultZoneId: string
471   ): any {
472     const defaultRealm = this.realms?.find((x: { id: string }) => x.id === defaultRealmId);
473     const defaultZonegroup = this.zonegroups?.find(
474       (x: { id: string }) => x.id === defaultZonegroupId
475     );
476     const defaultZone = this.zones?.find((x: { id: string }) => x.id === defaultZoneId);
477
478     return {
479       defaultRealmName: defaultRealm?.name,
480       defaultZonegroupName: defaultZonegroup?.name,
481       defaultZoneName: defaultZone?.name
482     };
483   }
484
485   onNodeSelected(node: Node) {
486     this.metadataTitle = node?.value?.name;
487     this.metadata = node?.value?.info;
488     this.activeNodeId = node?.value?.id;
489     node.expanded = true;
490   }
491
492   getDisable() {
493     let isMasterZone = true;
494     if (this.defaultRealmId === '') {
495       return this.messages.noDefaultRealm;
496     } else {
497       this.zonegroups.forEach((zgp: any) => {
498         if (_.isEmpty(zgp.master_zone)) {
499           isMasterZone = false;
500         }
501       });
502       if (!isMasterZone) {
503         setTimeout(() => {
504           this.editTitle =
505             'Please create a master zone for each existing zonegroup to enable this feature';
506         }, 1);
507         return this.messages.noMasterZone;
508       } else {
509         setTimeout(() => {
510           this.editTitle = 'Edit';
511         }, 1);
512         return false;
513       }
514     }
515   }
516
517   evaluateMigrateAndReplicationActions() {
518     if (
519       this.realms.length === 0 &&
520       this.zonegroups.length === 1 &&
521       this.zonegroups[0].name === 'default' &&
522       this.zones.length === 1 &&
523       this.zones[0].name === 'default'
524     ) {
525       this.showMigrateAndReplicationActions = true;
526     } else {
527       this.showMigrateAndReplicationActions = false;
528     }
529     return this.showMigrateAndReplicationActions;
530   }
531
532   isDeleteDisabled(node: Node): { isDisabled: boolean; deleteTitle: string } {
533     let isDisabled: boolean = false;
534     let deleteTitle: string = this.deleteTitle;
535     let masterZonegroupCount: number = 0;
536     if (node?.value?.type === 'realm' && node?.data?.is_default && this.realms.length < 2) {
537       isDisabled = true;
538     }
539
540     if (node?.data?.type === 'zonegroup') {
541       if (this.zonegroups.length < 2) {
542         deleteTitle = 'You can not delete the only zonegroup available';
543         isDisabled = true;
544       } else if (node?.data?.is_default) {
545         deleteTitle = 'You can not delete the default zonegroup';
546         isDisabled = true;
547       } else if (node?.data?.is_master) {
548         for (let zonegroup of this.zonegroups) {
549           if (zonegroup.is_master === true) {
550             masterZonegroupCount++;
551             if (masterZonegroupCount > 1) break;
552           }
553         }
554         if (masterZonegroupCount < 2) {
555           deleteTitle = 'You can not delete the only master zonegroup available';
556           isDisabled = true;
557         }
558       }
559     }
560
561     if (node?.data?.type === 'zone') {
562       if (this.zones.length < 2) {
563         deleteTitle = 'You can not delete the only zone available';
564         isDisabled = true;
565       } else if (node?.data?.is_default) {
566         deleteTitle = 'You can not delete the default zone';
567         isDisabled = true;
568       } else if (node?.data?.is_master && node?.data?.zone_zonegroup.zones.length < 2) {
569         deleteTitle =
570           'You can not delete the master zone as there are no more zones in this zonegroup';
571         isDisabled = true;
572       }
573     }
574
575     if (!isDisabled) {
576       this.deleteTitle = 'Delete';
577     }
578
579     return { isDisabled, deleteTitle };
580   }
581
582   delete(node: Node) {
583     if (node?.data?.type === 'realm') {
584       const modalRef = this.cdsModalService.show(CriticalConfirmationModalComponent, {
585         itemDescription: $localize`${node?.data?.type} ${node?.data?.name}`,
586         itemNames: [`${node?.data?.name}`],
587         submitAction: () => {
588           this.rgwRealmService.delete(node?.data?.name).subscribe(
589             () => {
590               this.notificationService.show(
591                 NotificationType.success,
592                 $localize`Realm: '${node?.data?.name}' deleted successfully`
593               );
594               this.cdsModalService.dismissAll();
595             },
596             () => {
597               this.cdsModalService.stopLoadingSpinner(modalRef.deletionForm);
598             }
599           );
600         }
601       });
602     } else if (node?.data?.type === 'zonegroup') {
603       this.modalRef = this.modalService.show(RgwMultisiteZonegroupDeletionFormComponent, {
604         zonegroup: node.data
605       });
606     } else if (node?.data?.type === 'zone') {
607       this.modalRef = this.modalService.show(RgwMultisiteZoneDeletionFormComponent, {
608         zone: node.data
609       });
610     }
611   }
612
613   enableRgwModule() {
614     let $obs;
615     const fnWaitUntilReconnected = () => {
616       observableTimer(2000).subscribe(() => {
617         // Trigger an API request to check if the connection is
618         // re-established.
619         this.mgrModuleService.list().subscribe(
620           () => {
621             // Resume showing the notification toasties.
622             this.notificationService.suspendToasties(false);
623             // Unblock the whole UI.
624             this.blockUI.stop();
625             // Reload the data table content.
626             this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
627             this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
628               this.router.navigate(['/rgw/multisite']);
629             });
630             // Reload the data table content.
631           },
632           () => {
633             fnWaitUntilReconnected();
634           }
635         );
636       });
637     };
638
639     if (!this.rgwModuleStatus) {
640       $obs = this.mgrModuleService.enable('rgw');
641     }
642     $obs.subscribe(
643       () => undefined,
644       () => {
645         // Suspend showing the notification toasties.
646         this.notificationService.suspendToasties(true);
647         // Block the whole UI to prevent user interactions until
648         // the connection to the backend is reestablished
649         this.blockUI.start($localize`Reconnecting, please wait ...`);
650         fnWaitUntilReconnected();
651       }
652     );
653   }
654 }