]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: RGW multisite sync remove zones fix 59825/head
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>
Tue, 17 Sep 2024 09:46:22 +0000 (15:16 +0530)
Fixes: https://tracker.ceph.com/issues/66945
Signed-off-by: Naman Munet <nmunet@redhat.com>
(cherry picked from commit 5eee7e8105167dc4f0542f62d041fbcb143611d0)

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 710681d..8260799
@@ -178,9 +178,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,
@@ -200,11 +200,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 b93c4ae..16cf5da
@@ -16,6 +16,12 @@ import { TableComponent } from '~/app/shared/datatable/table/table.component';
 import { RgwMultisiteSyncFlowModalComponent } from '../rgw-multisite-sync-flow-modal/rgw-multisite-sync-flow-modal.component';
 import { FlowType } from '../models/rgw-multisite';
 import { RgwMultisiteSyncPipeModalComponent } from '../rgw-multisite-sync-pipe-modal/rgw-multisite-sync-pipe-modal.component';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+
+enum MultisiteResourceType {
+  flow = 'flow',
+  pipe = 'pipe'
+}
 
 @Component({
   selector: 'cd-rgw-multisite-sync-policy-details',
@@ -33,6 +39,7 @@ export class RgwMultisiteSyncPolicyDetailsComponent implements OnChanges {
   @ViewChild('deleteTpl', { static: true })
   deleteTpl: TemplateRef<any>;
 
+  resourceType: MultisiteResourceType = MultisiteResourceType.flow;
   flowType = FlowType;
   modalRef: NgbModalRef;
   symmetricalFlowData: any = [];
@@ -134,22 +141,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,
@@ -227,13 +229,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;
@@ -295,13 +298,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.modalService.show(CriticalConfirmationModalComponent, {
       itemDescription: this.pipeSelection.hasSingleSelection ? $localize`Pipe` : $localize`Pipes`,
index aa51e7502bb53d74e8c6668f97c674096183a01c..6c02f82cc7fa1b82eca52235b90d0157dbe54ed4 100644 (file)
@@ -220,11 +220,13 @@ const routes: Routes = [
       { path: '', redirectTo: 'configuration', pathMatch: 'full' },
       {
         path: 'configuration',
-        component: RgwMultisiteDetailsComponent
+        component: RgwMultisiteDetailsComponent,
+        data: { breadcrumbs: 'Configuration' }
       },
       {
         path: 'sync-policy',
         component: RgwMultisiteSyncPolicyComponent,
+        data: { breadcrumbs: 'Sync-policy' },
         children: [
           {
             path: `${URLVerbs.CREATE}`,
index 2e82bf220ce5bfcf51d7fcc252776e57ff6be60b..a006d41ed685d9ec3ae21010528e89dc7f1ecc32 100644 (file)
@@ -10891,6 +10891,8 @@ paths:
               required:
               - group_id
               - pipe_id
+              - source_zones
+              - destination_zones
               type: object
       responses:
         '200':
old mode 100644 (file)
new mode 100755 (executable)
index b604ba3..c07b126
@@ -1973,31 +1973,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='',
@@ -2014,6 +2032,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:
@@ -2023,36 +2042,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,
@@ -2073,6 +2100,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:
@@ -2093,10 +2121,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()