From 8ae20324b61d140204b15a23ccedab178c6dcc7c Mon Sep 17 00:00:00 2001 From: Nizamudeen A Date: Fri, 24 May 2024 19:50:11 +0530 Subject: [PATCH] mgr/dashboard: apply replication policy for a bucket On a normal multisite configured cluster, you can create a bucket with this replication enabled which will stop the normal syncing and starts doing the granular bucket syncing; meaning only the bucket with the replication enabled will be syncing to the secondary site. To enable replication, there should be a group policy created in the primary site. If no group policy is there, the dashboard will create one with bidirectional rule and add all the zones in the zonegroup for syncing. Fixes: https://tracker.ceph.com/issues/66239 Signed-off-by: Nizamudeen A --- src/pybind/mgr/dashboard/controllers/rgw.py | 21 +++++- .../src/app/ceph/rgw/models/rgw-daemon.ts | 1 + .../rgw-bucket-form.component.html | 49 ++++++++++++ .../rgw-bucket-form.component.spec.ts | 8 ++ .../rgw-bucket-form.component.ts | 27 ++++++- .../rgw-daemon-list.component.spec.ts | 1 + .../rgw-overview-dashboard.component.spec.ts | 1 + .../frontend/src/app/ceph/rgw/rgw.module.ts | 2 +- .../app/shared/api/rgw-bucket.service.spec.ts | 5 +- .../src/app/shared/api/rgw-bucket.service.ts | 4 +- .../app/shared/api/rgw-multisite.service.ts | 4 + src/pybind/mgr/dashboard/openapi.yaml | 3 + .../mgr/dashboard/services/rgw_client.py | 75 ++++++++++++++++++- src/pybind/mgr/dashboard/tests/test_rgw.py | 10 +++ 14 files changed, 198 insertions(+), 13 deletions(-) diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index f48dc592292f..ab0f57509e07 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -14,7 +14,7 @@ from ..rest_client import RequestException from ..security import Permission, Scope from ..services.auth import AuthManager, JwtManager from ..services.ceph_service import CephService -from ..services.rgw_client import NoRgwDaemonsException, RgwClient, RgwMultisite +from ..services.rgw_client import _SYNC_GROUP_ID, NoRgwDaemonsException, RgwClient, RgwMultisite from ..tools import json_str_to_object, str_to_bool from . import APIDoc, APIRouter, BaseController, CreatePermission, \ CRUDCollectionMethod, CRUDEndpoint, DeletePermission, Endpoint, \ @@ -242,6 +242,7 @@ class RgwDaemon(RESTController): 'server_hostname': hostname, 'realm_name': metadata['realm_name'], 'zonegroup_name': metadata['zonegroup_name'], + 'zonegroup_id': metadata['zonegroup_id'], 'zone_name': metadata['zone_name'], 'default': instance.daemon.name == metadata['id'], 'port': int(port) if port else None @@ -307,6 +308,8 @@ class RgwSite(RgwRESTController): return RgwClient.admin_instance(daemon_name=daemon_name).get_realms() if query == 'default-realm': return RgwClient.admin_instance(daemon_name=daemon_name).get_default_realm() + if query == 'default-zonegroup': + return RgwMultisite().get_all_zonegroups_info()['default_zonegroup'] # @TODO: for multisite: by default, retrieve cluster topology/map. raise DashboardException(http_status_code=501, component='rgw', msg='Not Implemented') @@ -396,6 +399,16 @@ class RgwBucket(RgwRESTController): rgw_client = RgwClient.instance(owner, daemon_name) return rgw_client.set_acl(bucket_name, acl) + def _set_replication(self, bucket_name: str, replication: bool, owner, daemon_name): + multisite = RgwMultisite() + rgw_client = RgwClient.instance(owner, daemon_name) + zonegroup_name = RgwClient.admin_instance(daemon_name=daemon_name).get_default_zonegroup() + + policy_exists = multisite.policy_group_exists(_SYNC_GROUP_ID, zonegroup_name) + if replication and not policy_exists: + multisite.create_dashboard_admin_sync_group(zonegroup_name=zonegroup_name) + return rgw_client.set_bucket_replication(bucket_name, replication) + @staticmethod def strip_tenant_from_bucket_name(bucket_name): # type (str) -> str @@ -463,9 +476,11 @@ class RgwBucket(RgwRESTController): lock_retention_period_days=None, lock_retention_period_years=None, encryption_state='false', encryption_type=None, key_id=None, tags=None, - bucket_policy=None, canned_acl=None, daemon_name=None): + bucket_policy=None, canned_acl=None, replication='false', + daemon_name=None): lock_enabled = str_to_bool(lock_enabled) encryption_state = str_to_bool(encryption_state) + replication = str_to_bool(replication) try: rgw_client = RgwClient.instance(uid, daemon_name) result = rgw_client.create_bucket(bucket, zonegroup, @@ -488,6 +503,8 @@ class RgwBucket(RgwRESTController): if canned_acl: self._set_acl(bucket, canned_acl, uid, daemon_name) + if replication: + self._set_replication(bucket, replication, uid, daemon_name) return result except RequestException as e: # pragma: no cover - handling is too obvious raise DashboardException(e, http_status_code=500, component='rgw') diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-daemon.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-daemon.ts index c685ba027008..179d7b5ab9ac 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-daemon.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-daemon.ts @@ -5,6 +5,7 @@ export class RgwDaemon { server_hostname: string; realm_name: string; zonegroup_name: string; + zonegroup_id: string; zone_name: string; default: boolean; port: number; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html index 551aad7ac558..22b094d6bd54 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html @@ -387,6 +387,49 @@ + +
+ Replication +
+ +
+ + + Enables replication for the objects in the bucket. + +
+ + Multi-site needs to be configured on the current realm or you need to be on + the default zonegroup to enable replication. + + + A bi-directional sync policy group will be created by the dashboard along with flows and pipes. + The pipe id will then be used for applying the replication policy to the bucket. + +
+
+
+
+
+ + +
+ Checking multi-site status... +
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts index 44318eda88e1..34619824f206 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.spec.ts @@ -307,4 +307,12 @@ describe('RgwBucketFormComponent', () => { expectValidLockInputs(false, 'Compliance', '2'); }); }); + + describe('bucket replication', () => { + it('should validate replication input', () => { + formHelper.setValue('replication', true); + fixture.detectChanges(); + formHelper.expectValid('replication'); + }); + }); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts index 836ab3d301b3..59f6dd6e3995 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts @@ -10,7 +10,7 @@ import { AbstractControl, Validators } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import _ from 'lodash'; -import { forkJoin } from 'rxjs'; +import { Observable, forkJoin } from 'rxjs'; import * as xml2js from 'xml2js'; import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service'; @@ -36,6 +36,9 @@ import { RgwBucketVersioning } from '../models/rgw-bucket-versioning'; import { RgwConfigModalComponent } from '../rgw-config-modal/rgw-config-modal.component'; import { BucketTagModalComponent } from '../bucket-tag-modal/bucket-tag-modal.component'; import { TextAreaJsonFormatterService } from '~/app/shared/services/text-area-json-formatter.service'; +import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service'; +import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service'; +import { map, switchMap } from 'rxjs/operators'; @Component({ selector: 'cd-rgw-bucket-form', @@ -72,6 +75,8 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC ]; grantees: string[] = [Grantee.Owner, Grantee.Everyone, Grantee.AuthenticatedUsers]; aclPermissions: AclPermissionsType[] = [aclPermission.FullControl]; + multisiteStatus$: Observable; + isDefaultZoneGroup$: Observable; get isVersioningEnabled(): boolean { return this.bucketForm.getValue('versioning'); @@ -92,7 +97,9 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC private rgwEncryptionModal: RgwBucketEncryptionModel, private textAreaJsonFormatterService: TextAreaJsonFormatterService, public actionLabels: ActionLabelsI18n, - private readonly changeDetectorRef: ChangeDetectorRef + private readonly changeDetectorRef: ChangeDetectorRef, + private rgwMultisiteService: RgwMultisiteService, + private rgwDaemonService: RgwDaemonService ) { super(); this.editing = this.router.url.startsWith(`/rgw/bucket/${URLVerbs.EDIT}`); @@ -154,7 +161,8 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC lock_retention_period_days: [10, [CdValidators.number(false), lockDaysValidator]], bucket_policy: ['{}', CdValidators.json()], grantee: [Grantee.Owner, [Validators.required]], - aclPermission: [[aclPermission.FullControl], [Validators.required]] + aclPermission: [[aclPermission.FullControl], [Validators.required]], + replication: [false] }); } @@ -162,6 +170,16 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC const promises = { owners: this.rgwUserService.enumerate() }; + this.multisiteStatus$ = this.rgwMultisiteService.status(); + this.isDefaultZoneGroup$ = this.rgwDaemonService.selectedDaemon$.pipe( + switchMap((daemon) => + this.rgwSiteService.get('default-zonegroup').pipe( + map((defaultZoneGroup) => { + return daemon.zonegroup_id === defaultZoneGroup; + }) + ) + ) + ); this.kmsProviders = this.rgwEncryptionModal.kmsProviders; this.rgwBucketService.getEncryptionConfig().subscribe((data) => { @@ -331,7 +349,8 @@ export class RgwBucketFormComponent extends CdForm implements OnInit, AfterViewC values['keyId'], xmlStrTags, bucketPolicy, - cannedAcl + cannedAcl, + values['replication'] ) .subscribe( () => { diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts index bdb4decd9dab..4936ee54a484 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-daemon-list/rgw-daemon-list.component.spec.ts @@ -32,6 +32,7 @@ describe('RgwDaemonListComponent', () => { server_hostname: 'ceph', realm_name: 'realm1', zonegroup_name: 'zg1-realm1', + zonegroup_id: 'zg1-id', zone_name: 'zone1-zg1-realm1', default: true, port: 80 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts index 4f024f25f41c..36cafa855a3f 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-overview-dashboard/rgw-overview-dashboard.component.spec.ts @@ -26,6 +26,7 @@ describe('RgwOverviewDashboardComponent', () => { server_hostname: 'ceph', realm_name: 'realm1', zonegroup_name: 'zg1-realm1', + zonegroup_id: 'zg1-id', zone_name: 'zone1-zg1-realm1', default: true, port: 80 diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts index 04755928b0a9..248a59292d72 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts @@ -51,7 +51,7 @@ import { BucketTagModalComponent } from './bucket-tag-modal/bucket-tag-modal.com CommonModule, SharedModule, FormsModule, - ReactiveFormsModule, + ReactiveFormsModule.withConfig({ callSetDisabledState: 'whenDisabledForLegacyCode' }), PerformanceCounterModule, NgbNavModule, RouterModule, diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts index eaed2c4abac2..f930af6cba3c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts @@ -62,11 +62,12 @@ describe('RgwBucketService', () => { 'qwerty1', null, null, - 'private' + 'private', + 'true' ) .subscribe(); const req = httpTesting.expectOne( - `api/rgw/bucket?bucket=foo&uid=bar&zonegroup=default&lock_enabled=false&lock_mode=COMPLIANCE&lock_retention_period_days=5&encryption_state=true&encryption_type=aws%253Akms&key_id=qwerty1&tags=null&bucket_policy=null&canned_acl=private&${RgwHelper.DAEMON_QUERY_PARAM}` + `api/rgw/bucket?bucket=foo&uid=bar&zonegroup=default&lock_enabled=false&lock_mode=COMPLIANCE&lock_retention_period_days=5&encryption_state=true&encryption_type=aws%253Akms&key_id=qwerty1&tags=null&bucket_policy=null&canned_acl=private&replication=true&${RgwHelper.DAEMON_QUERY_PARAM}` ); expect(req.request.method).toBe('POST'); }); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts index ddeeadf5e49b..8c9a9bacf4d4 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts @@ -62,7 +62,8 @@ export class RgwBucketService extends ApiClient { key_id: string, tags: string, bucketPolicy: string, - cannedAcl: string + cannedAcl: string, + replication: string ) { return this.rgwDaemonService.request((params: HttpParams) => { const paramsObject = { @@ -78,6 +79,7 @@ export class RgwBucketService extends ApiClient { tags: tags, bucket_policy: bucketPolicy, canned_acl: cannedAcl, + replication: replication, daemon_name: params.get('daemon_name') }; diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts index da789d29fc49..9081c21e4400 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts @@ -30,4 +30,8 @@ export class RgwMultisiteService { getSyncStatus() { return this.http.get(`${this.url}/sync_status`); } + + status() { + return this.http.get(`${this.uiUrl}/status`); + } } diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml index dd907e1dc7f5..d98613b53d62 100644 --- a/src/pybind/mgr/dashboard/openapi.yaml +++ b/src/pybind/mgr/dashboard/openapi.yaml @@ -10615,6 +10615,9 @@ paths: type: string placement_target: type: string + replication: + default: 'false' + type: string tags: type: string uid: diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py index fb6c83d60cbe..0d7df8e31e87 100644 --- a/src/pybind/mgr/dashboard/services/rgw_client.py +++ b/src/pybind/mgr/dashboard/services/rgw_client.py @@ -29,6 +29,10 @@ except ImportError: logger = logging.getLogger('rgw_client') +_SYNC_GROUP_ID = 'dashboard_admin_group' +_SYNC_FLOW_ID = 'dashboard_admin_flow' +_SYNC_PIPE_ID = 'dashboard_admin_pipe' + class NoRgwDaemonsException(Exception): def __init__(self): @@ -605,6 +609,9 @@ class RgwClient(RestClient): return realm_info['name'] return None + def get_default_zonegroup(self): + return self.daemon.zonegroup_name + @RestClient.api_get('/{bucket_name}?versioning') def get_bucket_versioning(self, bucket_name, request=None): """ @@ -984,6 +991,38 @@ class RgwClient(RestClient): raise DashboardException(msg=msg, component='rgw') return retention_period_days, retention_period_years + @RestClient.api_put('/{bucket_name}?replication') + def set_bucket_replication(self, bucket_name, replication: bool, request=None): + # pGenerate the minimum replication configuration + # required for enabling the replication + root = ET.Element('ReplicationConfiguration', + xmlns='http://s3.amazonaws.com/doc/2006-03-01/') + role = ET.SubElement(root, 'Role') + role.text = f'{bucket_name}_replication_role' + + rule = ET.SubElement(root, 'Rule') + rule_id = ET.SubElement(rule, 'ID') + rule_id.text = _SYNC_PIPE_ID + + status = ET.SubElement(rule, 'Status') + status.text = 'Enabled' if replication else 'Disabled' + + filter_elem = ET.SubElement(rule, 'Filter') + prefix = ET.SubElement(filter_elem, 'Prefix') + prefix.text = '' + + destination = ET.SubElement(rule, 'Destination') + + bucket = ET.SubElement(destination, 'Bucket') + bucket.text = bucket_name + + replication_config = ET.tostring(root, encoding='utf-8', method='xml').decode() + + try: + request = request(data=replication_config) + except RequestException as e: + raise DashboardException(msg=str(e), component='rgw') + class SyncStatus(Enum): enabled = 'enabled' @@ -1655,8 +1694,8 @@ class RgwMultisite: rgw_realm_list = self.list_realms() rgw_zonegroup_list = self.list_zonegroups() rgw_zone_list = self.list_zones() - if len(rgw_realm_list['realms']) < 1 and len(rgw_zonegroup_list['zonegroups']) < 1 \ - and len(rgw_zone_list['zones']) < 1: + if len(rgw_realm_list['realms']) < 1 and len(rgw_zonegroup_list['zonegroups']) <= 1 \ + and len(rgw_zone_list['zones']) <= 1: is_multisite_configured = False return is_multisite_configured @@ -1772,10 +1811,13 @@ class RgwMultisite: except SubprocessError as error: raise DashboardException(error, http_status_code=500, component='rgw') - def get_sync_policy_group(self, group_id: str, bucket_name: str = ''): + def get_sync_policy_group(self, group_id: str, bucket_name: str = '', + zonegroup_name: str = ''): rgw_sync_policy_cmd = ['sync', 'group', 'get', '--group-id', group_id] if bucket_name: rgw_sync_policy_cmd += ['--bucket', bucket_name] + if zonegroup_name: + rgw_sync_policy_cmd += ['--rgw-zonegroup', zonegroup_name] try: exit_code, out, err = mgr.send_rgwadmin_command(rgw_sync_policy_cmd) if exit_code > 0: @@ -1922,3 +1964,30 @@ class RgwMultisite: http_status_code=500, component='rgw') except SubprocessError as error: raise DashboardException(error, http_status_code=500, component='rgw') + + def create_dashboard_admin_sync_group(self, zonegroup_name: str = ''): + + zonegroup_info = self.get_zonegroup(zonegroup_name) + zone_names = [] + for zones in zonegroup_info['zones']: + zone_names.append(zones['name']) + + # create a sync policy group with status allowed + self.create_sync_policy_group(_SYNC_GROUP_ID, SyncStatus.allowed.value) + # 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) + # create a sync pipe with source and destination zones + self.create_sync_pipe(_SYNC_GROUP_ID, _SYNC_PIPE_ID, source_zones=['*'], + destination_zones=['*'], destination_buckets=['*']) + # period update --commit + self.update_period() + + def policy_group_exists(self, group_name: str, zonegroup_name: str): + try: + _ = self.get_sync_policy_group( + group_id=group_name, zonegroup_name=zonegroup_name) + return True + except DashboardException: + return False diff --git a/src/pybind/mgr/dashboard/tests/test_rgw.py b/src/pybind/mgr/dashboard/tests/test_rgw.py index b84838d10167..6e7a2960644a 100644 --- a/src/pybind/mgr/dashboard/tests/test_rgw.py +++ b/src/pybind/mgr/dashboard/tests/test_rgw.py @@ -93,6 +93,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'id': 'daemon1', 'realm_name': 'realm1', 'zonegroup_name': 'zg1', + 'zonegroup_id': 'zg1-id', 'zone_name': 'zone1', 'frontend_config#0': 'beast port=80' }, @@ -101,6 +102,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'id': 'daemon2', 'realm_name': 'realm2', 'zonegroup_name': 'zg2', + 'zonegroup_id': 'zg2-id', 'zone_name': 'zone2', 'frontend_config#0': 'beast ssl_port=443 ssl_certificate=config:/config' }, @@ -109,6 +111,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'id': 'daemon3', 'realm_name': 'realm3', 'zonegroup_name': 'zg3', + 'zonegroup_id': 'zg3-id', 'zone_name': 'zone3', 'frontend_config#0': 'beast ssl_endpoint=0.0.0.0:8080 ssl_certificate=config:/config' @@ -118,6 +121,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'id': 'daemon4', 'realm_name': 'realm4', 'zonegroup_name': 'zg4', + 'zonegroup_id': 'zg4-id', 'zone_name': 'zone4', 'frontend_config#0': 'beast ssl_certificate=config:/config' }, @@ -126,6 +130,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'id': 'daemon5', 'realm_name': 'realm5', 'zonegroup_name': 'zg5', + 'zonegroup_id': 'zg5-id', 'zone_name': 'zone5', 'frontend_config#0': 'beast endpoint=0.0.0.0:8445 ssl_certificate=config:/config' @@ -139,6 +144,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'server_hostname': 'host1', 'realm_name': 'realm1', 'zonegroup_name': 'zg1', + 'zonegroup_id': 'zg1-id', 'zone_name': 'zone1', 'default': True, 'port': 80 }, @@ -149,6 +155,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'server_hostname': 'host1', 'realm_name': 'realm2', 'zonegroup_name': 'zg2', + 'zonegroup_id': 'zg2-id', 'zone_name': 'zone2', 'default': False, 'port': 443, @@ -160,6 +167,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'server_hostname': 'host1', 'realm_name': 'realm3', 'zonegroup_name': 'zg3', + 'zonegroup_id': 'zg3-id', 'zone_name': 'zone3', 'default': False, 'port': 8080, @@ -171,6 +179,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'server_hostname': 'host1', 'realm_name': 'realm4', 'zonegroup_name': 'zg4', + 'zonegroup_id': 'zg4-id', 'zone_name': 'zone4', 'default': False, 'port': None, @@ -182,6 +191,7 @@ class RgwDaemonControllerTestCase(ControllerTestCase): 'server_hostname': 'host1', 'realm_name': 'realm5', 'zonegroup_name': 'zg5', + 'zonegroup_id': 'zg5-id', 'zone_name': 'zone5', 'default': False, 'port': 8445, -- 2.47.3