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