]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
mgr/dashboard: RGW multisite sync remove zones fix
authorNaman Munet <nmunet@redhat.com>
Tue, 6 Aug 2024 19:45:44 +0000 (01:15 +0530)
committerNaman Munet <namanmunet@li-ff83bccc-26af-11b2-a85c-a4b04bfb1003.ibm.com>
Fri, 13 Sep 2024 10:43:42 +0000 (16:13 +0530)
Fixes: https://tracker.ceph.com/issues/66945
Signed-off-by: Naman Munet <nmunet@redhat.com>
src/pybind/mgr/dashboard/controllers/rgw.py [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component.ts [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component.ts [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy-details/rgw-multisite-sync-policy-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-sync-policy-details/rgw-multisite-sync-policy-details.component.ts [changed mode: 0644->0755]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rgw_client.py [changed mode: 0644->0755]

old mode 100644 (file)
new mode 100755 (executable)
index 95e2251..a1dab1c
@@ -200,9 +200,9 @@ class RgwMultisiteController(RESTController):
     @EndpointDoc("Create or update the sync flow")
     @CreatePermission
     def create_sync_flow(self, flow_id: str, flow_type: str, group_id: str,
-                         source_zone: Optional[List[str]] = None,
-                         destination_zone: Optional[List[str]] = None,
-                         zones: Optional[List[str]] = None,
+                         source_zone: Optional[str] = None,
+                         destination_zone: Optional[str] = None,
+                         zones: Optional[Dict[str, List]] = None,
                          bucket_name=''):
         multisite_instance = RgwMultisite()
         return multisite_instance.create_sync_flow(group_id, flow_id, flow_type, zones,
@@ -222,11 +222,10 @@ class RgwMultisiteController(RESTController):
     @EndpointDoc("Create or update the sync pipe")
     @CreatePermission
     def create_sync_pipe(self, group_id: str, pipe_id: str,
+                         source_zones: Dict[str, Any],
+                         destination_zones: Dict[str, Any],
                          source_bucket: str = '',
-                         source_zones: Optional[List[str]] = None,
-                         destination_zones: Optional[List[str]] = None,
-                         destination_bucket: str = '',
-                         bucket_name: str = ''):
+                         destination_bucket: str = '', bucket_name: str = ''):
         multisite_instance = RgwMultisite()
         return multisite_instance.create_sync_pipe(group_id, pipe_id, source_zones,
                                                    destination_zones, source_bucket,
index bc2f8eafe5b45808c87a77a184363d33499643f1..e385ecd5ada2e79e1774c9426b7edf4f3d37cf59 100644 (file)
@@ -61,3 +61,8 @@ export enum FlowType {
   directional = 'directional',
   symmetrical = 'symmetrical'
 }
+
+export interface Zone {
+  added: string[];
+  removed: string[];
+}
index b7f614ef16b6f5d548ff96e9fbaa42b438cf6653..50b0bf321bf2163c88c527d03a852cf573e23894 100644 (file)
                      i18n>Source Zone
               </label>
               <div class="cd-col-form-input">
-                <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'source_zone', zone: sourceZones }"></ng-container>
+                <select id="sourceZone"
+                        name="sourceZone"
+                        class="form-select"
+                        formControlName="source_zone"
+                        [autofocus]="editing">
+                <option i18n
+                        *ngIf="zones.data.available.length == 0"
+                        [ngValue]="null">Loading...</option>
+                <option i18n
+                        *ngIf="zones.data.available.length > 0"
+                        [ngValue]="null">-- Select source zone --</option>
+                <option *ngFor="let sourceZone of zones.data.available"
+                        [value]="sourceZone.name">{{ sourceZone.name }}</option>
+              </select>
+              <span class="invalid-feedback"
+                    *ngIf="currentFormGroupContext.showError('source_zone', frm, 'required')"
+                    i18n>This field is required.</span>
               </div>
             </div>
             <div class="form-group row">
                      for="destination_zone"
                      i18n>Destination Zone</label>
               <div class="cd-col-form-input">
-                <ng-container *ngTemplateOutlet="zoneMultiSelect;context: { name: 'destination_zone', zone: destinationZones }"></ng-container>
+                <input id="destination_zone"
+                       name="destination_zone"
+                       class="form-control"
+                       type="text"
+                       i18n-placeholder
+                       placeholder="Destination Zone..."
+                       formControlName="destination_zone"/>
+                <span class="invalid-feedback"
+                      *ngIf="currentFormGroupContext.showError('destination_zone', frm, 'required')"
+                      i18n>This field is required.</span>
               </div>
             </div>
           </ng-template>
old mode 100644 (file)
new mode 100755 (executable)
index 126279b..bf243f8
@@ -2,13 +2,13 @@ import { Component, OnInit } from '@angular/core';
 import { UntypedFormControl, Validators } from '@angular/forms';
 import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
 import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
-import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { ActionLabelsI18n, SucceededActionLabelsI18n } from '~/app/shared/constants/app.constants';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
 import { NotificationService } from '~/app/shared/services/notification.service';
 import { catchError, switchMap } from 'rxjs/operators';
 import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
 import { RgwDaemon } from '../models/rgw-daemon';
-import { FlowType, RgwZonegroup } from '../models/rgw-multisite';
+import { FlowType, RgwZonegroup, Zone } from '../models/rgw-multisite';
 import { of } from 'rxjs';
 import { SelectOption } from '~/app/shared/components/select/select-option.model';
 import _ from 'lodash';
@@ -35,8 +35,8 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
   flowType = FlowType;
   icons = Icons;
   zones = new ZoneData(false, 'Filter Zones');
-  sourceZones = new ZoneData(false, 'Filter Zones');
-  destinationZones = new ZoneData(true, 'Filter or Add Zones');
+  sourceZone: string;
+  destinationZone: string;
 
   constructor(
     public activeModal: NgbActiveModal,
@@ -44,7 +44,8 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
     public notificationService: NotificationService,
     private rgwDaemonService: RgwDaemonService,
     private rgwZonegroupService: RgwZonegroupService,
-    private rgwMultisiteService: RgwMultisiteService
+    private rgwMultisiteService: RgwMultisiteService,
+    private succeededLabels: SucceededActionLabelsI18n
   ) {}
 
   ngOnInit(): void {
@@ -90,13 +91,9 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
             zones.push(new SelectOption(false, zone.name, ''));
           });
           this.zones.data.available = [...zones];
-          this.sourceZones.data.available = [...zones];
           if (this.editing) {
             if (this.groupType === FlowType.symmetrical) {
-              this.zones.data.selected = this.flowSelectedRow.zones;
-            } else {
-              this.destinationZones.data.selected = [this.flowSelectedRow.dest_zone];
-              this.sourceZones.data.selected = [this.flowSelectedRow.source_zone];
+              this.zones.data.selected = [...this.flowSelectedRow.zones];
             }
             this.zoneSelection();
           }
@@ -145,15 +142,20 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
       this.currentFormGroupContext.patchValue({
         zones: this.zones.data.selected
       });
-    } else {
-      this.currentFormGroupContext.patchValue({
-        source_zone: this.sourceZones.data.selected,
-        destination_zone: this.destinationZones.data.selected
-      });
     }
   }
 
