]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Add option to edit zone with keys/ 68317/head
authorAashish Sharma <aashish@li-e9bf2ecc-2ad7-11b2-a85c-baf05c5182ab.ibm.com>
Tue, 31 Mar 2026 04:30:23 +0000 (10:00 +0530)
committerAfreen Misbah <afreen@ibm.com>
Wed, 6 May 2026 17:06:09 +0000 (22:36 +0530)
argument like"sync_from" and "sync_from_all"

Currently, there is no option to configure the sync_from and sync_from_all keys directly while creating or editing a zone from the dashboard. These arguments are particularly important when setting up archive zones. In archive zones, duplicate objects appear when sync_from_all is set to true (which is the default). The fix is to:
1.Set sync_from_all to false
2.Set sync_from to point to the master zone only
This ensures that the archive zone syncs exclusively from the master zone, preventing duplicate object issues.

Fixes: https://tracker.ceph.com/issues/75950
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
(cherry picked from commit 50a1bc6fe8ada3554f6b3b0a9f8a6823ca717d09)

Conflicts:
src/pybind/mgr/dashboard/frontend/src/styles.scss
- recent merged PRs added at the end of file more rules which were giving conflict with this commit as it also adds a rule in the end

src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts
src/pybind/mgr/dashboard/frontend/src/styles.scss
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/rgw_client.py

index f0fdfcb95832fd3dcc78b558519d09508206150f..1e7a36885bc9cc03977e68710c1ad70af1fe4854 100755 (executable)
@@ -1516,11 +1516,13 @@ class RgwZone(RESTController):
     @allow_empty_body
     # pylint: disable=W0613
     def create(self, zone_name, zonegroup_name=None, default=False, master=False,
-               zone_endpoints=None, access_key=None, secret_key=None, tier_type=''):
+               zone_endpoints=None, access_key=None, secret_key=None, tier_type='',
+               sync_from: str = '', sync_from_all: str = ''):
         multisite_instance = RgwMultisite()
         result = multisite_instance.create_zone(zone_name, zonegroup_name, default,
                                                 master, zone_endpoints, access_key,
-                                                secret_key, tier_type)
+                                                secret_key, tier_type, sync_from,
+                                                sync_from_all)
         return result
 
     @allow_empty_body
@@ -1565,13 +1567,14 @@ class RgwZone(RESTController):
             master: str = '', zone_endpoints: str = '', access_key: str = '', secret_key: str = '',
             placement_target: str = '', data_pool: str = '', index_pool: str = '',
             data_extra_pool: str = '', storage_class: str = '', data_pool_class: str = '',
-            compression: str = '', tier_type: str = ''):
+            compression: str = '', tier_type: str = '', sync_from: str = '',
+            sync_from_all: str = ''):
         multisite_instance = RgwMultisite()
         result = multisite_instance.edit_zone(zone_name, new_zone_name, zonegroup_name, default,
                                               master, zone_endpoints, access_key, secret_key,
                                               placement_target, data_pool, index_pool,
                                               data_extra_pool, storage_class, data_pool_class,
-                                              compression, tier_type)
+                                              compression, tier_type, sync_from, sync_from_all)
         return result
 
     @Endpoint()
