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