+  getZoneData(zoneDataToFilter: string[], zoneDataForCondition: string[]) {
+    return zoneDataToFilter.filter((zone: string) => !zoneDataForCondition.includes(zone));
+  }
+
+  assignZoneValue(zone: string[], selectedZone: string[]) {
+    return zone.length > 0 ? zone : selectedZone;
+  }
+
   submit() {
+    const zones: Zone = { added: [], removed: [] };
+
     if (this.currentFormGroupContext.invalid) {
       return;
     }
@@ -162,16 +164,24 @@ export class RgwMultisiteSyncFlowModalComponent implements OnInit {
       this.currentFormGroupContext.setErrors({ cdSubmitButton: true });
       return;
     }
+
+    if (this.groupType == FlowType.symmetrical) {
+      if (this.editing) {
+        zones.removed = this.getZoneData(this.flowSelectedRow.zones, this.zones.data.selected);
+        zones.added = this.getZoneData(this.zones.data.selected, this.flowSelectedRow.zones);
+      }
+      zones.added = this.assignZoneValue(zones.added, this.zones.data.selected);
+    }
     this.rgwMultisiteService
-      .createEditSyncFlow(this.currentFormGroupContext.getRawValue())
+      .createEditSyncFlow({ ...this.currentFormGroupContext.getRawValue(), zones: zones })
       .subscribe(
         () => {
-          const action = this.editing ? 'Modified' : 'Created';
+          const action = this.editing ? this.succeededLabels.EDITED : this.succeededLabels.CREATED;
           this.notificationService.show(
             NotificationType.success,
             $localize`${action} Sync Flow '${this.currentFormGroupContext.getValue('flow_id')}'`
           );
-          this.activeModal.close('success');
+          this.activeModal.close(NotificationType.success);
         },
         () => {
           // Reset the 'Submit' button.
old mode 100644 (file)
new mode 100755 (executable)
index 73bca12..da4475f
@@ -1,7 +1,7 @@
 import { Component, OnInit } from '@angular/core';
 import { UntypedFormControl, Validators } from '@angular/forms';
 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
-import { RgwZonegroup } from '../models/rgw-multisite';
+import { RgwZonegroup, Zone } from '../models/rgw-multisite';
 import { SelectOption } from '~/app/shared/components/select/select-option.model';
 import { catchError, switchMap } from 'rxjs/operators';
 import { of } from 'rxjs';
@@ -15,6 +15,7 @@ import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
 import { NotificationService } from '~/app/shared/services/notification.service';
 import { ZoneData } from '../models/rgw-multisite-zone-selector';
+import { SucceededActionLabelsI18n } from '~/app/shared/constants/app.constants';
 
 @Component({
   selector: 'cd-rgw-multisite-sync-pipe-modal',
@@ -36,7 +37,8 @@ export class RgwMultisiteSyncPipeModalComponent implements OnInit {
     private rgwDaemonService: RgwDaemonService,
     private rgwZonegroupService: RgwZonegroupService,
     private rgwMultisiteService: RgwMultisiteService,
-    private notificationService: NotificationService
+    private notificationService: NotificationService,
+    private succeededLabels: SucceededActionLabelsI18n
   ) {}
 
   ngOnInit(): void {
@@ -84,8 +86,13 @@ export class RgwMultisiteSyncPipeModalComponent implements OnInit {
           this.sourceZones.data.available = [...zones];
           if (this.editing) {
             this.pipeForm.get('pipe_id').disable();
-            this.sourceZones.data.selected = this.pipeSelectedRow.source.zones;
-            this.destZones.data.selected = this.pipeSelectedRow.dest.zones;
+            this.sourceZones.data.selected = [...this.pipeSelectedRow.source.zones];
+            this.destZones.data.selected = [...this.pipeSelectedRow.dest.zones];
+            const availableDestZone: SelectOption[] = [];
+            this.pipeSelectedRow.dest.zones.forEach((zone: string) => {
+              availableDestZone.push(new SelectOption(true, zone, ''));
+            });
+            this.destZones.data.available = availableDestZone;
             this.pipeForm.patchValue({
               pipe_id: this.pipeSelectedRow.id,
               source_zones: this.pipeSelectedRow.source.zones,
@@ -110,7 +117,18 @@ export class RgwMultisiteSyncPipeModalComponent implements OnInit {
     }
   }
 
+  getZoneData(zoneDataToFilter: string[], zoneDataForCondition: string[]) {
+    return zoneDataToFilter.filter((zone: string) => !zoneDataForCondition.includes(zone));
+  }
+
+  assignZoneValue(zone: string[], selectedZone: string[]) {
+    return zone.length > 0 ? zone : selectedZone;
+  }
+
   submit() {
+    const sourceZones: Zone = { added: [], removed: [] };
+    const destZones: Zone = { added: [], removed: [] };
+
     if (this.pipeForm.invalid) {
       return;
     }
@@ -119,20 +137,48 @@ export class RgwMultisiteSyncPipeModalComponent implements OnInit {
       this.pipeForm.setErrors({ cdSubmitButton: true });
       return;
     }
-    this.rgwMultisiteService.createEditSyncPipe(this.pipeForm.getRawValue()).subscribe(
-      () => {
-        const action = this.editing ? 'Modified' : 'Created';
-        this.notificationService.show(
-          NotificationType.success,
-          $localize`${action} Sync Pipe '${this.pipeForm.getValue('pipe_id')}'`
-        );
-        this.activeModal.close('success');
-      },
-      () => {
-        // Reset the 'Submit' button.
-        this.pipeForm.setErrors({ cdSubmitButton: true });
-        this.activeModal.dismiss();
-      }
-    );
+
+    if (this.editing) {
+      destZones.removed = this.getZoneData(
+        this.pipeSelectedRow.dest.zones,
+        this.destZones.data.selected
+      );
+      destZones.added = this.getZoneData(
+        this.destZones.data.selected,
+        this.pipeSelectedRow.dest.zones
+      );
+      sourceZones.removed = this.getZoneData(
+        this.pipeSelectedRow.source.zones,
+        this.sourceZones.data.selected
+      );
+      sourceZones.added = this.getZoneData(
+        this.sourceZones.data.selected,
+        this.pipeSelectedRow.source.zones
+      );
+    }
+    sourceZones.added = this.assignZoneValue(sourceZones.added, this.sourceZones.data.selected);
+    destZones.added = this.assignZoneValue(destZones.added, this.destZones.data.selected);
+
+    this.rgwMultisiteService
+      .createEditSyncPipe({
+        ...this.pipeForm.getRawValue(),
+        source_zones: sourceZones,
+        destination_zones: destZones
+      })
+      .subscribe(
+        () => {
+          const action = this.editing ? this.succeededLabels.EDITED : this.succeededLabels.CREATED;
+          this.notificationService.show(
+            NotificationType.success,
+            $localize`${action} Sync Pipe '${this.pipeForm.getValue('pipe_id')}'`
+          );
+          this.activeModal.close(NotificationType.success);
+        },
+        () => {
+          // Reset the 'Submit' button.
+          this.pipeForm.setErrors({ cdSubmitButton: true });
+          this.activeModal.dismiss();
+        }
+      );
   }
 }
index b9f16ab4d3c7b223621bbda44f36bfdb5580bcbf..8f45ce49bbf8e3f2995032af62502cfda0cec609 100644 (file)
             </cd-table-actions>
           </div>
         </cd-table>
-        <cd-alert-panel
-          *ngIf="dirFlowSelection.hasSelection"
-          type="info"
-          title="Directional Flow 'edit' & 'delete' actions disabled"
-          i18n-title
-          i18n>
-          Due to some internal dependencies these actions are disabled, it will get enabled once the issue gets resolved.
-        </cd-alert-panel>
       </ng-template>
     </ng-container>
     <ng-container ngbNavItem="pipe">
 <ng-template #deleteTpl>
   <cd-alert-panel type="danger"
                   i18n>
-    Are you sure you want to delete these Flow?
+    Deleting {{ resourceType | upperFirst }} may disrupt data synchronization
   </cd-alert-panel>
 </ng-template>
old mode 100644 (file)
new mode 100755 (executable)
index bbc5ac0..f613551
@@ -17,6 +17,12 @@ import { RgwMultisiteSyncFlowModalComponent } from '../rgw-multisite-sync-flow-m
 import { FlowType } from '../models/rgw-multisite';
 import { RgwMultisiteSyncPipeModalComponent } from '../rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+
+enum MultisiteResourceType {
+  flow = 'flow',
+  pipe = 'pipe'
+}
 
 @Component({
   selector: 'cd-rgw-multisite-sync-policy-details',
@@ -34,6 +40,7 @@ export class RgwMultisiteSyncPolicyDetailsComponent implements OnChanges {
   @ViewChild('deleteTpl', { static: true })
   deleteTpl: TemplateRef<any>;
 
+  resourceType: MultisiteResourceType = MultisiteResourceType.flow;
   flowType = FlowType;
   modalRef: NgbModalRef;
   symmetricalFlowData: any = [];
@@ -136,22 +143,17 @@ export class RgwMultisiteSyncPolicyDetailsComponent implements OnChanges {
       click: () => this.openModal(FlowType.directional),
       canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
     };
-    const dirEditAction: CdTableAction = {
-      permission: 'update',
-      icon: Icons.edit,
-      name: this.actionLabels.EDIT,
-      click: () => this.openModal(FlowType.directional, true),
-      disable: () => true // TODO: disabling 'edit' as we are not getting flow ID from backend which is needed for edit
-    };
     const dirDeleteAction: CdTableAction = {
       permission: 'delete',
       icon: Icons.destroy,
-      disable: () => true, // TODO: disabling 'delete' as we are not getting flow ID from backend which is needed for deletion
+      // TODO: disabling 'delete' as we are not getting flow_id from backend which is needed for deletion
+      disable: () =>
+        'Deleting the directional flow is disabled in the UI. Please use CLI to delete the directional flow',
       name: this.actionLabels.DELETE,
       click: () => this.deleteFlow(FlowType.directional),
-      canBePrimary: (selection: CdTableSelection) => selection.hasMultiSelection
+      canBePrimary: (selection: CdTableSelection) => selection.hasSelection
     };
-    this.dirFlowTableActions = [dirAddAction, dirEditAction, dirDeleteAction];
+    this.dirFlowTableActions = [dirAddAction, dirDeleteAction];
     const pipeAddAction: CdTableAction = {
       permission: 'create',
       icon: Icons.add,
@@ -229,13 +231,14 @@ export class RgwMultisiteSyncPolicyDetailsComponent implements OnChanges {
 
     try {
       const res = await this.modalRef.result;
-      if (res === 'success') {
+      if (res === NotificationType.success) {
         this.loadData();
       }
     } catch (err) {}
   }
 
   deleteFlow(flowType: FlowType) {
+    this.resourceType = MultisiteResourceType.flow;
     let selection = this.symFlowSelection;
     if (flowType === FlowType.directional) {
       selection = this.dirFlowSelection;
@@ -297,13 +300,14 @@ export class RgwMultisiteSyncPolicyDetailsComponent implements OnChanges {
 
     try {
       const res = await this.modalRef.result;
-      if (res === 'success') {
+      if (res === NotificationType.success) {
         this.loadData();
       }
     } catch (err) {}
   }
 
   deletePipe() {
+    this.resourceType = MultisiteResourceType.pipe;
     const pipeIds = this.pipeSelection.selected.map((pipe: any) => pipe.id);
     this.cdsModalService.show(CriticalConfirmationModalComponent, {
       itemDescription: this.pipeSelection.hasSingleSelection ? $localize`Pipe` : $localize`Pipes`,
index 3c7dc088d2f85101da324629a838895d35600c4c..91db38d319a1b3e45ab35460f94291933f8e7bc5 100644 (file)
@@ -227,6 +227,7 @@ const routes: Routes = [
       {
         path: 'configuration',
         component: RgwMultisiteDetailsComponent,
+        data: { breadcrumbs: 'Configuration' },
         children: [
           {
             path: 'setup-multisite-replication',
@@ -238,6 +239,7 @@ const routes: Routes = [
       {
         path: 'sync-policy',
         component: RgwMultisiteSyncPolicyComponent,
+        data: { breadcrumbs: 'Sync-policy' },
         children: [
           {
             path: `${URLVerbs.CREATE}`,
index 1fb9771795f6c5085b67992f8666201a5ca79848..8f98f1f62a0a8f41083c08c9281c357bda603270 100644 (file)
@@ -11370,6 +11370,8 @@ paths:
               required:
               - group_id
               - pipe_id
+              - source_zones
+              - destination_zones
               type: object
       responses:
         '200':
old mode 100644 (file)
new mode 100755 (executable)
index 013fc0c..1f9fa74
@@ -2071,31 +2071,49 @@ class RgwMultisite:
             raise DashboardException(error, http_status_code=500, component='rgw')
 
     def create_sync_flow(self, group_id: str, flow_id: str, flow_type: str,
-                         zones: Optional[List[str]] = None, bucket_name: str = '',
-                         source_zone: Optional[List[str]] = None,
-                         destination_zone: Optional[List[str]] = None):
+                         zones: Optional[Dict[str, List]] = None, bucket_name: str = '',
+                         source_zone: Optional[str] = None,
+                         destination_zone: Optional[str] = None):
         rgw_sync_policy_cmd = ['sync', 'group', 'flow', 'create', '--group-id', group_id,
                                '--flow-id', flow_id, '--flow-type', SyncFlowTypes[flow_type].value]
 
+        if bucket_name:
+            rgw_sync_policy_cmd += ['--bucket', bucket_name]
+
         if SyncFlowTypes[flow_type].value == 'directional':
+
             if source_zone is not None:
-                rgw_sync_policy_cmd += ['--source-zone', ','.join(source_zone)]
+                rgw_sync_policy_cmd += ['--source-zone', source_zone]
+
             if destination_zone is not None:
-                rgw_sync_policy_cmd += ['--dest-zone', ','.join(destination_zone)]
+                rgw_sync_policy_cmd += ['--dest-zone', destination_zone]
+
+            logger.info("Creating directional flow! %s", rgw_sync_policy_cmd)
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
+                if exit_code > 0:
+                    raise DashboardException(f'Unable to create sync flow: {err}',
+                                             http_status_code=500, component='rgw')
+            except SubprocessError as error:
+                raise DashboardException(error, http_status_code=500, component='rgw')
+
         else:
-            if zones:
-                rgw_sync_policy_cmd += ['--zones', ','.join(zones)]
+            if zones is not None and (zones['added'] or zones['removed']):
+                if len(zones['added']) > 0:
+                    rgw_sync_policy_cmd += ['--zones', ','.join(zones['added'])]
 
-        if bucket_name:
-            rgw_sync_policy_cmd += ['--bucket', bucket_name]
+                    logger.info("Creating symmetrical flow! %s", rgw_sync_policy_cmd)
+                    try:
+                        exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
+                        if exit_code > 0:
+                            raise DashboardException(f'Unable to create sync flow: {err}',
+                                                     http_status_code=500, component='rgw')
+                    except SubprocessError as error:
+                        raise DashboardException(error, http_status_code=500, component='rgw')
 
-        try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
-            if exit_code > 0:
-                raise DashboardException(f'Unable to create sync flow: {err}',
-                                         http_status_code=500, component='rgw')
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
+                if len(zones['removed']) > 0:
+                    self.remove_sync_flow(group_id, flow_id, flow_type, source_zone,
+                                          destination_zone, zones['removed'], bucket_name)
 
     def remove_sync_flow(self, group_id: str, flow_id: str, flow_type: str,
                          source_zone='', destination_zone='',
@@ -2112,6 +2130,7 @@ class RgwMultisite:
         if bucket_name:
             rgw_sync_policy_cmd += ['--bucket', bucket_name]
 
+        logger.info("Removing sync flow! %s", rgw_sync_policy_cmd)
         try:
             exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2121,36 +2140,44 @@ class RgwMultisite:
             raise DashboardException(error, http_status_code=500, component='rgw')
 
     def create_sync_pipe(self, group_id: str, pipe_id: str,
-                         source_zones: Optional[List[str]] = None,
-                         destination_zones: Optional[List[str]] = None,
+                         source_zones: Dict[str, Any],
+                         destination_zones: Dict[str, Any],
                          source_bucket: str = '',
                          destination_bucket: str = '',
                          bucket_name: str = ''):
-        rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'create',
-                               '--group-id', group_id, '--pipe-id', pipe_id]
 
-        if bucket_name:
-            rgw_sync_policy_cmd += ['--bucket', bucket_name]
+        if source_zones['added'] or destination_zones['added']:
+            rgw_sync_policy_cmd = ['sync', 'group', 'pipe', 'create',
+                                   '--group-id', group_id, '--pipe-id', pipe_id]
 
-        if source_zones:
-            rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones)]
+            if bucket_name:
+                rgw_sync_policy_cmd += ['--bucket', bucket_name]
 
-        if destination_zones:
-            rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones)]
+            if source_bucket:
+                rgw_sync_policy_cmd += ['--source-bucket', source_bucket]
 
-        if source_bucket:
-            rgw_sync_policy_cmd += ['--source-bucket', source_bucket]
+            if destination_bucket:
+                rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
 
-        if destination_bucket:
-            rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
+            if source_zones['added']:
+                rgw_sync_policy_cmd += ['--source-zones', ','.join(source_zones['added'])]
 
-        try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
-            if exit_code > 0:
-                raise DashboardException(f'Unable to create sync pipe: {err}',
-                                         http_status_code=500, component='rgw')
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
+            if destination_zones['added']:
+                rgw_sync_policy_cmd += ['--dest-zones', ','.join(destination_zones['added'])]
+
+            logger.info("Creating sync pipe!")
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
+                if exit_code > 0:
+                    raise DashboardException(f'Unable to create sync pipe: {err}',
+                                             http_status_code=500, component='rgw')
+            except SubprocessError as error:
+                raise DashboardException(error, http_status_code=500, component='rgw')
+
+        if source_zones['removed'] or destination_zones['removed']:
+            self.remove_sync_pipe(group_id, pipe_id, source_zones['removed'],
+                                  destination_zones['removed'], destination_bucket,
+                                  bucket_name)
 
     def remove_sync_pipe(self, group_id: str, pipe_id: str,
                          source_zones: Optional[List[str]] = None,
@@ -2171,6 +2198,7 @@ class RgwMultisite:
         if destination_bucket:
             rgw_sync_policy_cmd += ['--dest-bucket', destination_bucket]
 
+        logger.info("Removing sync pipe! %s", rgw_sync_policy_cmd)
         try:
             exit_code, _, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd)
             if exit_code > 0:
@@ -2191,10 +2219,12 @@ class RgwMultisite:
         # create a sync flow with source and destination zones
         self.create_sync_flow(_SYNC_GROUP_ID, _SYNC_FLOW_ID,
                               SyncFlowTypes.symmetrical.value,
-                              zones=zone_names)
+                              zones={'added': zone_names, 'removed': []})
         # create a sync pipe with source and destination zones
-        self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID, source_zones=['*'],
-                              destination_zones=['*'], source_bucket='*', destination_bucket='*')
+        self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID,
+                              source_zones={'added': '*', 'removed': []},
+                              destination_zones={'added': '*', 'removed': []}, source_bucket='*',
+                              destination_bucket='*')
         # period update --commit
         self.update_period()