index 8791b9cfab16de00ed510ceba975d427476312df..72037d4dfe95db5b274eeb152aa2acdf20a597ed 100644 (file)
@@ -49,6 +49,8 @@ export class RgwZone {
   notif_pool: string;
   endpoints: string;
   tier_type: string;
+  sync_from: string;
+  sync_from_all: boolean;
 }
 
 export class SystemKey {
index a669723f369e632417bdbfba9c1a8e0b47fdd4fd..0d0604805c8b0b38d6551b27fae1aa2736a0d0cc 100644 (file)
           id="archive_zone"
           formControlName="archive_zone"
           cdOptionalField="Archive"
+          (checkedChange)="onArchiveZoneChange($event)"
           i18n
           >Archive
           <cd-help-text>
           </cd-help-text>
         </cds-checkbox>
       </div>
+      <div class="form-item">
+        <cds-checkbox
+          id="sync_from_all"
+          formControlName="sync_from_all"
+          cdOptionalField="Sync from all"
+          (checkedChange)="onZoneGroupChange(multisiteZoneForm.getValue('selectedZonegroup'))"
+          i18n
+          >Sync from all zones
+          <cd-help-text>
+            <span
+              >Enable this option to synchronize data from all zones.</span
+            >
+          </cd-help-text>
+          <div class="cds-mt-3">
+            <cd-alert-panel *ngIf="multisiteZoneForm.get('archive_zone')?.value === true"
+                            type="warning">
+                “Sync from all zones” is disabled to avoid duplicate objects in archive zones. Please select specific zones instead.
+            </cd-alert-panel>
+          </div>
+        </cds-checkbox>
+      </div>
+      <div *ngIf="multisiteZoneForm.get('sync_from_all')?.value === false"
+           class="form-item">
+        <cds-combo-box type="multi"
+                       selectionFeedback="top-after-reopen"
+                       label="Sync from zones"
+                       formControlName="sync_from_zones"
+                       id="sync_from_zones"
+                       [appendInline]="true"
+                       [items]="syncFromZonesOptions"
+                       i18n>
+          <cds-dropdown-list></cds-dropdown-list>
+        </cds-combo-box>
+        <cd-help-text>
+          <span i18n>Select specific zones to synchronize data from when "Sync from all zones" is disabled.</span>
+        </cd-help-text>
+      </div>
       <div class="form-item">
         <cds-text-label
           i18n
index 75edd53e47ce347c019ce11f86c3d8f5cfa78106..24b408a511141e700ea53f1130b52e6064fcfd1b 100644 (file)
@@ -14,6 +14,7 @@ import { NotificationService } from '~/app/shared/services/notification.service'
 import { RgwRealm, RgwZone, RgwZonegroup, SystemKey } from '../models/rgw-multisite';
 import { ModalCdsService } from '~/app/shared/services/modal-cds.service';
 import { CdForm } from '~/app/shared/forms/cd-form';
+import { ComboBoxItem } from '~/app/shared/models/combo-box.model';
 
 @Component({
   selector: 'cd-rgw-multisite-zone-form',
@@ -41,7 +42,7 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
   syncStatusTimedOut: boolean = false;
   bsModalRef: NgbModalRef;
   createSystemUser: boolean = false;
-  master_zone_of_master_zonegroup: RgwZone;
+  master_zone_name: string;
   masterZoneUser: any;
   access_key: any;
   master_zonegroup_of_realm: RgwZonegroup;
@@ -51,6 +52,8 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
     'To see or copy your S3 secret key, go to Object Gateway > Users and click on your user name. In Keys, click Show. View the secret key by clicking Show and copy the key by clicking Copy to Clipboard';
   AccessKeyText =
     'To see or copy your S3 access key, go to Object Gateway > Users and click on your user name. In Keys, click Show. View the access key by clicking Show and copy the key by clicking Copy to Clipboard';
+  syncFromZonesOptions: ComboBoxItem[] = [];
+
   constructor(
     public activeModal: NgbActiveModal,
     public actionLabels: ActionLabelsI18n,
@@ -99,14 +102,33 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
       placementDataExtraPool: new UntypedFormControl(null),
       storageClass: new UntypedFormControl(null),
       storageDataPool: new UntypedFormControl(null),
-      storageCompression: new UntypedFormControl(null)
+      storageCompression: new UntypedFormControl(null),
+      sync_from_zones: new UntypedFormControl(null),
+      sync_from_all: new UntypedFormControl(true)
     });
   }
 
   onZoneGroupChange(zonegroupName: string) {
+    this.syncFromZonesOptions = [];
+
     let zg = new RgwZonegroup();
     zg.name = zonegroupName;
+
     this.rgwZoneGroupService.get(zg).subscribe((zonegroup: RgwZonegroup) => {
+      const syncFromZones = this.info?.data?.info?.sync_from || [];
+
+      this.syncFromZonesOptions = zonegroup.zones
+        .filter((zone) => zone.name !== this.multisiteZoneForm.getValue('zoneName'))
+        .map((zone) => ({
+          name: zone.name,
+          content: zone.name,
+          selected: syncFromZones.includes(zone.name)
+        }));
+
+      this.multisiteZoneForm
+        .get('sync_from_zones')
+        .setValue(this.syncFromZonesOptions.filter((opt) => opt.selected));
+
       if (_.isEmpty(zonegroup.master_zone)) {
         this.multisiteZoneForm.get('master_zone').setValue(true);
         this.multisiteZoneForm.get('master_zone').disable();
@@ -116,6 +138,10 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
         this.multisiteZoneForm.get('master_zone').disable();
         this.disableMaster = true;
       }
+      const masterZone = this.zoneList.find((zone) => zone.id === zonegroup.master_zone);
+      if (masterZone) {
+        this.master_zone_name = masterZone.name;
+      }
     });
     if (
       this.multisiteZoneForm.getValue('selectedZonegroup') !==
@@ -165,6 +191,8 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
       this.multisiteZoneForm
         .get('archive_zone')
         .setValue(this.info.data.info.tier_type === 'archive');
+      this.onArchiveZoneChange(this.info.data.info.tier_type === 'archive');
+      this.multisiteZoneForm.get('sync_from_all').setValue(this.info.data.info.sync_from_all);
       this.multisiteZoneForm
         .get('placementTarget')
         .setValue((this.info.data?.parentNode || this.info.parent.data)?.default_placement);
@@ -244,6 +272,10 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
       this.zone.system_key.access_key = values['access_key'];
       this.zone.system_key.secret_key = values['secret_key'];
       this.zone.tier_type = values['archive_zone'] ? 'archive' : '';
+      this.zone.sync_from = values['sync_from_zones']
+        ? values['sync_from_zones'].map((zone: RgwZone) => zone.name).join(',')
+        : '';
+      this.zone.sync_from_all = values['sync_from_all'];
       this.rgwZoneService
         .create(
           this.zone,
@@ -274,6 +306,10 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
       this.zone.system_key.access_key = values['access_key'];
       this.zone.system_key.secret_key = values['secret_key'];
       this.zone.tier_type = values['archive_zone'] ? 'archive' : '';
+      this.zone.sync_from = values['sync_from_zones']
+        ? values['sync_from_zones'].map((zone: RgwZone) => zone.name).join(',')
+        : '';
+      this.zone.sync_from_all = values['sync_from_all'];
       this.rgwZoneService
         .update(
           this.zone,
@@ -304,4 +340,21 @@ export class RgwMultisiteZoneFormComponent extends CdForm implements OnInit {
         );
     }
   }
+
+  onArchiveZoneChange(isArchiveZone: boolean) {
+    if (isArchiveZone) {
+      this.multisiteZoneForm.get('sync_from_all').setValue(false);
+      this.multisiteZoneForm.get('sync_from_all').disable();
+      this.syncFromZonesOptions = this.syncFromZonesOptions.map((zone) => ({
+        ...zone,
+        selected: zone.name === this.master_zone_name
+      }));
+      this.multisiteZoneForm
+        .get('sync_from_zones')
+        .setValue(this.syncFromZonesOptions.filter((opt) => opt.selected));
+    } else {
+      this.multisiteZoneForm.get('sync_from_all').enable();
+      this.multisiteZoneForm.get('sync_from_all').setValue(true);
+    }
+  }
 }
index aa94d94d60d2c35ae15b57a702aa272f5682aaaa..01d0a6d8581c61cc0634392ba745c210f71afdd4 100644 (file)
@@ -33,7 +33,9 @@ export class RgwZoneService {
       zone_endpoints: endpoints,
       access_key: zone.system_key.access_key,
       secret_key: zone.system_key.secret_key,
-      tier_type: zone.tier_type
+      tier_type: zone.tier_type,
+      sync_from: zone.sync_from,
+      sync_from_all: zone.sync_from_all
     });
     return this.http.post(`${this.url}`, null, { params: params });
   }
@@ -99,7 +101,9 @@ export class RgwZoneService {
       storage_class: storageClass,
       data_pool_class: dataPoolClass,
       compression: compression,
-      tier_type: zone.tier_type
+      tier_type: zone.tier_type,
+      sync_from: zone.sync_from,
+      sync_from_all: zone.sync_from_all
     };
     return this.http.put(`${this.url}/${zone.name}`, requestBody);
   }
@@ -127,6 +131,9 @@ export class RgwZoneService {
     nodes['endpoints'] = zone.endpoints;
     nodes['is_master'] = zonegroup && zonegroup.master_zone === zone.id ? true : false;
     nodes['tier_type'] = zonegroup?.zones?.find((z) => z.name === zone.name)?.tier_type || '';
+    nodes['sync_from'] = zonegroup?.zones?.find((z) => z.name === zone.name)?.sync_from || [];
+    nodes['sync_from_all'] =
+      zonegroup?.zones?.find((z) => z.name === zone.name)?.sync_from_all ?? true;
     nodes['type'] = 'zone';
     const zoneNames = zones.map((zone: RgwZone) => {
       return zone['name'];
index 85fecc6447321c5e682d2f915daf2cd56a528f05..d81f64d8bbf97d9050fa25de63e4a38e9c4f46d5 100644 (file)
@@ -224,3 +224,7 @@ input:-webkit-autofill:active {
 .border-subtle {
   border: 1px solid var(--cds-border-subtle);
 }
+
+.popover {
+  --bs-popover-zindex: 9999;
+}
index 0b9e2aa6b649d855343351fce48074bf13462778..46fc5e9858b90f44437610666ae3937bc5be6b10 100644 (file)
@@ -16020,6 +16020,12 @@ paths:
                   type: boolean
                 secret_key:
                   type: string
+                sync_from:
+                  default: ''
+                  type: string
+                sync_from_all:
+                  default: ''
+                  type: string
                 tier_type:
                   default: ''
                   type: string
@@ -16394,6 +16400,12 @@ paths:
                 storage_class:
                   default: ''
                   type: string
+                sync_from:
+                  default: ''
+                  type: string
+                sync_from_all:
+                  default: ''
+                  type: string
                 tier_type:
                   default: ''
                   type: string
index f28edf31d38f31217abc81f934817aeded6da78e..23ea7dca9ca798de95a2c64177135aa0b3ffd29d 100755 (executable)
@@ -2414,7 +2414,8 @@ class RgwMultisite:
             raise DashboardException(error, http_status_code=500, component='rgw')
 
     def create_zone(self, zone_name, zonegroup_name, default, master, endpoints, access_key,
-                    secret_key, tier_type):
+                    secret_key, tier_type, sync_from: Optional[str] = None,
+                    sync_from_all: Optional[str] = None):
         rgw_zone_create_cmd = ['zone', 'create']
         cmd_create_zone_options = ['--rgw-zone', zone_name]
         if zonegroup_name != 'null':
@@ -2436,6 +2437,12 @@ class RgwMultisite:
         if tier_type:
             cmd_create_zone_options.append('--tier-type')
             cmd_create_zone_options.append(tier_type)
+        if sync_from is not None:
+            cmd_create_zone_options.append('--sync-from')
+            cmd_create_zone_options.append(sync_from)
+        if sync_from_all is not None:
+            cmd_create_zone_options.append('--sync-from-all')
+            cmd_create_zone_options.append(str(sync_from_all).lower())
         rgw_zone_create_cmd += cmd_create_zone_options
         try:
             exit_code, out, err = mgr.send_rgwadmin_command(rgw_zone_create_cmd)
@@ -2457,7 +2464,8 @@ class RgwMultisite:
         return '', ''
 
     def modify_zone(self, zone_name: str, zonegroup_name: str, default: str, master: str,
-                    endpoints: str, access_key: str, secret_key: str, tier_type: str):
+                    endpoints: str, access_key: str, secret_key: str, tier_type: str,
+                    sync_from: Optional[str] = None, sync_from_all: Optional[str] = None):
         rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zonegroup',
                                zonegroup_name, '--rgw-zone', zone_name]
         if endpoints:
@@ -2476,6 +2484,12 @@ class RgwMultisite:
         if tier_type is not None:
             rgw_zone_modify_cmd.append('--tier-type')
             rgw_zone_modify_cmd.append(tier_type)
+        if sync_from is not None:
+            rgw_zone_modify_cmd.append('--sync-from')
+            rgw_zone_modify_cmd.append(sync_from)
+        if sync_from_all is not None:
+            rgw_zone_modify_cmd.append('--sync-from-all')
+            rgw_zone_modify_cmd.append(str(sync_from_all).lower())
         try:
             exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
             if exit_code > 0:
@@ -2551,7 +2565,8 @@ class RgwMultisite:
                   master: str = '', endpoints: str = '', access_key: str = '', secret_key: str = '',
                   placement_target: str = '', data_pool: str = '', index_pool: str = '',
                   data_extra_pool: str = '', storage_class: str = '', data_pool_class: str = '',
-                  compression: str = '', tier_type: str = ''):
+                  compression: str = '', tier_type: str = '', sync_from: str = '',
+                  sync_from_all: str = ''):
         if new_zone_name != zone_name:
             rgw_zone_rename_cmd = ['zone', 'rename', '--rgw-zone',
                                    zone_name, '--zone-new-name', new_zone_name]
@@ -2565,7 +2580,7 @@ class RgwMultisite:
                 raise DashboardException(error, http_status_code=500, component='rgw')
             self.update_period()
         self.modify_zone(new_zone_name, zonegroup_name, default, master, endpoints, access_key,
-                         secret_key, tier_type)
+                         secret_key, tier_type, sync_from, sync_from_all)
 
         if placement_target:
             self.add_placement_targets_storage_class_zone(