summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr
diff options
context:
space:
mode:
authorAashish Sharma <aasharma@redhat.com>2023-03-28 10:04:25 +0200
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>2023-07-04 13:20:32 +0200
commit5415a2611de7c50cbf429e92b253ed7bbd2ab7ea (patch)
treec7d5dce136ea823724540afe24e212c5b06a1f27 /src/pybind/mgr
parentMerge pull request #52194 from zdover23/wip-doc-2023-06-26-radosgw-s3select-o... (diff)
downloadceph-5415a2611de7c50cbf429e92b253ed7bbd2ab7ea.tar.xz
ceph-5415a2611de7c50cbf429e92b253ed7bbd2ab7ea.zip
mgr/dashboard: Allow the user to import and export multi-site configuration
Fixes: https://tracker.ceph.com/issues/61776 Signed-off-by: Aashish Sharma <aasharma@redhat.com>
Diffstat (limited to 'src/pybind/mgr')
-rw-r--r--src/pybind/mgr/dashboard/controllers/rgw.py303
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html72
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts29
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts206
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts4
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.html70
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.scss0
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.spec.ts38
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.ts99
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component.spec.ts2
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component.spec.ts2
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts11
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html38
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss6
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts9
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts213
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.html65
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.scss0
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.spec.ts38
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.ts62
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html56
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.scss0
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts38
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts77
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.html39
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.ts31
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html51
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts85
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component.ts7
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts8
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts11
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts25
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts67
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts170
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts77
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html3
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts2
-rw-r--r--src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts3
-rw-r--r--src/pybind/mgr/dashboard/openapi.yaml195
-rw-r--r--src/pybind/mgr/dashboard/services/ceph_service.py29
-rw-r--r--src/pybind/mgr/dashboard/services/rgw_client.py975
-rw-r--r--src/pybind/mgr/rgw/module.py59
42 files changed, 2090 insertions, 1185 deletions
diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py
index 2dc5874ea22..3ba4cf4923e 100644
--- a/src/pybind/mgr/dashboard/controllers/rgw.py
+++ b/src/pybind/mgr/dashboard/controllers/rgw.py
@@ -12,12 +12,11 @@ from ..rest_client import RequestException
from ..security import Permission, Scope
from ..services.auth import AuthManager, JwtManager
from ..services.ceph_service import CephService
-from ..services.orchestrator import OrchClient
-from ..services.rgw_client import NoRgwDaemonsException, RgwClient
+from ..services.rgw_client import NoRgwDaemonsException, RgwClient, RgwMultisite
from ..tools import json_str_to_object, str_to_bool
from . import APIDoc, APIRouter, BaseController, CreatePermission, \
CRUDCollectionMethod, CRUDEndpoint, Endpoint, EndpointDoc, ReadPermission, \
- RESTController, UIRouter, allow_empty_body
+ RESTController, UIRouter, UpdatePermission, allow_empty_body
from ._crud import CRUDMeta, Form, FormField, FormTaskInfo, Icon, MethodType, \
TableAction, Validator, VerticalContainer
from ._version import APIVersion
@@ -82,49 +81,31 @@ class Rgw(BaseController):
@UIRouter('/rgw/multisite')
-class RgwStatus(BaseController):
+class RgwMultisiteStatus(RESTController):
@Endpoint()
@ReadPermission
# pylint: disable=R0801
def status(self):
status = {'available': True, 'message': None}
- try:
- instance = RgwClient.admin_instance()
- is_multisite_configured = instance.get_multisite_status()
- if not is_multisite_configured:
- status['available'] = False
- status['message'] = 'Multi-site provides disaster recovery and may also \
- serve as a foundation for content delivery networks' # type: ignore
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ multisite_instance = RgwMultisite()
+ is_multisite_configured = multisite_instance.get_multisite_status()
+ if not is_multisite_configured:
+ status['available'] = False
+ status['message'] = 'Multi-site provides disaster recovery and may also \
+ serve as a foundation for content delivery networks' # type: ignore
return status
- @Endpoint()
- @ReadPermission
- # pylint: disable=R0801
- def sync_status(self):
- try:
- instance = RgwClient.admin_instance()
- result = instance.get_multisite_sync_status()
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw') # noqa: E501 pylint: disable=line-too-long
- return result
-
- @Endpoint(method='PUT')
- # pylint: disable=W0102
- def migrate(self, realm_name=None, zonegroup_name=None, zone_name=None,
- zonegroup_endpoints: List[str] = [], zone_endpoints: List[str] = [],
- user=None, daemon_name: Optional[str] = None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.migrate_to_multisite(realm_name, zonegroup_name, zone_name,
- zonegroup_endpoints, zone_endpoints, user)
- orch = OrchClient.instance()
- daemons = orch.services.list_daemons(service_name='rgw')
- for daemon in daemons:
- orch.daemons.action(action='reload', daemon_name=daemon.daemon_id)
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ @RESTController.Collection(method='PUT', path='/migrate')
+ @allow_empty_body
+ # pylint: disable=W0102,W0613
+ def migrate(self, daemon_name=None, realm_name=None, zonegroup_name=None, zone_name=None,
+ zonegroup_endpoints=None, zone_endpoints=None, access_key=None,
+ secret_key=None):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.migrate_to_multisite(realm_name, zonegroup_name,
+ zone_name, zonegroup_endpoints,
+ zone_endpoints, access_key,
+ secret_key)
return result
@@ -187,6 +168,12 @@ class RgwDaemon(RESTController):
daemon['rgw_status'] = status
return daemon
+ @RESTController.Collection(method='PUT', path='/set_multisite_config')
+ @allow_empty_body
+ def set_multisite_config(self, realm_name=None, zonegroup_name=None,
+ zone_name=None, daemon_name=None):
+ CephService.set_multisite_config(realm_name, zonegroup_name, zone_name, daemon_name)
+
class RgwRESTController(RESTController):
def proxy(self, daemon_name, method, path, params=None, json_response=True):
@@ -738,132 +725,120 @@ class RgwUserRole(NamedTuple):
class RgwRealm(RESTController):
@allow_empty_body
# pylint: disable=W0613
- def create(self, realm_name, default, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.create_realm(realm_name, default)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def create(self, realm_name, default):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.create_realm(realm_name, default)
+ return result
@allow_empty_body
# pylint: disable=W0613
- def list(self, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.list_realms()
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def list(self):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.list_realms()
+ return result
@allow_empty_body
# pylint: disable=W0613
- def get(self, realm_name, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.get_realm(realm_name)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def get(self, realm_name):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_realm(realm_name)
+ return result
@Endpoint()
@ReadPermission
def get_all_realms_info(self):
- try:
- instance = RgwClient.admin_instance()
- result = instance.get_all_realms_info()
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_all_realms_info()
+ return result
@allow_empty_body
# pylint: disable=W0613
- def set(self, realm_name: str, new_realm_name: str, default: str = '', daemon_name=None):
+ def set(self, realm_name: str, new_realm_name: str, default: str = ''):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.edit_realm(realm_name, new_realm_name, default)
+ return result
+
+ @Endpoint()
+ @ReadPermission
+ def get_realm_tokens(self):
try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.edit_realm(realm_name, new_realm_name, default)
+ result = CephService.get_realm_tokens()
return result
except NoRgwDaemonsException as e:
raise DashboardException(e, http_status_code=404, component='rgw')
- def delete(self, realm_name, daemon_name=None):
+ @Endpoint(method='POST')
+ @UpdatePermission
+ @allow_empty_body
+ # pylint: disable=W0613
+ def import_realm_token(self, realm_token, zone_name, daemon_name=None):
try:
- instance = RgwClient.admin_instance(daemon_name)
- result = instance.delete_realm(realm_name)
+ multisite_instance = RgwMultisite()
+ result = CephService.import_realm_token(realm_token, zone_name)
+ multisite_instance.update_period()
return result
except NoRgwDaemonsException as e:
raise DashboardException(e, http_status_code=404, component='rgw')
+ def delete(self, realm_name):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.delete_realm(realm_name)
+ return result
+
@APIRouter('/rgw/zonegroup', Scope.RGW)
class RgwZonegroup(RESTController):
@allow_empty_body
# pylint: disable=W0613
def create(self, realm_name, zonegroup_name, default=None, master=None,
- zonegroup_endpoints=None, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.create_zonegroup(realm_name, zonegroup_name, default,
- master, zonegroup_endpoints)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ zonegroup_endpoints=None):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.create_zonegroup(realm_name, zonegroup_name, default,
+ master, zonegroup_endpoints)
+ return result
@allow_empty_body
# pylint: disable=W0613
- def list(self, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.list_zonegroups()
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def list(self):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.list_zonegroups()
+ return result
@allow_empty_body
# pylint: disable=W0613
- def get(self, zonegroup_name, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.get_zonegroup(zonegroup_name)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def get(self, zonegroup_name):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_zonegroup(zonegroup_name)
+ return result
@Endpoint()
@ReadPermission
def get_all_zonegroups_info(self):
- try:
- instance = RgwClient.admin_instance()
- result = instance.get_all_zonegroups_info()
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_all_zonegroups_info()
+ return result
- def delete(self, zonegroup_name, delete_pools, pools: Optional[List[str]] = None,
- daemon_name=None):
+ def delete(self, zonegroup_name, delete_pools, pools: Optional[List[str]] = None):
if pools is None:
pools = []
try:
- instance = RgwClient.admin_instance(daemon_name)
- result = instance.delete_zonegroup(zonegroup_name, delete_pools, pools)
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.delete_zonegroup(zonegroup_name, delete_pools, pools)
return result
except NoRgwDaemonsException as e:
raise DashboardException(e, http_status_code=404, component='rgw')
@allow_empty_body
# pylint: disable=W0613,W0102
- def set(self, zonegroup_name: str, realm_name: str, new_zonegroup_name: str, default: str = '',
- master: str = '', zonegroup_endpoints: List[str] = [], add_zones: List[str] = [],
- remove_zones: List[str] = [], placement_targets: List[Dict[str, str]] = [],
- daemon_name=None):
- try:
- instance = RgwClient.admin_instance()
- result = instance.edit_zonegroup(realm_name, zonegroup_name, new_zonegroup_name,
- default, master, zonegroup_endpoints, add_zones,
- remove_zones, placement_targets)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def set(self, zonegroup_name: str, realm_name: str, new_zonegroup_name: str,
+ default: str = '', master: str = '', zonegroup_endpoints: str = '',
+ add_zones: List[str] = [], remove_zones: List[str] = [],
+ placement_targets: List[Dict[str, str]] = []):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.edit_zonegroup(realm_name, zonegroup_name, new_zonegroup_name,
+ default, master, zonegroup_endpoints, add_zones,
+ remove_zones, placement_targets)
+ return result
@APIRouter('/rgw/zone', Scope.RGW)
@@ -871,56 +846,43 @@ class RgwZone(RESTController):
@allow_empty_body
# pylint: disable=W0613
def create(self, zone_name, zonegroup_name=None, default=False, master=False,
- zone_endpoints=None, user=None, createSystemUser=False, daemon_name=None,
- master_zone_of_master_zonegroup=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.create_zone(zone_name, zonegroup_name, default,
- master, zone_endpoints, user, createSystemUser,
- master_zone_of_master_zonegroup)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ zone_endpoints=None, access_key=None, secret_key=None):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.create_zone(zone_name, zonegroup_name, default,
+ master, zone_endpoints, access_key,
+ secret_key)
+ return result
@allow_empty_body
# pylint: disable=W0613
- def list(self, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.list_zones()
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def list(self):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.list_zones()
+ return result
@allow_empty_body
# pylint: disable=W0613
- def get(self, zone_name, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.get_zone(zone_name)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def get(self, zone_name):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_zone(zone_name)
+ return result
@Endpoint()
@ReadPermission
def get_all_zones_info(self):
- try:
- instance = RgwClient.admin_instance()
- result = instance.get_all_zones_info()
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_all_zones_info()
+ return result
def delete(self, zone_name, delete_pools, pools: Optional[List[str]] = None,
- zonegroup_name=None, daemon_name=None):
+ zonegroup_name=None):
if pools is None:
pools = []
if zonegroup_name is None:
zonegroup_name = ''
try:
- instance = RgwClient.admin_instance(daemon_name)
- result = instance.delete_zone(zone_name, delete_pools, pools, zonegroup_name)
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.delete_zone(zone_name, delete_pools, pools, zonegroup_name)
return result
except NoRgwDaemonsException as e:
raise DashboardException(e, http_status_code=404, component='rgw')
@@ -928,20 +890,17 @@ class RgwZone(RESTController):
@allow_empty_body
# pylint: disable=W0613,W0102
def set(self, zone_name: str, new_zone_name: str, zonegroup_name: str, default: str = '',
- master: str = '', zone_endpoints: List[str] = [], user: str = '',
+ 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 = '', daemon_name=None, master_zone_of_master_zonegroup=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.edit_zone(zone_name, new_zone_name, zonegroup_name, default,
- master, zone_endpoints, user, placement_target,
- data_pool, index_pool, data_extra_pool, storage_class,
- data_pool_class, compression,
- master_zone_of_master_zonegroup)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ compression: 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)
+ return result
@Endpoint()
@ReadPermission
@@ -957,20 +916,14 @@ class RgwZone(RESTController):
@Endpoint('PUT')
@CreatePermission
- def create_system_user(self, userName: str, zoneName: str, daemon_name=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.create_system_user(userName, zoneName)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def create_system_user(self, userName: str, zoneName: str):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.create_system_user(userName, zoneName)
+ return result
@Endpoint()
@ReadPermission
- def get_user_list(self, daemon_name=None, zoneName=None):
- try:
- instance = RgwClient.admin_instance(daemon_name=daemon_name)
- result = instance.get_user_list(zoneName)
- return result
- except NoRgwDaemonsException as e:
- raise DashboardException(e, http_status_code=404, component='rgw')
+ def get_user_list(self, zoneName=None):
+ multisite_instance = RgwMultisite()
+ result = multisite_instance.get_user_list(zoneName)
+ return result
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html
index 6c465d46998..0c2392fce41 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html
@@ -7,7 +7,14 @@
[formGroup]="serviceForm"
novalidate>
<div class="modal-body">
-
+ <cd-alert-panel *ngIf="serviceForm.controls.service_type.value === 'rgw' && showRealmCreationForm"
+ type="info"
+ spacingClass="mb-3"
+ i18n>
+ <a class="text-decoration-underline"
+ (click)="createMultisiteSetup()">
+ Click here</a> to create a new realm/zonegroup/zone
+ </cd-alert-panel>
<!-- Service type -->
<div class="form-group row">
<label class="cd-col-form-label required"
@@ -84,14 +91,71 @@
*ngIf="serviceForm.showError('service_id', frm, 'uniqueName')"
i18n>This service id is already in use.</span>
<span class="invalid-feedback"
- *ngIf="serviceForm.showError('service_id', frm, 'rgwPattern')"
- i18n>The value does not match the pattern <strong>&lt;service_id&gt;[.&lt;realm_name&gt;.&lt;zone_name&gt;]</strong>.</span>
- <span class="invalid-feedback"
*ngIf="serviceForm.showError('service_id', frm, 'mdsPattern')"
i18n>MDS service id must start with a letter and contain alphanumeric characters or '.', '-', and '_'</span>
</div>
</div>
+ <div class="form-group row"
+ *ngIf="serviceForm.controls.service_type.value === 'rgw'">
+ <label class="cd-col-form-label"
+ for="realm_name"
+ i18n>Realm</label>
+ <div class="cd-col-form-input">
+ <select class="form-select"
+ id="realm_name"
+ formControlName="realm_name"
+ name="realm_name"
+ [attr.disabled]="realmList.length === 0 || editing ? true : null">
+ <option *ngIf="realmList.length === 0"
+ i18n
+ selected>-- No realm available --</option>
+ <option *ngFor="let realm of realmList"
+ [value]="realm.name">
+ {{ realm.name }}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="form-group row"
+ *ngIf="serviceForm.controls.service_type.value === 'rgw'">
+ <label class="cd-col-form-label"
+ for="zonegroup_name"
+ i18n>Zonegroup</label>
+ <div class="cd-col-form-input">
+ <select class="form-select"
+ id="zonegroup_name"
+ formControlName="zonegroup_name"
+ name="zonegroup_name"
+ [attr.disabled]="zonegroupList.length === 0 || editing ? true : null">
+ <option *ngFor="let zonegroup of zonegroupList"
+ [value]="zonegroup.name">
+ {{ zonegroup.name }}
+ </option>
+ </select>
+ </div>
+ </div>
+
+ <div class="form-group row"
+ *ngIf="serviceForm.controls.service_type.value === 'rgw'">
+ <label class="cd-col-form-label"
+ for="zone_name"
+ i18n>Zone</label>
+ <div class="cd-col-form-input">
+ <select class="form-select"
+ id="zone_name"
+ formControlName="zone_name"
+ name="zone_name"
+ [attr.disabled]="zoneList.length === 0 || editing ? true : null">
+ <option *ngFor="let zone of zoneList"
+ [value]="zone.name">
+ {{ zone.name }}
+ </option>
+ </select>
+ </div>
+ </div>
+
<!-- unmanaged -->
<div class="form-group row">
<div class="cd-col-form-offset">
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts
index b2c965ee71b..ebecec5cc38 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts
@@ -191,29 +191,18 @@ describe('ServiceFormComponent', () => {
formHelper.expectValid('service_id');
});
- it('should test rgw invalid service id', () => {
- formHelper.setValue('service_id', '.');
- formHelper.expectError('service_id', 'rgwPattern');
- formHelper.setValue('service_id', 'svc.');
- formHelper.expectError('service_id', 'rgwPattern');
- formHelper.setValue('service_id', 'svc.realm');
- formHelper.expectError('service_id', 'rgwPattern');
- formHelper.setValue('service_id', 'svc.realm.');
- formHelper.expectError('service_id', 'rgwPattern');
- formHelper.setValue('service_id', '.svc.realm');
- formHelper.expectError('service_id', 'rgwPattern');
- formHelper.setValue('service_id', 'svc.realm.zone.');
- formHelper.expectError('service_id', 'rgwPattern');
- });
-
- it('should submit rgw with realm and zone', () => {
- formHelper.setValue('service_id', 'svc.my-realm.my-zone');
+ it('should submit rgw with realm, zonegroup and zone', () => {
+ formHelper.setValue('service_id', 'svc');
+ formHelper.setValue('realm_name', 'my-realm');
+ formHelper.setValue('zone_name', 'my-zone');
+ formHelper.setValue('zonegroup_name', 'my-zonegroup');
component.onSubmit();
expect(cephServiceService.create).toHaveBeenCalledWith({
service_type: 'rgw',
service_id: 'svc',
rgw_realm: 'my-realm',
rgw_zone: 'my-zone',
+ rgw_zonegroup: 'my-zonegroup',
placement: {},
unmanaged: false,
ssl: false
@@ -227,6 +216,9 @@ describe('ServiceFormComponent', () => {
expect(cephServiceService.create).toHaveBeenCalledWith({
service_type: 'rgw',
service_id: 'svc',
+ rgw_realm: null,
+ rgw_zone: null,
+ rgw_zonegroup: null,
placement: {},
unmanaged: false,
rgw_frontend_port: 1234,
@@ -271,6 +263,9 @@ describe('ServiceFormComponent', () => {
expect(cephServiceService.create).toHaveBeenCalledWith({
service_type: 'rgw',
service_id: 'svc',
+ rgw_realm: null,
+ rgw_zone: null,
+ rgw_zonegroup: null,
placement: {},
unmanaged: false,
ssl: false
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
index 5ae2dfa50b4..00276d771bb 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
@@ -3,24 +3,36 @@ import { Component, Input, OnInit, ViewChild } from '@angular/core';
import { AbstractControl, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
-import { NgbActiveModal, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
+import { NgbActiveModal, NgbModalRef, NgbTypeahead } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';
-import { merge, Observable, Subject } from 'rxjs';
+import { forkJoin, merge, Observable, Subject, Subscription } from 'rxjs';
import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators';
+import { CreateRgwServiceEntitiesComponent } from '~/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component';
+import { RgwRealm, RgwZonegroup, RgwZone } from '~/app/ceph/rgw/models/rgw-multisite';
import { CephServiceService } from '~/app/shared/api/ceph-service.service';
import { HostService } from '~/app/shared/api/host.service';
import { PoolService } from '~/app/shared/api/pool.service';
+import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
+import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
+import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
+import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
import { SelectMessages } from '~/app/shared/components/select/select-messages.model';
import { SelectOption } from '~/app/shared/components/select/select-option.model';
-import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants';
+import {
+ ActionLabelsI18n,
+ TimerServiceInterval,
+ URLVerbs
+} from '~/app/shared/constants/app.constants';
import { CdForm } from '~/app/shared/forms/cd-form';
import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { CdValidators } from '~/app/shared/forms/cd-validators';
import { FinishedTask } from '~/app/shared/models/finished-task';
import { CephServiceSpec } from '~/app/shared/models/service.interface';
+import { ModalService } from '~/app/shared/services/modal.service';
import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
+import { TimerService } from '~/app/shared/services/timer.service';
@Component({
selector: 'cd-service-form',
@@ -28,7 +40,8 @@ import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service';
styleUrls: ['./service-form.component.scss']
})
export class ServiceFormComponent extends CdForm implements OnInit {
- readonly RGW_SVC_ID_PATTERN = /^([^.]+)(\.([^.]+)\.([^.]+))?$/;
+ public sub = new Subscription();
+
readonly MDS_SVC_ID_PATTERN = /^[a-zA-Z_.-][a-zA-Z0-9_.-]*$/;
readonly SNMP_DESTINATION_PATTERN = /^[^\:]+:[0-9]/;
readonly SNMP_ENGINE_ID_PATTERN = /^[0-9A-Fa-f]{10,64}/g;
@@ -57,6 +70,20 @@ export class ServiceFormComponent extends CdForm implements OnInit {
services: Array<CephServiceSpec> = [];
pageURL: string;
serviceList: CephServiceSpec[];
+ multisiteInfo: object[] = [];
+ defaultRealmId = '';
+ defaultZonegroupId = '';
+ defaultZoneId = '';
+ realmList: RgwRealm[] = [];
+ zonegroupList: RgwZonegroup[] = [];
+ zoneList: RgwZone[] = [];
+ bsModalRef: NgbModalRef;
+ defaultZonegroup: RgwZonegroup;
+ showRealmCreationForm = false;
+ defaultsInfo: { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string };
+ realmNames: string[];
+ zonegroupNames: string[];
+ zoneNames: string[];
constructor(
public actionLabels: ActionLabelsI18n,
@@ -66,8 +93,15 @@ export class ServiceFormComponent extends CdForm implements OnInit {
private poolService: PoolService,
private router: Router,
private taskWrapperService: TaskWrapperService,
+ public timerService: TimerService,
+ public timerServiceVariable: TimerServiceInterval,
+ public rgwRealmService: RgwRealmService,
+ public rgwZonegroupService: RgwZonegroupService,
+ public rgwZoneService: RgwZoneService,
+ public rgwMultisiteService: RgwMultisiteService,
private route: ActivatedRoute,
- public activeModal: NgbActiveModal
+ public activeModal: NgbActiveModal,
+ public modalService: ModalService
) {
super();
this.resource = $localize`service`;
@@ -115,15 +149,7 @@ export class ServiceFormComponent extends CdForm implements OnInit {
{
service_type: 'rgw'
},
- [
- Validators.required,
- CdValidators.custom('rgwPattern', (value: string) => {
- if (_.isEmpty(value)) {
- return false;
- }
- return !this.RGW_SVC_ID_PATTERN.test(value);
- })
- ]
+ [Validators.required]
),
CdValidators.custom('uniqueName', (service_id: string) => {
return this.serviceIds && this.serviceIds.includes(service_id);
@@ -154,6 +180,9 @@ export class ServiceFormComponent extends CdForm implements OnInit {
],
// RGW
rgw_frontend_port: [null, [CdValidators.number(false)]],
+ realm_name: [null],
+ zonegroup_name: [null],
+ zone_name: [null],
// iSCSI
trusted_ip_list: [null],
api_port: [null, [CdValidators.number(false)]],
@@ -425,6 +454,12 @@ export class ServiceFormComponent extends CdForm implements OnInit {
this.serviceForm
.get('rgw_frontend_port')
.setValue(response[0].spec?.rgw_frontend_port);
+ this.getServiceIds(
+ 'rgw',
+ response[0].spec?.rgw_realm,
+ response[0].spec?.rgw_zonegroup,
+ response[0].spec?.rgw_zone
+ );
this.serviceForm.get('ssl').setValue(response[0].spec?.ssl);
if (response[0].spec?.ssl) {
this.serviceForm
@@ -493,10 +528,131 @@ export class ServiceFormComponent extends CdForm implements OnInit {
}
}
- getServiceIds(selectedServiceType: string) {
+ getDefaultsEntities(
+ defaultRealmId: string,
+ defaultZonegroupId: string,
+ defaultZoneId: string
+ ): { defaultRealmName: string; defaultZonegroupName: string; defaultZoneName: string } {
+ const defaultRealm = this.realmList.find((x: { id: string }) => x.id === defaultRealmId);
+ const defaultZonegroup = this.zonegroupList.find(
+ (x: { id: string }) => x.id === defaultZonegroupId
+ );
+ const defaultZone = this.zoneList.find((x: { id: string }) => x.id === defaultZoneId);
+ const defaultRealmName = defaultRealm !== undefined ? defaultRealm.name : null;
+ const defaultZonegroupName = defaultZonegroup !== undefined ? defaultZonegroup.name : 'default';
+ const defaultZoneName = defaultZone !== undefined ? defaultZone.name : 'default';
+ if (defaultZonegroupName === 'default' && !this.zonegroupNames.includes(defaultZonegroupName)) {
+ const defaultZonegroup = new RgwZonegroup();
+ defaultZonegroup.name = 'default';
+ this.zonegroupList.push(defaultZonegroup);
+ }
+ if (defaultZoneName === 'default' && !this.zoneNames.includes(defaultZoneName)) {
+ const defaultZone = new RgwZone();
+ defaultZone.name = 'default';
+ this.zoneList.push(defaultZone);
+ }
+ return {
+ defaultRealmName: defaultRealmName,
+ defaultZonegroupName: defaultZonegroupName,
+ defaultZoneName: defaultZoneName
+ };
+ }
+
+ getServiceIds(
+ selectedServiceType: string,
+ realm_name?: string,
+ zonegroup_name?: string,
+ zone_name?: string
+ ) {
this.serviceIds = this.serviceList
?.filter((service) => service['service_type'] === selectedServiceType)
.map((service) => service['service_id']);
+
+ if (selectedServiceType === 'rgw') {
+ const observables = [
+ this.rgwRealmService.getAllRealmsInfo(),
+ this.rgwZonegroupService.getAllZonegroupsInfo(),
+ this.rgwZoneService.getAllZonesInfo()
+ ];
+ this.sub = forkJoin(observables).subscribe(
+ (multisiteInfo: [object, object, object]) => {
+ this.multisiteInfo = multisiteInfo;
+ this.realmList =
+ this.multisiteInfo[0] !== undefined && this.multisiteInfo[0].hasOwnProperty('realms')
+ ? this.multisiteInfo[0]['realms']
+ : [];
+ this.zonegroupList =
+ this.multisiteInfo[1] !== undefined &&
+ this.multisiteInfo[1].hasOwnProperty('zonegroups')
+ ? this.multisiteInfo[1]['zonegroups']
+ : [];
+ this.zoneList =
+ this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
+ ? this.multisiteInfo[2]['zones']
+ : [];
+ this.realmNames = this.realmList.map((realm) => {
+ return realm['name'];
+ });
+ this.zonegroupNames = this.zonegroupList.map((zonegroup) => {
+ return zonegroup['name'];
+ });
+ this.zoneNames = this.zoneList.map((zone) => {
+ return zone['name'];
+ });
+ this.defaultRealmId = multisiteInfo[0]['default_realm'];
+ this.defaultZonegroupId = multisiteInfo[1]['default_zonegroup'];
+ this.defaultZoneId = multisiteInfo[2]['default_zone'];
+ this.defaultsInfo = this.getDefaultsEntities(
+ this.defaultRealmId,
+ this.defaultZonegroupId,
+ this.defaultZoneId
+ );
+ if (!this.editing) {
+ this.serviceForm.get('realm_name').setValue(this.defaultsInfo['defaultRealmName']);
+ this.serviceForm
+ .get('zonegroup_name')
+ .setValue(this.defaultsInfo['defaultZonegroupName']);
+ this.serviceForm.get('zone_name').setValue(this.defaultsInfo['defaultZoneName']);
+ } else {
+ if (realm_name && !this.realmNames.includes(realm_name)) {
+ const realm = new RgwRealm();
+ realm.name = realm_name;
+ this.realmList.push(realm);
+ }
+ if (zonegroup_name && !this.zonegroupNames.includes(zonegroup_name)) {
+ const zonegroup = new RgwZonegroup();
+ zonegroup.name = zonegroup_name;
+ this.zonegroupList.push(zonegroup);
+ }
+ if (zone_name && !this.zoneNames.includes(zone_name)) {
+ const zone = new RgwZone();
+ zone.name = zone_name;
+ this.zoneList.push(zone);
+ }
+ if (zonegroup_name === undefined && zone_name === undefined) {
+ zonegroup_name = 'default';
+ zone_name = 'default';
+ }
+ this.serviceForm.get('realm_name').setValue(realm_name);
+ this.serviceForm.get('zonegroup_name').setValue(zonegroup_name);
+ this.serviceForm.get('zone_name').setValue(zone_name);
+ }
+ if (this.realmList.length === 0) {
+ this.showRealmCreationForm = true;
+ } else {
+ this.showRealmCreationForm = false;
+ }
+ },
+ (_error) => {
+ const defaultZone = new RgwZone();
+ defaultZone.name = 'default';
+ const defaultZonegroup = new RgwZonegroup();
+ defaultZonegroup.name = 'default';
+ this.zoneList.push(defaultZone);
+ this.zonegroupList.push(defaultZonegroup);
+ }
+ );
+ }
}
disableForEditing(serviceType: string) {
@@ -559,12 +715,11 @@ export class ServiceFormComponent extends CdForm implements OnInit {
};
let svcId: string;
if (serviceType === 'rgw') {
- const svcIdMatch = values['service_id'].match(this.RGW_SVC_ID_PATTERN);
- svcId = svcIdMatch[1];
- if (svcIdMatch[3]) {
- serviceSpec['rgw_realm'] = svcIdMatch[3];
- serviceSpec['rgw_zone'] = svcIdMatch[4];
- }
+ serviceSpec['rgw_realm'] = values['realm_name'] ? values['realm_name'] : null;
+ serviceSpec['rgw_zonegroup'] =
+ values['zonegroup_name'] !== 'default' ? values['zonegroup_name'] : null;
+ serviceSpec['rgw_zone'] = values['zone_name'] !== 'default' ? values['zone_name'] : null;
+ svcId = values['service_id'];
} else {
svcId = values['service_id'];
}
@@ -705,4 +860,13 @@ export class ServiceFormComponent extends CdForm implements OnInit {
this.serviceForm.get('snmp_v3_priv_password').clearValidators();
}
}
+
+ createMultisiteSetup() {
+ this.bsModalRef = this.modalService.show(CreateRgwServiceEntitiesComponent, {
+ size: 'lg'
+ });
+ this.bsModalRef.componentInstance.submitAction.subscribe(() => {
+ this.getServiceIds('rgw');
+ });
+ }
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
index 1234a684e6c..82a975c9df4 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
@@ -90,8 +90,8 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI
icon: Icons.add,
click: () => this.openModal(),
name: this.actionLabels.CREATE,
- canBePrimary: (selection: CdTableSelection) => !selection.hasSelection,
- disable: (selection: CdTableSelection) => this.getDisable('create', selection)
+ canBePrimary: (selection: CdTableSelection) => !selection.hasSelection
+ // disable: (selection: CdTableSelection) => this.getDisable('create', selection)
},
{
permission: 'update',
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.html
new file mode 100644
index 00000000000..b6e4f805d0c
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.html
@@ -0,0 +1,70 @@
+<cd-modal [modalRef]="activeModal">
+ <ng-container i18n="form title"
+ class="modal-title">Create Realm/Zonegroup/Zone
+ </ng-container>
+
+ <ng-container class="modal-content">
+ <form name="createMultisiteEntitiesForm"
+ #formDir="ngForm"
+ [formGroup]="createMultisiteEntitiesForm"
+ novalidate>
+ <div class="modal-body">
+ <cd-alert-panel type="info"
+ spacingClass="mb-3">The realm/zonegroup/zone created will be set as default and master.
+ </cd-alert-panel>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="realmName"
+ i18n>Realm Name</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="Realm name..."
+ id="realmName"
+ name="realmName"
+ formControlName="realmName">
+ <span class="invalid-feedback"
+ *ngIf="createMultisiteEntitiesForm.showError('realmName', formDir, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="zonegroupName"
+ i18n>Zonegroup Name</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="Zonegroup name..."
+ id="zonegroupName"
+ name="zonegroupName"
+ formControlName="zonegroupName">
+ <span class="invalid-feedback"
+ *ngIf="createMultisiteEntitiesForm.showError('zonegroupName', formDir, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="zoneName"
+ i18n>Zone Name</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="Zone name..."
+ id="zoneName"
+ name="zoneName"
+ formControlName="zoneName">
+ <span class="invalid-feedback"
+ *ngIf="createMultisiteEntitiesForm.showError('zoneName', formDir, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <cd-form-button-panel (submitActionEvent)="submit()"
+ [form]="createMultisiteEntitiesForm"></cd-form-button-panel>
+ </div>
+ </form>
+ </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.scss
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.scss
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.spec.ts
new file mode 100644
index 00000000000..5e6621b3b83
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.spec.ts
@@ -0,0 +1,38 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { ToastrModule } from 'ngx-toastr';
+import { SharedModule } from '~/app/shared/shared.module';
+
+import { CreateRgwServiceEntitiesComponent } from './create-rgw-service-entities.component';
+
+describe('CreateRgwServiceEntitiesComponent', () => {
+ let component: CreateRgwServiceEntitiesComponent;
+ let fixture: ComponentFixture<CreateRgwServiceEntitiesComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ SharedModule,
+ ReactiveFormsModule,
+ RouterTestingModule,
+ HttpClientTestingModule,
+ ToastrModule.forRoot()
+ ],
+ providers: [NgbActiveModal],
+ declarations: [CreateRgwServiceEntitiesComponent]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(CreateRgwServiceEntitiesComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.ts
new file mode 100644
index 00000000000..041915186c0
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.ts
@@ -0,0 +1,99 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { RgwMultisiteService } from '~/app/shared/api/rgw-multisite.service';
+import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
+import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
+import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { ModalService } from '~/app/shared/services/modal.service';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { RgwRealm, RgwZonegroup, RgwZone, SystemKey } from '../models/rgw-multisite';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { Subscription } from 'rxjs';
+
+@Component({
+ selector: 'cd-create-rgw-service-entities',
+ templateUrl: './create-rgw-service-entities.component.html',
+ styleUrls: ['./create-rgw-service-entities.component.scss']
+})
+export class CreateRgwServiceEntitiesComponent {
+ public sub = new Subscription();
+ createMultisiteEntitiesForm: CdFormGroup;
+ realm: RgwRealm;
+ zonegroup: RgwZonegroup;
+ zone: RgwZone;
+
+ @Output()
+ submitAction = new EventEmitter();
+
+ constructor(
+ public activeModal: NgbActiveModal,
+ public actionLabels: ActionLabelsI18n,
+ public rgwMultisiteService: RgwMultisiteService,
+ public rgwZoneService: RgwZoneService,
+ public notificationService: NotificationService,
+ public rgwZonegroupService: RgwZonegroupService,
+ public rgwRealmService: RgwRealmService,
+ public modalService: ModalService
+ ) {
+ this.createForm();
+ }
+
+ createForm() {
+ this.createMultisiteEntitiesForm = new CdFormGroup({
+ realmName: new FormControl(null, {
+ validators: [Validators.required]
+ }),
+ zonegroupName: new FormControl(null, {
+ validators: [Validators.required]
+ }),
+ zoneName: new FormControl(null, {
+ validators: [Validators.required]
+ })
+ });
+ }
+
+ submit() {
+ const values = this.createMultisiteEntitiesForm.value;
+ this.realm = new RgwRealm();
+ this.realm.name = values['realmName'];
+ this.zonegroup = new RgwZonegroup();
+ this.zonegroup.name = values['zonegroupName'];
+ this.zonegroup.endpoints = '';
+ this.zone = new RgwZone();
+ this.zone.name = values['zoneName'];
+ this.zone.endpoints = '';
+ this.zone.system_key = new SystemKey();
+ this.zone.system_key.access_key = '';
+ this.zone.system_key.secret_key = '';
+ this.rgwRealmService
+ .create(this.realm, true)
+ .toPromise()
+ .then(() => {
+ this.rgwZonegroupService
+ .create(this.realm, this.zonegroup, true, true)
+ .toPromise()
+ .then(() => {
+ this.rgwZoneService
+ .create(this.zone, this.zonegroup, true, true, this.zone.endpoints)
+ .toPromise()
+ .then(() => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Realm/Zonegroup/Zone created successfully`
+ );
+ this.submitAction.emit();
+ this.activeModal.close();
+ })
+ .catch(() => {
+ this.notificationService.show(
+ NotificationType.error,
+ $localize`Realm/Zonegroup/Zone creation failed`
+ );
+ });
+ });
+ });
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component.spec.ts
index 947ef3cbfbc..8cdd79e6549 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component.spec.ts
@@ -5,6 +5,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
+import { RgwZone } from '../rgw-multisite';
import { RgwMultisiteZoneDeletionFormComponent } from './rgw-multisite-zone-deletion-form.component';
@@ -21,6 +22,7 @@ describe('RgwMultisiteZoneDeletionFormComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(RgwMultisiteZoneDeletionFormComponent);
component = fixture.componentInstance;
+ component.zone = new RgwZone();
fixture.detectChanges();
});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component.spec.ts
index 3ee39f2e9fb..2c4059f251a 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component.spec.ts
@@ -5,6 +5,7 @@ import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '~/app/shared/shared.module';
import { configureTestBed } from '~/testing/unit-test-helper';
+import { RgwZonegroup } from '../rgw-multisite';
import { RgwMultisiteZonegroupDeletionFormComponent } from './rgw-multisite-zonegroup-deletion-form.component';
@@ -21,6 +22,7 @@ describe('RgwMultisiteZonegroupDeletionFormComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(RgwMultisiteZonegroupDeletionFormComponent);
component = fixture.componentInstance;
+ component.zonegroup = new RgwZonegroup();
fixture.detectChanges();
});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts
index fb0ce154900..1729f6418b2 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts
@@ -10,7 +10,7 @@ export class RgwZonegroup {
name: string;
api_name: string;
is_master: boolean;
- endpoints: string[];
+ endpoints: string;
hostnames: string[];
hostnames_s3website: string[];
master_zone: string;
@@ -39,9 +39,14 @@ export class RgwZone {
user_swift_pool: string;
user_uid_pool: string;
otp_pool: string;
- system_key: object;
+ system_key: SystemKey;
placement_pools: any[];
realm_id: string;
notif_pool: string;
- endpoints: string[];
+ endpoints: string;
+}
+
+export class SystemKey {
+ access_key: string;
+ secret_key: string;
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
index 757d5f78ae3..331ce22e9a2 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
@@ -1,6 +1,14 @@
<div class="row">
<div class="col-sm-12 col-lg-12">
<div>
+ <cd-alert-panel *ngIf="!rgwModuleStatus"
+ type="info"
+ spacingClass="mb-3"
+ i18n>You need to enable the rgw module to access the import/export feature.
+ <a class="text-decoration-underline"
+ (click)="enableRgwModule()">
+ Enable RGW Module</a>
+ </cd-alert-panel>
<cd-table-actions class="btn-group mb-4 me-2"
[permission]="permission"
[selection]="selection"
@@ -14,6 +22,18 @@
[tableActions]="migrateTableAction">
</cd-table-actions>
</span>
+ <cd-table-actions class="btn-group mb-4 me-2"
+ [permission]="permission"
+ [btnColor]="'light'"
+ [selection]="selection"
+ [tableActions]="importAction">
+ </cd-table-actions>
+ <cd-table-actions class="btn-group mb-4 me-2"
+ [permission]="permission"
+ [btnColor]="'light'"
+ [selection]="selection"
+ [tableActions]="exportAction">
+ </cd-table-actions>
</div>
<div class="card">
<div class="card-header"
@@ -31,6 +51,12 @@
let-node>
<span *ngIf="node.data.name"
class="me-3">
+ <span *ngIf="(node.data.show_warning)">
+ <i class="text-danger"
+ i18n-title
+ [title]="node.data.warning_message"
+ [ngClass]="icons.danger"></i>
+ </span>
<i [ngClass]="node.data.icon"></i>
{{ node.data.name }}
</span>
@@ -42,6 +68,10 @@
*ngIf="node.data.is_master">
master
</span>
+ <span class="badge badge-warning me-2"
+ *ngIf="node.data.secondary_zone">
+ secondary-zone
+ </span>
<div class="btn-group align-inline-btns"
*ngIf="node.isFocused"
role="group">
@@ -50,7 +80,7 @@
<button type="button"
class="btn btn-light dropdown-toggle-split ms-1"
(click)="openModal(node, true)"
- [disabled]="getDisable()">
+ [disabled]="getDisable() || node.data.secondary_zone">
<i [ngClass]="[icons.edit]"></i>
</button>
</div>
@@ -58,7 +88,7 @@
i18n-title>
<button type="button"
class="btn btn-light ms-1"
- [disabled]="isDeleteDisabled(node)"
+ [disabled]="isDeleteDisabled(node) || node.data.secondary_zone"
(click)="delete(node)">
<i [ngClass]="[icons.destroy]"></i>
</button>
@@ -71,9 +101,7 @@
*ngIf="metadata">
<legend>{{ metadataTitle }}</legend>
<cd-table-key-value cdTableDetail
- [data]="metadata"
- [renderObjects]="true"
- [customCss]="customCss">
+ [data]="metadata">
</cd-table-key-value>
</div>
</div>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss
index 537b53a519c..4eefc8892b8 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss
@@ -11,3 +11,9 @@
.btn:disabled {
pointer-events: none;
}
+
+cd-table-key-value {
+ ::ng-deep .table-scroller {
+ overflow: unset;
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts
index 7f1c0b19769..a2fd6c6f331 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts
@@ -6,6 +6,7 @@ import { ToastrModule } from 'ngx-toastr';
import { SharedModule } from '~/app/shared/shared.module';
import { RgwMultisiteDetailsComponent } from './rgw-multisite-details.component';
+import { RouterTestingModule } from '@angular/router/testing';
describe('RgwMultisiteDetailsComponent', () => {
let component: RgwMultisiteDetailsComponent;
@@ -15,7 +16,13 @@ describe('RgwMultisiteDetailsComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [RgwMultisiteDetailsComponent],
- imports: [HttpClientTestingModule, TreeModule, SharedModule, ToastrModule.forRoot()]
+ imports: [
+ HttpClientTestingModule,
+ TreeModule,
+ SharedModule,
+ ToastrModule.forRoot(),
+ RouterTestingModule
+ ]
}).compileComponents();
});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
index 29f9b9d9e9c..5a667cbe41f 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
@@ -8,7 +8,8 @@ import {
} from '@circlon/angular-tree-component';
import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import _ from 'lodash';
-import { forkJoin, Subscription } from 'rxjs';
+
+import { forkJoin, Subscription, timer as observableTimer } from 'rxjs';
import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
import { RgwZoneService } from '~/app/shared/api/rgw-zone.service';
import { RgwZonegroupService } from '~/app/shared/api/rgw-zonegroup.service';
@@ -27,9 +28,15 @@ import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
import { RgwMultisiteMigrateComponent } from '../rgw-multisite-migrate/rgw-multisite-migrate.component';
import { RgwMultisiteZoneDeletionFormComponent } from '../models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component';
import { RgwMultisiteZonegroupDeletionFormComponent } from '../models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component';
+import { RgwMultisiteExportComponent } from '../rgw-multisite-export/rgw-multisite-export.component';
+import { RgwMultisiteImportComponent } from '../rgw-multisite-import/rgw-multisite-import.component';
import { RgwMultisiteRealmFormComponent } from '../rgw-multisite-realm-form/rgw-multisite-realm-form.component';
import { RgwMultisiteZoneFormComponent } from '../rgw-multisite-zone-form/rgw-multisite-zone-form.component';
import { RgwMultisiteZonegroupFormComponent } from '../rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component';
+import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
+import { MgrModuleService } from '~/app/shared/api/mgr-module.service';
+import { BlockUI, NgBlockUI } from 'ng-block-ui';
+import { Router } from '@angular/router';
@Component({
selector: 'cd-rgw-multisite-details',
@@ -43,15 +50,21 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
messages = {
noDefaultRealm: $localize`Please create a default realm first to enable this feature`,
- noMasterZone: $localize`Please create a master zone for each zonegroup to enable this feature`,
- disableMigrate: $localize`Deployment is already migrated to multi-site system.`
+ noMasterZone: $localize`Please create a master zone for each zonegroups to enable this feature`,
+ noRealmExists: $localize`No realm exists`,
+ disableExport: $localize`Please create master zonegroup and master zone for each of the realms`
};
+ @BlockUI()
+ blockUI: NgBlockUI;
+
icons = Icons;
permission: Permission;
selection = new CdTableSelection();
createTableActions: CdTableAction[];
migrateTableAction: CdTableAction[];
+ importAction: CdTableAction[];
+ exportAction: CdTableAction[];
loadingIndicator = true;
nodes: object[] = [];
treeOptions: ITreeOptions = {
@@ -82,6 +95,9 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
showMigrateAction: boolean = false;
editTitle: string = 'Edit';
deleteTitle: string = 'Delete';
+ disableExport = true;
+ rgwModuleStatus: boolean;
+ rgwModuleData: string | any[] = [];
constructor(
private modalService: ModalService,
@@ -89,39 +105,15 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
private authStorageService: AuthStorageService,
public actionLabels: ActionLabelsI18n,
public timerServiceVariable: TimerServiceInterval,
+ public router: Router,
public rgwRealmService: RgwRealmService,
public rgwZonegroupService: RgwZonegroupService,
public rgwZoneService: RgwZoneService,
+ public rgwDaemonService: RgwDaemonService,
+ public mgrModuleService: MgrModuleService,
private notificationService: NotificationService
) {
this.permission = this.authStorageService.getPermissions().rgw;
- const createRealmAction: CdTableAction = {
- permission: 'create',
- icon: Icons.add,
- name: this.actionLabels.CREATE + ' Realm',
- click: () => this.openModal('realm')
- };
- const createZonegroupAction: CdTableAction = {
- permission: 'create',
- icon: Icons.add,
- name: this.actionLabels.CREATE + ' Zonegroup',
- click: () => this.openModal('zonegroup'),
- disable: () => this.getDisable()
- };
- const createZoneAction: CdTableAction = {
- permission: 'create',
- icon: Icons.add,
- name: this.actionLabels.CREATE + ' Zone',
- click: () => this.openModal('zone')
- };
- const migrateMultsiteAction: CdTableAction = {
- permission: 'read',
- icon: Icons.exchange,
- name: this.actionLabels.MIGRATE,
- click: () => this.openMigrateModal()
- };
- this.createTableActions = [createRealmAction, createZonegroupAction, createZoneAction];
- this.migrateTableAction = [migrateMultsiteAction];
}
openModal(entity: any, edit = false) {
@@ -158,7 +150,100 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
});
}
+ openImportModal() {
+ const initialState = {
+ multisiteInfo: this.multisiteInfo
+ };
+ this.bsModalRef = this.modalService.show(RgwMultisiteImportComponent, initialState, {
+ size: 'lg'
+ });
+ }
+
+ openExportModal() {
+ const initialState = {
+ defaultsInfo: this.defaultsInfo,
+ multisiteInfo: this.multisiteInfo
+ };
+ this.bsModalRef = this.modalService.show(RgwMultisiteExportComponent, initialState, {
+ size: 'lg'
+ });
+ }
+
+ getDisableExport() {
+ this.realms.forEach((realm: any) => {
+ this.zonegroups.forEach((zonegroup) => {
+ if (realm.id === zonegroup.realm_id) {
+ if (zonegroup.is_master && zonegroup.master_zone !== '') {
+ this.disableExport = false;
+ }
+ }
+ });
+ });
+ if (!this.rgwModuleStatus) {
+ return true;
+ }
+ if (this.realms.length < 1) {
+ return this.messages.noRealmExists;
+ } else if (this.disableExport) {
+ return this.messages.disableExport;
+ } else {
+ return false;
+ }
+ }
+
+ getDisableImport() {
+ if (!this.rgwModuleStatus) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
ngOnInit() {
+ const createRealmAction: CdTableAction = {
+ permission: 'create',
+ icon: Icons.add,
+ name: this.actionLabels.CREATE + ' Realm',
+ click: () => this.openModal('realm')
+ };
+ const createZonegroupAction: CdTableAction = {
+ permission: 'create',
+ icon: Icons.add,
+ name: this.actionLabels.CREATE + ' Zonegroup',
+ click: () => this.openModal('zonegroup'),
+ disable: () => this.getDisable()
+ };
+ const createZoneAction: CdTableAction = {
+ permission: 'create',
+ icon: Icons.add,
+ name: this.actionLabels.CREATE + ' Zone',
+ click: () => this.openModal('zone')
+ };
+ const migrateMultsiteAction: CdTableAction = {
+ permission: 'read',
+ icon: Icons.exchange,
+ name: this.actionLabels.MIGRATE,
+ click: () => this.openMigrateModal()
+ };
+ const importMultsiteAction: CdTableAction = {
+ permission: 'read',
+ icon: Icons.download,
+ name: this.actionLabels.IMPORT,
+ click: () => this.openImportModal(),
+ disable: () => this.getDisableImport()
+ };
+ const exportMultsiteAction: CdTableAction = {
+ permission: 'read',
+ icon: Icons.upload,
+ name: this.actionLabels.EXPORT,
+ click: () => this.openExportModal(),
+ disable: () => this.getDisableExport()
+ };
+ this.createTableActions = [createRealmAction, createZonegroupAction, createZoneAction];
+ this.migrateTableAction = [migrateMultsiteAction];
+ this.importAction = [importMultsiteAction];
+ this.exportAction = [exportMultsiteAction];
+
const observables = [
this.rgwRealmService.getAllRealmsInfo(),
this.rgwZonegroupService.getAllZonegroupsInfo(),
@@ -174,8 +259,24 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
},
(_error) => {}
);
+ this.mgrModuleService.list().subscribe((moduleData: any) => {
+ this.rgwModuleData = moduleData.filter((module: object) => module['name'] === 'rgw');
+ if (this.rgwModuleData.length > 0) {
+ this.rgwModuleStatus = this.rgwModuleData[0].enabled;
+ }
+ });
}
+ /* setConfigValues() {
+ this.rgwDaemonService
+ .setMultisiteConfig(
+ this.defaultsInfo['defaultRealmName'],
+ this.defaultsInfo['defaultZonegroupName'],
+ this.defaultsInfo['defaultZoneName']
+ )
+ .subscribe(() => {});
+ }*/
+
ngOnDestroy() {
this.sub.unsubscribe();
}
@@ -215,6 +316,7 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
const zoneResult = this.rgwZoneService.getZoneTree(
zone,
this.defaultZoneId,
+ this.zones,
zonegroup,
realm
);
@@ -244,7 +346,12 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
if (!this.realmIds.includes(zonegroup.realm_id)) {
rootNodes = this.rgwZonegroupService.getZonegroupTree(zonegroup, this.defaultZonegroupId);
for (const zone of zonegroup.zones) {
- const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, zonegroup);
+ const zoneResult = this.rgwZoneService.getZoneTree(
+ zone,
+ this.defaultZoneId,
+ this.zones,
+ zonegroup
+ );
firstChildNodes = zoneResult['nodes'];
this.zoneIds = this.zoneIds.concat(zoneResult['zoneIds']);
allFirstChildNodes.push(firstChildNodes);
@@ -262,7 +369,7 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
// get tree for standalone zones(zones that do not belong to a zonegroup)
for (const zone of this.zones) {
if (this.zoneIds.length > 0 && !this.zoneIds.includes(zone.id)) {
- const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId);
+ const zoneResult = this.rgwZoneService.getZoneTree(zone, this.defaultZoneId, this.zones);
rootNodes = zoneResult['nodes'];
allNodes.push(rootNodes);
rootNodes = {};
@@ -428,4 +535,46 @@ export class RgwMultisiteDetailsComponent implements OnDestroy, OnInit {
});
}
}
+
+ enableRgwModule() {
+ let $obs;
+ const fnWaitUntilReconnected = () => {
+ observableTimer(2000).subscribe(() => {
+ // Trigger an API request to check if the connection is
+ // re-established.
+ this.mgrModuleService.list().subscribe(
+ () => {
+ // Resume showing the notification toasties.
+ this.notificationService.suspendToasties(false);
+ // Unblock the whole UI.
+ this.blockUI.stop();
+ // Reload the data table content.
+ this.notificationService.show(NotificationType.success, $localize`Enabled RGW Module`);
+ this.router.navigateByUrl('/', { skipLocationChange: true }).then(() => {
+ this.router.navigate(['/rgw/multisite']);
+ });
+ // Reload the data table content.
+ },
+ () => {
+ fnWaitUntilReconnected();
+ }
+ );
+ });
+ };
+
+ if (!this.rgwModuleStatus) {
+ $obs = this.mgrModuleService.enable('rgw');
+ }
+ $obs.subscribe(
+ () => undefined,
+ () => {
+ // Suspend showing the notification toasties.
+ this.notificationService.suspendToasties(true);
+ // Block the whole UI to prevent user interactions until
+ // the connection to the backend is reestablished
+ this.blockUI.start($localize`Reconnecting, please wait ...`);
+ fnWaitUntilReconnected();
+ }
+ );
+ }
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.html
new file mode 100644
index 00000000000..77988493693
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.html
@@ -0,0 +1,65 @@
+<cd-modal [modalRef]="activeModal">
+ <ng-container i18n="form title"
+ class="modal-title">Export Multi-site Realm Token</ng-container>
+
+ <ng-container class="modal-content">
+ <form name="exportTokenForm"
+ #frm="ngForm"
+ [formGroup]="exportTokenForm">
+ <span *ngIf="loading"
+ class="d-flex justify-content-center">
+ <i [ngClass]="[icons.large3x, icons.spinner, icons.spin]"></i></span>
+ <div class="modal-body"
+ *ngIf="!loading">
+ <cd-alert-panel *ngIf="!tokenValid"
+ type="warning"
+ class="mx-3"
+ i18n>
+ <div *ngFor="let realminfo of realms">
+ <b>{{realminfo.realm}}</b> -
+ {{realminfo.token}}
+ </div>
+ </cd-alert-panel>
+ <div *ngFor="let realminfo of realms">
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ for="realmName"
+ i18n>Realm Name
+ </label>
+ <div class="cd-col-form-input">
+ <input id="realmName"
+ name="realmName"
+ type="text"
+ value="{{ realminfo.realm }}"
+ readonly>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label"
+ for="token"
+ i18n>Token
+ </label>
+ <div class="cd-col-form-input">
+ <input id="realmToken"
+ name="realmToken"
+ type="text"
+ value="{{ realminfo.token }}"
+ class="me-2 mb-4"
+ readonly>
+ <cd-copy-2-clipboard-button
+ source="{{ realminfo.token }}"
+ [byId]="false">
+ </cd-copy-2-clipboard-button>
+ </div>
+ <hr *ngIf="realms.length > 1">
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <cd-back-button class="m-2 float-end"
+ aria-label="Close"
+ (backAction)="activeModal.close()"></cd-back-button>
+ </div>
+ </form>
+ </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.scss
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.scss
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.spec.ts
new file mode 100644
index 00000000000..13c33d339d9
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.spec.ts
@@ -0,0 +1,38 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { ToastrModule } from 'ngx-toastr';
+import { SharedModule } from '~/app/shared/shared.module';
+
+import { RgwMultisiteExportComponent } from './rgw-multisite-export.component';
+
+describe('RgwMultisiteExportComponent', () => {
+ let component: RgwMultisiteExportComponent;
+ let fixture: ComponentFixture<RgwMultisiteExportComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ SharedModule,
+ ReactiveFormsModule,
+ RouterTestingModule,
+ HttpClientTestingModule,
+ ToastrModule.forRoot()
+ ],
+ declarations: [RgwMultisiteExportComponent],
+ providers: [NgbActiveModal]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RgwMultisiteExportComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.ts
new file mode 100644
index 00000000000..0b1b2428729
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.ts
@@ -0,0 +1,62 @@
+import { AfterViewChecked, ChangeDetectorRef, Component, OnInit } from '@angular/core';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { RgwRealm } from '../models/rgw-multisite';
+import { Icons } from '~/app/shared/enum/icons.enum';
+
+@Component({
+ selector: 'cd-rgw-multisite-export',
+ templateUrl: './rgw-multisite-export.component.html',
+ styleUrls: ['./rgw-multisite-export.component.scss']
+})
+export class RgwMultisiteExportComponent implements OnInit, AfterViewChecked {
+ exportTokenForm: CdFormGroup;
+ realms: any;
+ realmList: RgwRealm[];
+ multisiteInfo: any;
+ tokenValid = false;
+ loading = true;
+ icons = Icons;
+
+ constructor(
+ public activeModal: NgbActiveModal,
+ public rgwRealmService: RgwRealmService,
+ public actionLabels: ActionLabelsI18n,
+ public notificationService: NotificationService,
+ private readonly changeDetectorRef: ChangeDetectorRef
+ ) {
+ this.createForm();
+ }
+
+ createForm() {
+ this.exportTokenForm = new CdFormGroup({});
+ }
+
+ onSubmit() {
+ this.activeModal.close();
+ }
+
+ ngOnInit(): void {
+ this.rgwRealmService.getRealmTokens().subscribe((data: object[]) => {
+ this.loading = false;
+ this.realms = data;
+ var base64Matcher = new RegExp(
+ '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{4})$'
+ );
+ this.realms.forEach((realmInfo: any) => {
+ if (base64Matcher.test(realmInfo.token)) {
+ this.tokenValid = true;
+ } else {
+ this.tokenValid = false;
+ }
+ });
+ });
+ }
+
+ ngAfterViewChecked(): void {
+ this.changeDetectorRef.detectChanges();
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html
new file mode 100644
index 00000000000..823e8e8d4f3
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html
@@ -0,0 +1,56 @@
+<cd-modal [modalRef]="activeModal">
+ <ng-container i18n="form title"
+ class="modal-title">Import Multi-site Token</ng-container>
+
+ <ng-container class="modal-content">
+ <form name="importTokenForm"
+ #frm="ngForm"
+ [formGroup]="importTokenForm">
+ <div class="modal-body">
+ <cd-alert-panel type="info"
+ spacingClass="mb-3">Please create a rgw service using the secondary zone(created after submitting this form) to start the replication between zones.
+ </cd-alert-panel>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="realmToken"
+ i18n>Token
+ </label>
+ <div class="cd-col-form-input">
+ <input id="realmToken"
+ name="realmToken"
+ class="form-control"
+ type="text"
+ formControlName="realmToken">
+ <span class="invalid-feedback"
+ *ngIf="importTokenForm.showError('realmToken', frm, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="zoneName"
+ i18n>Secondary Zone Name</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="Zone name..."
+ id="zoneName"
+ name="zoneName"
+ formControlName="zoneName">
+ <span class="invalid-feedback"
+ *ngIf="importTokenForm.showError('zoneName', frm, 'required')"
+ i18n>This field is required.</span>
+ <span class="invalid-feedback"
+ *ngIf="importTokenForm.showError('zoneName', frm, 'uniqueName')"
+ i18n>The chosen zone name is already in use.</span>
+ </div>
+ </div>
+ </div>
+ <div class="modal-footer">
+ <cd-form-button-panel (submitActionEvent)="onSubmit()"
+ [submitText]="actionLabels.IMPORT"
+ [form]="importTokenForm"></cd-form-button-panel>
+ </div>
+ </form>
+ </ng-container>
+</cd-modal>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.scss
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.scss
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts
new file mode 100644
index 00000000000..8ceb42087ea
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts
@@ -0,0 +1,38 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
+import { RouterTestingModule } from '@angular/router/testing';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { ToastrModule } from 'ngx-toastr';
+import { SharedModule } from '~/app/shared/shared.module';
+
+import { RgwMultisiteImportComponent } from './rgw-multisite-import.component';
+
+describe('RgwMultisiteImportComponent', () => {
+ let component: RgwMultisiteImportComponent;
+ let fixture: ComponentFixture<RgwMultisiteImportComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [
+ SharedModule,
+ ReactiveFormsModule,
+ RouterTestingModule,
+ HttpClientTestingModule,
+ ToastrModule.forRoot()
+ ],
+ declarations: [RgwMultisiteImportComponent],
+ providers: [NgbActiveModal]
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(RgwMultisiteImportComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts
new file mode 100644
index 00000000000..5581a80bfe1
--- /dev/null
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts
@@ -0,0 +1,77 @@
+import { Component, OnInit } from '@angular/core';
+import { FormControl, Validators } from '@angular/forms';
+import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { RgwRealmService } from '~/app/shared/api/rgw-realm.service';
+import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
+import { NotificationType } from '~/app/shared/enum/notification-type.enum';
+import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
+import { CdValidators } from '~/app/shared/forms/cd-validators';
+import { NotificationService } from '~/app/shared/services/notification.service';
+import { RgwZone } from '../models/rgw-multisite';
+import _ from 'lodash';
+
+@Component({
+ selector: 'cd-rgw-multisite-import',
+ templateUrl: './rgw-multisite-import.component.html',
+ styleUrls: ['./rgw-multisite-import.component.scss']
+})
+export class RgwMultisiteImportComponent implements OnInit {
+ readonly endpoints = /^((https?:\/\/)|(www.))(?:([a-zA-Z]+)|(\d+\.\d+.\d+.\d+)):\d{2,4}$/;
+ readonly ipv4Rgx = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/i;
+ readonly ipv6Rgx = /^(?:[a-f0-9]{1,4}:){7}[a-f0-9]{1,4}$/i;
+
+ importTokenForm: CdFormGroup;
+ multisiteInfo: object[] = [];
+ zoneList: RgwZone[] = [];
+ zoneNames: string[];
+
+ constructor(
+ public activeModal: NgbActiveModal,
+ public rgwRealmService: RgwRealmService,
+ public actionLabels: ActionLabelsI18n,
+ public notificationService: NotificationService
+ ) {
+ this.createForm();
+ }
+ ngOnInit(): void {
+ this.zoneList =
+ this.multisiteInfo[2] !== undefined && this.multisiteInfo[2].hasOwnProperty('zones')
+ ? this.multisiteInfo[2]['zones']
+ : [];
+ this.zoneNames = this.zoneList.map((zone) => {
+ return zone['name'];
+ });
+ }
+
+ createForm() {
+ this.importTokenForm = new CdFormGroup({
+ realmToken: new FormControl('', {
+ validators: [Validators.required]
+ }),
+ zoneName: new FormControl(null, {
+ validators: [
+ Validators.required,
+ CdValidators.custom('uniqueName', (zoneName: string) => {
+ return this.zoneNames && this.zoneNames.indexOf(zoneName) !== -1;
+ })
+ ]
+ })
+ });
+ }
+
+ onSubmit() {
+ const values = this.importTokenForm.value;
+ this.rgwRealmService.importRealmToken(values['realmToken'], values['zoneName']).subscribe(
+ () => {
+ this.notificationService.show(
+ NotificationType.success,
+ $localize`Realm token import successfull`
+ );
+ this.activeModal.close();
+ },
+ () => {
+ this.importTokenForm.setErrors({ cdSubmitButton: true });
+ }
+ );
+ }
+}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.html
index ceb0afc5d57..4e0cef63322 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.html
@@ -108,28 +108,35 @@
</div>
</div>
<div class="form-group row">
- <label class="cd-col-form-label"
- for="users"
- i18n>System User</label>
+ <label class="cd-col-form-label required"
+ for="access_key"
+ i18n>Access key</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="e.g."
+ id="access_key"
+ name="access_key"
+ formControlName="access_key">
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="access_key"
+ i18n>Secret key</label>
<div class="cd-col-form-input">
- <select id="users"
- name="users"
- class="form-select"
- formControlName="users">
- <option i18n
- *ngIf="users === null"
- [ngValue]="null">Loading...</option>
- <option i18n
- *ngIf="users !== null"
- [ngValue]="null">-- Select a user --</option>
- <option *ngFor="let user of users"
- [value]="user.user_id">{{ user.user_id }}</option>
- </select>
+ <input class="form-control"
+ type="text"
+ placeholder="e.g."
+ id="secret_key"
+ name="secret_key"
+ formControlName="secret_key">
</div>
</div>
</div>
<div class="modal-footer">
<cd-form-button-panel (submitActionEvent)="submit()"
+ [submitText]="actionLabels.MIGRATE"
[form]="multisiteMigrateForm"></cd-form-button-panel>
</div>
</form>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.ts
index 786a30fc119..4ede615a2e4 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.ts
@@ -11,8 +11,9 @@ import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { CdValidators } from '~/app/shared/forms/cd-validators';
import { NotificationService } from '~/app/shared/services/notification.service';
-import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
+import { RgwRealm, RgwZone, RgwZonegroup, SystemKey } from '../models/rgw-multisite';
import { ModalService } from '~/app/shared/services/modal.service';
+import { RgwDaemonService } from '~/app/shared/api/rgw-daemon.service';
@Component({
selector: 'cd-rgw-multisite-migrate',
@@ -51,6 +52,7 @@ export class RgwMultisiteMigrateComponent implements OnInit {
public notificationService: NotificationService,
public rgwZonegroupService: RgwZonegroupService,
public rgwRealmService: RgwRealmService,
+ public rgwDaemonService: RgwDaemonService,
public modalService: ModalService
) {
this.createForm();
@@ -133,7 +135,8 @@ export class RgwMultisiteMigrateComponent implements OnInit {
Validators.required
]
),
- users: new FormControl(null)
+ access_key: new FormControl(null),
+ secret_key: new FormControl(null)
});
}
@@ -159,9 +162,6 @@ export class RgwMultisiteMigrateComponent implements OnInit {
this.zoneNames = this.zoneList.map((zone) => {
return zone['name'];
});
- this.rgwZoneService.getUserList('default').subscribe((users: any) => {
- this.users = users.filter((user: any) => user['system'] === true);
- });
}
submit() {
@@ -170,17 +170,20 @@ export class RgwMultisiteMigrateComponent implements OnInit {
this.realm.name = values['realmName'];
this.zonegroup = new RgwZonegroup();
this.zonegroup.name = values['zonegroupName'];
- this.zonegroup.endpoints = this.checkUrlArray(values['zonegroup_endpoints']);
+ this.zonegroup.endpoints = values['zonegroup_endpoints'];
this.zone = new RgwZone();
this.zone.name = values['zoneName'];
- this.zone.endpoints = this.checkUrlArray(values['zone_endpoints']);
- const user = values['users'];
- this.rgwMultisiteService.migrate(this.realm, this.zonegroup, this.zone, user).subscribe(
+ this.zone.endpoints = values['zone_endpoints'];
+ this.zone.system_key = new SystemKey();
+ this.zone.system_key.access_key = values['access_key'];
+ this.zone.system_key.secret_key = values['secret_key'];
+ this.rgwMultisiteService.migrate(this.realm, this.zonegroup, this.zone).subscribe(
() => {
this.notificationService.show(
NotificationType.success,
$localize`${this.actionLabels.MIGRATE} done successfully`
);
+ this.notificationService.show(NotificationType.success, `Daemon restart scheduled`);
this.submitAction.emit();
this.activeModal.close();
},
@@ -189,14 +192,4 @@ export class RgwMultisiteMigrateComponent implements OnInit {
}
);
}
-
- checkUrlArray(endpoints: string) {
- let endpointsArray = [];
- if (endpoints.includes(',')) {
- endpointsArray = endpoints.split(',');
- } else {
- endpointsArray.push(endpoints);
- }
- return endpointsArray;
- }
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html
index 0c4ec560d38..8f4422051c4 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html
@@ -109,33 +109,34 @@
i18n>Please enter a valid IP address.</span>
</div>
</div>
- <div class="form-group row"
- *ngIf="action === 'edit'">
- <label class="cd-col-form-label"
- for="users"
- i18n>System User</label>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="access_key"
+ i18n>Access key</label>
<div class="cd-col-form-input">
- <select id="users"
- name="users"
- class="form-select"
- formControlName="users">
- <option i18n
- *ngIf="users === null"
- [ngValue]="null">Loading...</option>
- <option i18n
- *ngIf="users !== null"
- [ngValue]="null">-- Select a user --</option>
- <option *ngFor="let user of users"
- [value]="user.user_id">{{ user.user_id }}</option>
- </select><br><br>
- <div *ngIf="info.data.zone_zonegroup.is_master && info.data.is_master">
- <button type="button"
- class="btn btn-light"
- (click)="CreateSystemUser()">
- Create System User
- </button>
- </div>
+ <input class="form-control"
+ type="text"
+ placeholder="DiPt4V7WWvy2njL1z6aC"
+ id="access_key"
+ name="access_key"
+ formControlName="access_key">
+ </div>
+ </div>
+ <div class="form-group row">
+ <label class="cd-col-form-label required"
+ for="access_key"
+ i18n>Secret key</label>
+ <div class="cd-col-form-input">
+ <input class="form-control"
+ type="text"
+ placeholder="xSZUdYky0bTctAdCEEW8ikhfBVKsBV5LFYL82vvh"
+ id="secret_key"
+ name="secret_key"
+ formControlName="secret_key">
</div>
+ </div>
+ <div class="form-group row"
+ *ngIf="action === 'edit'">
<div *ngIf="action === 'edit'">
<legend>Placement Targets</legend>
<div class="form-group row">
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts
index 1fe980e4cd5..1fb9c178da1 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts
@@ -11,9 +11,8 @@ import { NotificationType } from '~/app/shared/enum/notification-type.enum';
import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
import { CdValidators } from '~/app/shared/forms/cd-validators';
import { NotificationService } from '~/app/shared/services/notification.service';
-import { RgwRealm, RgwZone, RgwZonegroup } from '../models/rgw-multisite';
+import { RgwRealm, RgwZone, RgwZonegroup, SystemKey } from '../models/rgw-multisite';
import { ModalService } from '~/app/shared/services/modal.service';
-import { RgwSystemUserComponent } from '../rgw-system-user/rgw-system-user.component';
@Component({
selector: 'cd-rgw-multisite-zone-form',
@@ -55,6 +54,7 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
access_key: any;
master_zonegroup_of_realm: RgwZonegroup;
compressionTypes = ['lz4', 'zlib', 'snappy'];
+ userListReady: boolean = false;
constructor(
public activeModal: NgbActiveModal,
@@ -87,7 +87,7 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
default_zone: new FormControl(false),
master_zone: new FormControl(false),
selectedZonegroup: new FormControl(null),
- zone_endpoints: new FormControl([], {
+ zone_endpoints: new FormControl(null, {
validators: [
CdValidators.custom('endpoint', (value: string) => {
if (_.isEmpty(value)) {
@@ -112,7 +112,8 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
Validators.required
]
}),
- users: new FormControl(null),
+ access_key: new FormControl(null),
+ secret_key: new FormControl(null),
placementTarget: new FormControl(null),
placementDataPool: new FormControl(''),
placementIndexPool: new FormControl(null),
@@ -136,24 +137,6 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
this.multisiteZoneForm.get('master_zone').disable();
this.disableMaster = true;
}
- const zonegroupInfo = this.zonegroupList.filter((zgroup: any) => zgroup.name === zg.name)[0];
- if (zonegroupInfo) {
- const realm_id = zonegroupInfo.realm_id;
- this.master_zonegroup_of_realm = this.zonegroupList.filter(
- (zg: any) => zg.realm_id === realm_id && zg.is_master === true
- )[0];
- }
- if (this.master_zonegroup_of_realm) {
- this.master_zone_of_master_zonegroup = this.zoneList.filter(
- (zone: any) => zone.id === this.master_zonegroup_of_realm.master_zone
- )[0];
- }
- if (this.master_zone_of_master_zonegroup) {
- this.getUserInfo(this.master_zone_of_master_zonegroup);
- }
- if (zonegroupInfo.is_master && this.multisiteZoneForm.getValue('master_zone') === true) {
- this.createSystemUser = true;
- }
});
if (
this.multisiteZoneForm.getValue('selectedZonegroup') !==
@@ -193,7 +176,9 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
this.multisiteZoneForm.get('selectedZonegroup').setValue(this.info.data.parent);
this.multisiteZoneForm.get('default_zone').setValue(this.info.data.is_default);
this.multisiteZoneForm.get('master_zone').setValue(this.info.data.is_master);
- this.multisiteZoneForm.get('zone_endpoints').setValue(this.info.data.endpoints);
+ this.multisiteZoneForm.get('zone_endpoints').setValue(this.info.data.endpoints.toString());
+ this.multisiteZoneForm.get('access_key').setValue(this.info.data.access_key);
+ this.multisiteZoneForm.get('secret_key').setValue(this.info.data.secret_key);
this.multisiteZoneForm
.get('placementTarget')
.setValue(this.info.parent.data.default_placement);
@@ -209,9 +194,6 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
const zone = new RgwZone();
zone.name = this.info.data.name;
this.onZoneGroupChange(this.info.data.parent);
- setTimeout(() => {
- this.getUserInfo(zone);
- }, 1000);
}
if (
this.multisiteZoneForm.getValue('selectedZonegroup') !==
@@ -222,22 +204,6 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
}
}
- getUserInfo(zone: RgwZone) {
- this.rgwZoneService
- .getUserList(this.master_zone_of_master_zonegroup.name)
- .subscribe((users: any) => {
- this.users = users.filter((user: any) => user.keys.length !== 0);
- this.rgwZoneService.get(zone).subscribe((zone: RgwZone) => {
- const access_key = zone.system_key['access_key'];
- const user = this.users.filter((user: any) => user.keys[0].access_key === access_key);
- if (user.length > 0) {
- this.multisiteZoneForm.get('users').setValue(user[0].user_id);
- }
- return user[0].user_id;
- });
- });
- }
-
getZonePlacementData(placementTarget: string) {
this.zone = new RgwZone();
this.zone.name = this.info.data.name;
@@ -296,20 +262,17 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
this.zonegroup.name = values['selectedZonegroup'];
this.zone = new RgwZone();
this.zone.name = values['zoneName'];
- this.zone.endpoints = this.checkUrlArray(values['zone_endpoints']);
- if (this.createSystemUser) {
- values['users'] = values['zoneName'] + '_User';
- }
+ this.zone.endpoints = values['zone_endpoints'];
+ this.zone.system_key = new SystemKey();
+ this.zone.system_key.access_key = values['access_key'];
+ this.zone.system_key.secret_key = values['secret_key'];
this.rgwZoneService
.create(
this.zone,
this.zonegroup,
values['default_zone'],
values['master_zone'],
- this.zone.endpoints,
- values['users'],
- this.createSystemUser,
- this.master_zone_of_master_zonegroup
+ this.zone.endpoints
)
.subscribe(
() => {
@@ -328,10 +291,10 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
this.zonegroup.name = values['selectedZonegroup'];
this.zone = new RgwZone();
this.zone.name = this.info.data.name;
- this.zone.endpoints =
- values['zone_endpoints'] === this.info.data.endpoints
- ? values['zone_endpoints']
- : this.checkUrlArray(values['zone_endpoints']);
+ this.zone.endpoints = values['zone_endpoints'];
+ this.zone.system_key = new SystemKey();
+ this.zone.system_key.access_key = values['access_key'];
+ this.zone.system_key.secret_key = values['secret_key'];
this.rgwZoneService
.update(
this.zone,
@@ -340,15 +303,13 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
values['default_zone'],
values['master_zone'],
this.zone.endpoints,
- values['users'],
values['placementTarget'],
values['placementDataPool'],
values['placementIndexPool'],
values['placementDataExtraPool'],
values['storageClass'],
values['storageDataPool'],
- values['storageCompression'],
- this.master_zone_of_master_zonegroup
+ values['storageCompression']
)
.subscribe(
() => {
@@ -374,14 +335,4 @@ export class RgwMultisiteZoneFormComponent implements OnInit {
}
return endpointsArray;
}
-
- CreateSystemUser() {
- const initialState = {
- zoneName: this.master_zone_of_master_zonegroup.name
- };
- this.bsModalRef = this.modalService.show(RgwSystemUserComponent, initialState);
- this.bsModalRef.componentInstance.submitAction.subscribe(() => {
- this.getUserInfo(this.master_zone_of_master_zonegroup);
- });
- }
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component.ts
index be9e3f64859..01955cd49a1 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component.ts
@@ -217,7 +217,7 @@ export class RgwMultisiteZonegroupFormComponent implements OnInit {
this.realm.name = values['selectedRealm'];
this.zonegroup = new RgwZonegroup();
this.zonegroup.name = values['zonegroupName'];
- this.zonegroup.endpoints = this.checkUrlArray(values['zonegroup_endpoints']);
+ this.zonegroup.endpoints = values['zonegroup_endpoints'];
this.rgwZonegroupService
.create(this.realm, this.zonegroup, values['default_zonegroup'], values['master_zonegroup'])
.subscribe(
@@ -248,10 +248,7 @@ export class RgwMultisiteZonegroupFormComponent implements OnInit {
this.zonegroup = new RgwZonegroup();
this.zonegroup.name = this.info.data.name;
this.newZonegroupName = values['zonegroupName'];
- this.zonegroup.endpoints =
- values['zonegroup_endpoints'] === this.info.data.endpoints
- ? values['zonegroup_endpoints']
- : this.checkUrlArray(values['zonegroup_endpoints']);
+ this.zonegroup.endpoints = values['zonegroup_endpoints'];
this.zonegroup.placement_targets = values['placementTargets'];
this.rgwZonegroupService
.update(
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 0ecbd3eb53f..a0082209b96 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
@@ -38,6 +38,9 @@ import { RgwMultisiteZoneDeletionFormComponent } from './models/rgw-multisite-zo
import { RgwMultisiteZonegroupDeletionFormComponent } from './models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component';
import { RgwSystemUserComponent } from './rgw-system-user/rgw-system-user.component';
import { RgwMultisiteMigrateComponent } from './rgw-multisite-migrate/rgw-multisite-migrate.component';
+import { RgwMultisiteImportComponent } from './rgw-multisite-import/rgw-multisite-import.component';
+import { RgwMultisiteExportComponent } from './rgw-multisite-export/rgw-multisite-export.component';
+import { CreateRgwServiceEntitiesComponent } from './create-rgw-service-entities/create-rgw-service-entities.component';
@NgModule({
imports: [
@@ -85,7 +88,10 @@ import { RgwMultisiteMigrateComponent } from './rgw-multisite-migrate/rgw-multis
RgwMultisiteZoneDeletionFormComponent,
RgwMultisiteZonegroupDeletionFormComponent,
RgwSystemUserComponent,
- RgwMultisiteMigrateComponent
+ RgwMultisiteMigrateComponent,
+ RgwMultisiteImportComponent,
+ RgwMultisiteExportComponent,
+ CreateRgwServiceEntitiesComponent
]
})
export class RgwModule {}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts
index 5c513c7f1fa..a6007404681 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts
@@ -79,4 +79,15 @@ export class RgwDaemonService {
})
);
}
+
+ setMultisiteConfig(realm_name: string, zonegroup_name: string, zone_name: string) {
+ return this.request((params: HttpParams) => {
+ params = params.appendAll({
+ realm_name: realm_name,
+ zonegroup_name: zonegroup_name,
+ zone_name: zone_name
+ });
+ return this.http.put(`${this.url}/set_multisite_config`, null, { params: params });
+ });
+ }
}
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 0a601bf0fa1..fbd2ad64ec4 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
@@ -1,7 +1,7 @@
-import { HttpClient } from '@angular/common/http';
+import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
-import { RgwDaemonService } from './rgw-daemon.service';
import { RgwRealm, RgwZone, RgwZonegroup } from '~/app/ceph/rgw/models/rgw-multisite';
+import { RgwDaemonService } from './rgw-daemon.service';
@Injectable({
providedIn: 'root'
@@ -9,25 +9,20 @@ import { RgwRealm, RgwZone, RgwZonegroup } from '~/app/ceph/rgw/models/rgw-multi
export class RgwMultisiteService {
private url = 'ui-api/rgw/multisite';
- constructor(private http: HttpClient, private rgwDaemonService: RgwDaemonService) {}
-
- getMultisiteSyncStatus() {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/sync_status`);
- });
- }
+ constructor(private http: HttpClient, public rgwDaemonService: RgwDaemonService) {}
- migrate(realm: RgwRealm, zonegroup: RgwZonegroup, zone: RgwZone, user: string) {
- return this.rgwDaemonService.request((requestBody: any) => {
- requestBody = {
+ migrate(realm: RgwRealm, zonegroup: RgwZonegroup, zone: RgwZone) {
+ return this.rgwDaemonService.request((params: HttpParams) => {
+ params = params.appendAll({
realm_name: realm.name,
zonegroup_name: zonegroup.name,
zone_name: zone.name,
zonegroup_endpoints: zonegroup.endpoints,
zone_endpoints: zone.endpoints,
- user: user
- };
- return this.http.put(`${this.url}/migrate`, requestBody);
+ access_key: zone.system_key.access_key,
+ secret_key: zone.system_key.secret_key
+ });
+ return this.http.put(`${this.url}/migrate`, null, { params: params });
});
}
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts
index 63bb7b8c657..efa882c8b34 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts
@@ -11,54 +11,43 @@ import { RgwDaemonService } from './rgw-daemon.service';
export class RgwRealmService {
private url = 'api/rgw/realm';
- constructor(private http: HttpClient, private rgwDaemonService: RgwDaemonService) {}
+ constructor(private http: HttpClient, public rgwDaemonService: RgwDaemonService) {}
create(realm: RgwRealm, defaultRealm: boolean) {
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- realm_name: realm.name,
- default: defaultRealm
- });
- return this.http.post(`${this.url}`, null, { params: params });
- });
+ let requestBody = {
+ realm_name: realm.name,
+ default: defaultRealm
+ };
+ return this.http.post(`${this.url}`, requestBody);
}
update(realm: RgwRealm, defaultRealm: boolean, newRealmName: string) {
- return this.rgwDaemonService.request((requestBody: any) => {
- requestBody = {
- realm_name: realm.name,
- default: defaultRealm,
- new_realm_name: newRealmName
- };
- return this.http.put(`${this.url}/${realm.name}`, requestBody);
- });
+ let requestBody = {
+ realm_name: realm.name,
+ default: defaultRealm,
+ new_realm_name: newRealmName
+ };
+ return this.http.put(`${this.url}/${realm.name}`, requestBody);
}
list(): Observable<object> {
- return this.rgwDaemonService.request(() => {
- return this.http.get<object>(`${this.url}`);
- });
+ return this.http.get<object>(`${this.url}`);
}
- get(realm: RgwRealm): Observable<RgwRealm> {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/${realm.name}`);
- });
+ get(realm: RgwRealm): Observable<object> {
+ return this.http.get(`${this.url}/${realm.name}`);
}
getAllRealmsInfo(): Observable<object> {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/get_all_realms_info`);
- });
+ return this.http.get(`${this.url}/get_all_realms_info`);
}
delete(realmName: string): Observable<any> {
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- realm_name: realmName
- });
- return this.http.delete(`${this.url}/${realmName}`, { params: params });
+ let params = new HttpParams();
+ params = params.appendAll({
+ realm_name: realmName
});
+ return this.http.delete(`${this.url}/${realmName}`, { params: params });
}
getRealmTree(realm: RgwRealm, defaultRealmId: string) {
@@ -76,4 +65,20 @@ export class RgwRealmService {
realmIds: realmIds
};
}
+
+ importRealmToken(realm_token: string, zone_name: string) {
+ return this.rgwDaemonService.request((params: HttpParams) => {
+ params = params.appendAll({
+ realm_token: realm_token,
+ zone_name: zone_name
+ });
+ return this.http.post(`${this.url}/import_realm_token`, null, { params: params });
+ });
+ }
+
+ getRealmTokens() {
+ return this.rgwDaemonService.request(() => {
+ return this.http.get(`${this.url}/get_realm_tokens`);
+ });
+ }
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts
index 513747c31a2..02877816102 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts
@@ -3,7 +3,6 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { RgwRealm, RgwZone, RgwZonegroup } from '~/app/ceph/rgw/models/rgw-multisite';
import { Icons } from '../enum/icons.enum';
-import { RgwDaemonService } from './rgw-daemon.service';
@Injectable({
providedIn: 'root'
@@ -11,55 +10,38 @@ import { RgwDaemonService } from './rgw-daemon.service';
export class RgwZoneService {
private url = 'api/rgw/zone';
- constructor(private http: HttpClient, private rgwDaemonService: RgwDaemonService) {}
+ constructor(private http: HttpClient) {}
create(
zone: RgwZone,
zonegroup: RgwZonegroup,
defaultZone: boolean,
master: boolean,
- endpoints: Array<string>,
- user: string,
- createSystemUser: boolean,
- master_zone_of_master_zonegroup: RgwZone
+ endpoints: string
) {
- let master_zone_name = '';
- if (master_zone_of_master_zonegroup !== undefined) {
- master_zone_name = master_zone_of_master_zonegroup.name;
- } else {
- master_zone_name = '';
- }
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- zone_name: zone.name,
- zonegroup_name: zonegroup.name,
- default: defaultZone,
- master: master,
- zone_endpoints: endpoints,
- user: user,
- createSystemUser: createSystemUser,
- master_zone_of_master_zonegroup: master_zone_name
- });
- return this.http.post(`${this.url}`, null, { params: params });
+ let params = new HttpParams();
+ params = params.appendAll({
+ zone_name: zone.name,
+ zonegroup_name: zonegroup.name,
+ default: defaultZone,
+ master: master,
+ zone_endpoints: endpoints,
+ access_key: zone.system_key.access_key,
+ secret_key: zone.system_key.secret_key
});
+ return this.http.post(`${this.url}`, null, { params: params });
}
list(): Observable<object> {
- return this.rgwDaemonService.request(() => {
- return this.http.get<object>(`${this.url}`);
- });
+ return this.http.get<object>(`${this.url}`);
}
- get(zone: RgwZone): Observable<RgwZone> {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/${zone.name}`);
- });
+ get(zone: RgwZone): Observable<object> {
+ return this.http.get(`${this.url}/${zone.name}`);
}
getAllZonesInfo(): Observable<object> {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/get_all_zones_info`);
- });
+ return this.http.get(`${this.url}/get_all_zones_info`);
}
delete(
@@ -68,15 +50,14 @@ export class RgwZoneService {
pools: Set<string>,
zonegroupName: string
): Observable<any> {
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- zone_name: zoneName,
- delete_pools: deletePools,
- pools: Array.from(pools.values()),
- zonegroup_name: zonegroupName
- });
- return this.http.delete(`${this.url}/${zoneName}`, { params: params });
+ let params = new HttpParams();
+ params = params.appendAll({
+ zone_name: zoneName,
+ delete_pools: deletePools,
+ pools: Array.from(pools.values()),
+ zonegroup_name: zonegroupName
});
+ return this.http.delete(`${this.url}/${zoneName}`, { params: params });
}
update(
@@ -85,46 +66,42 @@ export class RgwZoneService {
newZoneName: string,
defaultZone?: boolean,
master?: boolean,
- endpoints?: Array<string>,
- user?: string,
+ endpoints?: string,
placementTarget?: string,
dataPool?: string,
indexPool?: string,
dataExtraPool?: string,
storageClass?: string,
dataPoolClass?: string,
- compression?: string,
- master_zone_of_master_zonegroup?: RgwZone
+ compression?: string
) {
- let master_zone_name = '';
- if (master_zone_of_master_zonegroup !== undefined) {
- master_zone_name = master_zone_of_master_zonegroup.name;
- } else {
- master_zone_name = '';
- }
- return this.rgwDaemonService.request((requestBody: any) => {
- requestBody = {
- zone_name: zone.name,
- zonegroup_name: zonegroup.name,
- new_zone_name: newZoneName,
- default: defaultZone,
- master: master,
- zone_endpoints: endpoints,
- user: user,
- placement_target: placementTarget,
- data_pool: dataPool,
- index_pool: indexPool,
- data_extra_pool: dataExtraPool,
- storage_class: storageClass,
- data_pool_class: dataPoolClass,
- compression: compression,
- master_zone_of_master_zonegroup: master_zone_name
- };
- return this.http.put(`${this.url}/${zone.name}`, requestBody);
- });
+ let requestBody = {
+ zone_name: zone.name,
+ zonegroup_name: zonegroup.name,
+ new_zone_name: newZoneName,
+ default: defaultZone,
+ master: master,
+ zone_endpoints: endpoints,
+ access_key: zone.system_key.access_key,
+ secret_key: zone.system_key.secret_key,
+ placement_target: placementTarget,
+ data_pool: dataPool,
+ index_pool: indexPool,
+ data_extra_pool: dataExtraPool,
+ storage_class: storageClass,
+ data_pool_class: dataPoolClass,
+ compression: compression
+ };
+ return this.http.put(`${this.url}/${zone.name}`, requestBody);
}
- getZoneTree(zone: RgwZone, defaultZoneId: string, zonegroup?: RgwZonegroup, realm?: RgwRealm) {
+ getZoneTree(
+ zone: RgwZone,
+ defaultZoneId: string,
+ zones: RgwZone[],
+ zonegroup?: RgwZonegroup,
+ realm?: RgwRealm
+ ) {
let nodes = {};
let zoneIds = [];
nodes['id'] = zone.id;
@@ -141,6 +118,28 @@ export class RgwZoneService {
nodes['endpoints'] = zone.endpoints;
nodes['is_master'] = zonegroup && zonegroup.master_zone === zone.id ? true : false;
nodes['type'] = 'zone';
+ const zoneNames = zones.map((zone: RgwZone) => {
+ return zone['name'];
+ });
+ nodes['secondary_zone'] = !zoneNames.includes(zone.name) ? true : false;
+ const zoneInfo = zones.filter((zoneInfo) => zoneInfo.name === zone.name);
+ if (zoneInfo && zoneInfo.length > 0) {
+ const access_key = zoneInfo[0].system_key['access_key'];
+ const secret_key = zoneInfo[0].system_key['secret_key'];
+ nodes['access_key'] = access_key ? access_key : '';
+ nodes['secret_key'] = secret_key ? secret_key : '';
+ nodes['user'] = access_key && access_key !== '' ? true : false;
+ }
+ if (nodes['access_key'] === '' || nodes['access_key'] === 'null') {
+ nodes['show_warning'] = true;
+ nodes['warning_message'] = 'Access/Secret keys not found';
+ } else {
+ nodes['show_warning'] = false;
+ }
+ if (nodes['endpoints'] && nodes['endpoints'].length === 0) {
+ nodes['show_warning'] = true;
+ nodes['warning_message'] = nodes['warning_message'] + '\n' + 'Endpoints not configured';
+ }
return {
nodes: nodes,
zoneIds: zoneIds
@@ -148,27 +147,22 @@ export class RgwZoneService {
}
getPoolNames() {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/get_pool_names`);
- });
+ return this.http.get(`${this.url}/get_pool_names`);
}
createSystemUser(userName: string, zone: string) {
- return this.rgwDaemonService.request((requestBody: any) => {
- requestBody = {
- userName: userName,
- zoneName: zone
- };
- return this.http.put(`${this.url}/create_system_user`, requestBody);
- });
+ let requestBody = {
+ userName: userName,
+ zoneName: zone
+ };
+ return this.http.put(`${this.url}/create_system_user`, requestBody);
}
getUserList(zoneName: string) {
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- zoneName: zoneName
- });
- return this.http.get(`${this.url}/get_user_list`, { params: params });
+ let params = new HttpParams();
+ params = params.appendAll({
+ zoneName: zoneName
});
+ return this.http.get(`${this.url}/get_user_list`, { params: params });
}
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts
index 28b5cc676a8..7f795c1d1d8 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts
@@ -3,7 +3,6 @@ import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { RgwRealm, RgwZonegroup } from '~/app/ceph/rgw/models/rgw-multisite';
import { Icons } from '../enum/icons.enum';
-import { RgwDaemonService } from './rgw-daemon.service';
@Injectable({
providedIn: 'root'
@@ -11,19 +10,18 @@ import { RgwDaemonService } from './rgw-daemon.service';
export class RgwZonegroupService {
private url = 'api/rgw/zonegroup';
- constructor(private http: HttpClient, private rgwDaemonService: RgwDaemonService) {}
+ constructor(private http: HttpClient) {}
create(realm: RgwRealm, zonegroup: RgwZonegroup, defaultZonegroup: boolean, master: boolean) {
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- realm_name: realm.name,
- zonegroup_name: zonegroup.name,
- default: defaultZonegroup,
- master: master,
- zonegroup_endpoints: zonegroup.endpoints
- });
- return this.http.post(`${this.url}`, null, { params: params });
+ let params = new HttpParams();
+ params = params.appendAll({
+ realm_name: realm.name,
+ zonegroup_name: zonegroup.name,
+ default: defaultZonegroup,
+ master: master,
+ zonegroup_endpoints: zonegroup.endpoints
});
+ return this.http.post(`${this.url}`, null, { params: params });
}
update(
@@ -35,49 +33,40 @@ export class RgwZonegroupService {
removedZones?: string[],
addedZones?: string[]
) {
- return this.rgwDaemonService.request((requestBody: any) => {
- requestBody = {
- zonegroup_name: zonegroup.name,
- realm_name: realm.name,
- new_zonegroup_name: newZonegroupName,
- default: defaultZonegroup,
- master: master,
- zonegroup_endpoints: zonegroup.endpoints,
- placement_targets: zonegroup.placement_targets,
- remove_zones: removedZones,
- add_zones: addedZones
- };
- return this.http.put(`${this.url}/${zonegroup.name}`, requestBody);
- });
+ let requestBody = {
+ zonegroup_name: zonegroup.name,
+ realm_name: realm.name,
+ new_zonegroup_name: newZonegroupName,
+ default: defaultZonegroup,
+ master: master,
+ zonegroup_endpoints: zonegroup.endpoints,
+ placement_targets: zonegroup.placement_targets,
+ remove_zones: removedZones,
+ add_zones: addedZones
+ };
+ return this.http.put(`${this.url}/${zonegroup.name}`, requestBody);
}
list(): Observable<object> {
- return this.rgwDaemonService.request(() => {
- return this.http.get<object>(`${this.url}`);
- });
+ return this.http.get<object>(`${this.url}`);
}
- get(zonegroup: RgwZonegroup): Observable<RgwZonegroup> {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/${zonegroup.name}`);
- });
+ get(zonegroup: RgwZonegroup): Observable<object> {
+ return this.http.get(`${this.url}/${zonegroup.name}`);
}
getAllZonegroupsInfo(): Observable<object> {
- return this.rgwDaemonService.request(() => {
- return this.http.get(`${this.url}/get_all_zonegroups_info`);
- });
+ return this.http.get(`${this.url}/get_all_zonegroups_info`);
}
delete(zonegroupName: string, deletePools: boolean, pools: Set<string>): Observable<any> {
- return this.rgwDaemonService.request((params: HttpParams) => {
- params = params.appendAll({
- zonegroup_name: zonegroupName,
- delete_pools: deletePools,
- pools: Array.from(pools.values())
- });
- return this.http.delete(`${this.url}/${zonegroupName}`, { params: params });
+ let params = new HttpParams();
+ params = params.appendAll({
+ zonegroup_name: zonegroupName,
+ delete_pools: deletePools,
+ pools: Array.from(pools.values())
});
+ return this.http.delete(`${this.url}/${zonegroupName}`, { params: params });
}
getZonegroupTree(zonegroup: RgwZonegroup, defaultZonegroupId: string, realm?: RgwRealm) {
@@ -95,6 +84,10 @@ export class RgwZonegroupService {
nodes['zones'] = zonegroup.zones;
nodes['placement_targets'] = zonegroup.placement_targets;
nodes['default_placement'] = zonegroup.default_placement;
+ if (nodes['endpoints'].length === 0) {
+ nodes['show_warning'] = true;
+ nodes['warning_message'] = 'Endpoints not configured';
+ }
return nodes;
}
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html
index be8096427a6..30f8b530a59 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html
@@ -1,6 +1,7 @@
<ngb-alert type="{{ bootstrapClass }}"
[dismissible]="dismissible"
- (closed)="onClose()">
+ (closed)="onClose()"
+ [ngClass]="spacingClass">
<table>
<ng-container *ngIf="size === 'normal'; else slim">
<tr>
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts
index 51088840e33..cc2024baa23 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts
@@ -24,6 +24,8 @@ export class AlertPanelComponent implements OnInit {
showTitle = true;
@Input()
dismissible = false;
+ @Input()
+ spacingClass = '';
/**
* The event that is triggered when the close button (x) has been
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts
index f271c364c26..177382c5350 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts
@@ -37,6 +37,9 @@ export interface CephServiceAdditionalSpec {
ssl_key: string;
port: number;
initial_admin_password: string;
+ rgw_realm: string;
+ rgw_zonegroup: string;
+ rgw_zone: string;
}
export interface CephServicePlacement {
diff --git a/src/pybind/mgr/dashboard/openapi.yaml b/src/pybind/mgr/dashboard/openapi.yaml
index 83c58d9c5ca..dff1912142a 100644
--- a/src/pybind/mgr/dashboard/openapi.yaml
+++ b/src/pybind/mgr/dashboard/openapi.yaml
@@ -8328,6 +8328,47 @@ paths:
summary: Display RGW Daemons
tags:
- RgwDaemon
+ /api/rgw/daemon/set_multisite_config:
+ put:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ daemon_name:
+ type: string
+ realm_name:
+ type: string
+ zone_name:
+ type: string
+ zonegroup_name:
+ type: string
+ type: object
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource updated.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwDaemon
/api/rgw/daemon/{svc_id}:
get:
parameters:
@@ -8357,12 +8398,7 @@ paths:
- RgwDaemon
/api/rgw/realm:
get:
- parameters:
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
+ parameters: []
responses:
'200':
content:
@@ -8389,8 +8425,6 @@ paths:
application/json:
schema:
properties:
- daemon_name:
- type: string
default:
type: string
realm_name:
@@ -8445,6 +8479,70 @@ paths:
- jwt: []
tags:
- RgwRealm
+ /api/rgw/realm/get_realm_tokens:
+ get:
+ parameters: []
+ responses:
+ '200':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwRealm
+ /api/rgw/realm/import_realm_token:
+ post:
+ parameters: []
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ daemon_name:
+ type: string
+ realm_token:
+ type: string
+ zone_name:
+ type: string
+ required:
+ - realm_token
+ - zone_name
+ type: object
+ responses:
+ '201':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Resource created.
+ '202':
+ content:
+ application/vnd.ceph.api.v1.0+json:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ tags:
+ - RgwRealm
/api/rgw/realm/{realm_name}:
delete:
parameters:
@@ -8453,11 +8551,6 @@ paths:
required: true
schema:
type: string
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
responses:
'202':
content:
@@ -8489,11 +8582,6 @@ paths:
required: true
schema:
type: string
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
responses:
'200':
content:
@@ -8525,8 +8613,6 @@ paths:
application/json:
schema:
properties:
- daemon_name:
- type: string
default:
default: ''
type: string
@@ -9282,12 +9368,7 @@ paths:
- RgwUser
/api/rgw/zone:
get:
- parameters:
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
+ parameters: []
responses:
'200':
content:
@@ -9314,10 +9395,7 @@ paths:
application/json:
schema:
properties:
- createSystemUser:
- default: false
- type: boolean
- daemon_name:
+ access_key:
type: string
default:
default: false
@@ -9325,9 +9403,7 @@ paths:
master:
default: false
type: boolean
- master_zone_of_master_zonegroup:
- type: string
- user:
+ secret_key:
type: string
zone_endpoints:
type: string
@@ -9370,8 +9446,6 @@ paths:
application/json:
schema:
properties:
- daemon_name:
- type: string
userName:
type: string
zoneName:
@@ -9453,11 +9527,6 @@ paths:
parameters:
- allowEmptyValue: true
in: query
- name: daemon_name
- schema:
- type: string
- - allowEmptyValue: true
- in: query
name: zoneName
schema:
type: string
@@ -9503,11 +9572,6 @@ paths:
name: zonegroup_name
schema:
type: string
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
responses:
'202':
content:
@@ -9539,11 +9603,6 @@ paths:
required: true
schema:
type: string
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
responses:
'200':
content:
@@ -9575,10 +9634,11 @@ paths:
application/json:
schema:
properties:
- compression:
+ access_key:
default: ''
type: string
- daemon_name:
+ compression:
+ default: ''
type: string
data_extra_pool:
default: ''
@@ -9598,21 +9658,19 @@ paths:
master:
default: ''
type: string
- master_zone_of_master_zonegroup:
- type: string
new_zone_name:
type: string
placement_target:
default: ''
type: string
- storage_class:
+ secret_key:
default: ''
type: string
- user:
+ storage_class:
default: ''
type: string
zone_endpoints:
- default: []
+ default: ''
type: string
zonegroup_name:
type: string
@@ -9646,12 +9704,7 @@ paths:
- RgwZone
/api/rgw/zonegroup:
get:
- parameters:
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
+ parameters: []
responses:
'200':
content:
@@ -9678,8 +9731,6 @@ paths:
application/json:
schema:
properties:
- daemon_name:
- type: string
default:
type: string
master:
@@ -9758,11 +9809,6 @@ paths:
name: pools
schema:
type: string
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
responses:
'202':
content:
@@ -9794,11 +9840,6 @@ paths:
required: true
schema:
type: string
- - allowEmptyValue: true
- in: query
- name: daemon_name
- schema:
- type: string
responses:
'200':
content:
@@ -9833,8 +9874,6 @@ paths:
add_zones:
default: []
type: string
- daemon_name:
- type: string
default:
default: ''
type: string
@@ -9852,7 +9891,7 @@ paths:
default: []
type: string
zonegroup_endpoints:
- default: []
+ default: ''
type: string
required:
- realm_name
diff --git a/src/pybind/mgr/dashboard/services/ceph_service.py b/src/pybind/mgr/dashboard/services/ceph_service.py
index f0e21c59898..135f88ca2c9 100644
--- a/src/pybind/mgr/dashboard/services/ceph_service.py
+++ b/src/pybind/mgr/dashboard/services/ceph_service.py
@@ -294,6 +294,35 @@ class CephService(object):
return {}
@classmethod
+ def set_multisite_config(cls, realm_name, zonegroup_name, zone_name, daemon_name):
+ full_daemon_name = 'rgw.' + daemon_name
+
+ KMS_CONFIG = [
+ ['rgw_realm', realm_name],
+ ['rgw_zonegroup', zonegroup_name],
+ ['rgw_zone', zone_name]
+ ]
+
+ for (key, value) in KMS_CONFIG:
+ if value == 'null':
+ continue
+ CephService.send_command('mon', 'config set',
+ who=name_to_config_section(full_daemon_name),
+ name=key, value=value)
+ return {}
+
+ @classmethod
+ def get_realm_tokens(cls):
+ tokens_info = mgr.remote('rgw', 'get_realm_tokens')
+ return tokens_info
+
+ @classmethod
+ def import_realm_token(cls, realm_token, zone_name):
+ tokens_info = mgr.remote('rgw', 'import_realm_token', zone_name=zone_name,
+ realm_token=realm_token, start_radosgw=True)
+ return tokens_info
+
+ @classmethod
def get_pool_pg_status(cls, pool_name):
# type: (str) -> dict
pool = cls.get_pool_by_attribute('pool_name', pool_name)
diff --git a/src/pybind/mgr/dashboard/services/rgw_client.py b/src/pybind/mgr/dashboard/services/rgw_client.py
index 648afb13339..707612acf83 100644
--- a/src/pybind/mgr/dashboard/services/rgw_client.py
+++ b/src/pybind/mgr/dashboard/services/rgw_client.py
@@ -8,7 +8,6 @@ import json
import logging
import os
import re
-import subprocess
import xml.etree.ElementTree as ET # noqa: N814
from subprocess import SubprocessError
@@ -594,6 +593,355 @@ class RgwClient(RestClient):
return realm_info['name']
return None
+ @RestClient.api_get('/{bucket_name}?versioning')
+ def get_bucket_versioning(self, bucket_name, request=None):
+ """
+ Get bucket versioning.
+ :param str bucket_name: the name of the bucket.
+ :return: versioning info
+ :rtype: Dict
+ """
+ # pylint: disable=unused-argument
+ result = request()
+ if 'Status' not in result:
+ result['Status'] = 'Suspended'
+ if 'MfaDelete' not in result:
+ result['MfaDelete'] = 'Disabled'
+ return result
+
+ @RestClient.api_put('/{bucket_name}?versioning')
+ def set_bucket_versioning(self, bucket_name, versioning_state, mfa_delete,
+ mfa_token_serial, mfa_token_pin, request=None):
+ """
+ Set bucket versioning.
+ :param str bucket_name: the name of the bucket.
+ :param str versioning_state:
+ https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTVersioningStatus.html
+ :param str mfa_delete: MFA Delete state.
+ :param str mfa_token_serial:
+ https://docs.ceph.com/docs/master/radosgw/mfa/
+ :param str mfa_token_pin: value of a TOTP token at a certain time (auth code)
+ :return: None
+ """
+ # pylint: disable=unused-argument
+ versioning_configuration = ET.Element('VersioningConfiguration')
+ status_element = ET.SubElement(versioning_configuration, 'Status')
+ status_element.text = versioning_state
+
+ headers = {}
+ if mfa_delete and mfa_token_serial and mfa_token_pin:
+ headers['x-amz-mfa'] = '{} {}'.format(mfa_token_serial, mfa_token_pin)
+ mfa_delete_element = ET.SubElement(versioning_configuration, 'MfaDelete')
+ mfa_delete_element.text = mfa_delete
+
+ data = ET.tostring(versioning_configuration, encoding='unicode')
+
+ try:
+ request(data=data, headers=headers)
+ except RequestException as error:
+ msg = str(error)
+ if mfa_delete and mfa_token_serial and mfa_token_pin \
+ and 'AccessDenied' in error.content.decode():
+ msg = 'Bad MFA credentials: {}'.format(msg)
+ raise DashboardException(msg=msg,
+ http_status_code=error.status_code,
+ component='rgw')
+
+ @RestClient.api_get('/{bucket_name}?encryption')
+ def get_bucket_encryption(self, bucket_name, request=None):
+ # pylint: disable=unused-argument
+ try:
+ result = request() # type: ignore
+ result['Status'] = 'Enabled'
+ return result
+ except RequestException as e:
+ if e.content:
+ content = json_str_to_object(e.content)
+ if content.get(
+ 'Code') == 'ServerSideEncryptionConfigurationNotFoundError':
+ return {
+ 'Status': 'Disabled',
+ }
+ raise e
+
+ @RestClient.api_delete('/{bucket_name}?encryption')
+ def delete_bucket_encryption(self, bucket_name, request=None):
+ # pylint: disable=unused-argument
+ result = request() # type: ignore
+ return result
+
+ @RestClient.api_put('/{bucket_name}?encryption')
+ def set_bucket_encryption(self, bucket_name, key_id,
+ sse_algorithm, request: Optional[object] = None):
+ # pylint: disable=unused-argument
+ encryption_configuration = ET.Element('ServerSideEncryptionConfiguration')
+ rule_element = ET.SubElement(encryption_configuration, 'Rule')
+ default_encryption_element = ET.SubElement(rule_element,
+ 'ApplyServerSideEncryptionByDefault')
+ sse_algo_element = ET.SubElement(default_encryption_element,
+ 'SSEAlgorithm')
+ sse_algo_element.text = sse_algorithm
+ if sse_algorithm == 'aws:kms':
+ kms_master_key_element = ET.SubElement(default_encryption_element,
+ 'KMSMasterKeyID')
+ kms_master_key_element.text = key_id
+ data = ET.tostring(encryption_configuration, encoding='unicode')
+ try:
+ _ = request(data=data) # type: ignore
+ except RequestException as e:
+ raise DashboardException(msg=str(e), component='rgw')
+
+ @RestClient.api_get('/{bucket_name}?object-lock')
+ def get_bucket_locking(self, bucket_name, request=None):
+ # type: (str, Optional[object]) -> dict
+ """
+ Gets the locking configuration for a bucket. The locking
+ configuration will be applied by default to every new object
+ placed in the specified bucket.
+ :param bucket_name: The name of the bucket.
+ :type bucket_name: str
+ :return: The locking configuration.
+ :rtype: Dict
+ """
+ # pylint: disable=unused-argument
+
+ # Try to get the Object Lock configuration. If there is none,
+ # then return default values.
+ try:
+ result = request() # type: ignore
+ return {
+ 'lock_enabled': dict_get(result, 'ObjectLockEnabled') == 'Enabled',
+ 'lock_mode': dict_get(result, 'Rule.DefaultRetention.Mode'),
+ 'lock_retention_period_days': dict_get(result, 'Rule.DefaultRetention.Days', 0),
+ 'lock_retention_period_years': dict_get(result, 'Rule.DefaultRetention.Years', 0)
+ }
+ except RequestException as e:
+ if e.content:
+ content = json_str_to_object(e.content)
+ if content.get(
+ 'Code') == 'ObjectLockConfigurationNotFoundError':
+ return {
+ 'lock_enabled': False,
+ 'lock_mode': 'compliance',
+ 'lock_retention_period_days': None,
+ 'lock_retention_period_years': None
+ }
+ raise e
+
+ @RestClient.api_put('/{bucket_name}?object-lock')
+ def set_bucket_locking(self,
+ bucket_name: str,
+ mode: str,
+ retention_period_days: Optional[Union[int, str]] = None,
+ retention_period_years: Optional[Union[int, str]] = None,
+ request: Optional[object] = None) -> None:
+ """
+ Places the locking configuration on the specified bucket. The
+ locking configuration will be applied by default to every new
+ object placed in the specified bucket.
+ :param bucket_name: The name of the bucket.
+ :type bucket_name: str
+ :param mode: The lock mode, e.g. `COMPLIANCE` or `GOVERNANCE`.
+ :type mode: str
+ :param retention_period_days:
+ :type retention_period_days: int
+ :param retention_period_years:
+ :type retention_period_years: int
+ :rtype: None
+ """
+ # pylint: disable=unused-argument
+
+ retention_period_days, retention_period_years = self.perform_validations(
+ retention_period_days, retention_period_years, mode)
+
+ # Generate the XML data like this:
+ # <ObjectLockConfiguration>
+ # <ObjectLockEnabled>string</ObjectLockEnabled>
+ # <Rule>
+ # <DefaultRetention>
+ # <Days>integer</Days>
+ # <Mode>string</Mode>
+ # <Years>integer</Years>
+ # </DefaultRetention>
+ # </Rule>
+ # </ObjectLockConfiguration>
+ locking_configuration = ET.Element('ObjectLockConfiguration')
+ enabled_element = ET.SubElement(locking_configuration,
+ 'ObjectLockEnabled')
+ enabled_element.text = 'Enabled' # Locking can't be disabled.
+ rule_element = ET.SubElement(locking_configuration, 'Rule')
+ default_retention_element = ET.SubElement(rule_element,
+ 'DefaultRetention')
+ mode_element = ET.SubElement(default_retention_element, 'Mode')
+ mode_element.text = mode.upper()
+ if retention_period_days:
+ days_element = ET.SubElement(default_retention_element, 'Days')
+ days_element.text = str(retention_period_days)
+ if retention_period_years:
+ years_element = ET.SubElement(default_retention_element, 'Years')
+ years_element.text = str(retention_period_years)
+
+ data = ET.tostring(locking_configuration, encoding='unicode')
+
+ try:
+ _ = request(data=data) # type: ignore
+ except RequestException as e:
+ raise DashboardException(msg=str(e), component='rgw')
+
+ def list_roles(self) -> List[Dict[str, Any]]:
+ rgw_list_roles_command = ['role', 'list']
+ code, roles, err = mgr.send_rgwadmin_command(rgw_list_roles_command)
+ if code < 0:
+ logger.warning('Error listing roles with code %d: %s', code, err)
+ return []
+
+ return roles
+
+ def create_role(self, role_name: str, role_path: str, role_assume_policy_doc: str) -> None:
+ try:
+ json.loads(role_assume_policy_doc)
+ except: # noqa: E722
+ raise DashboardException('Assume role policy document is not a valid json')
+
+ # valid values:
+ # pylint: disable=C0301
+ # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path # noqa: E501
+ if len(role_name) > 64:
+ raise DashboardException(
+ f'Role name "{role_name}" is invalid. Should be 64 characters or less')
+
+ role_name_regex = '[0-9a-zA-Z_+=,.@-]+'
+ if not re.fullmatch(role_name_regex, role_name):
+ raise DashboardException(
+ f'Role name "{role_name}" is invalid. Valid characters are "{role_name_regex}"')
+
+ if not os.path.isabs(role_path):
+ raise DashboardException(
+ f'Role path "{role_path}" is invalid. It should be an absolute path')
+ if role_path[-1] != '/':
+ raise DashboardException(
+ f'Role path "{role_path}" is invalid. It should start and end with a slash')
+ path_regex = '(\u002F)|(\u002F[\u0021-\u007E]+\u002F)'
+ if not re.fullmatch(path_regex, role_path):
+ raise DashboardException(
+ (f'Role path "{role_path}" is invalid.'
+ f'Role path should follow the pattern "{path_regex}"'))
+
+ rgw_create_role_command = ['role', 'create', '--role-name', role_name, '--path', role_path]
+ if role_assume_policy_doc:
+ rgw_create_role_command += ['--assume-role-policy-doc', f"{role_assume_policy_doc}"]
+
+ code, _roles, _err = mgr.send_rgwadmin_command(rgw_create_role_command,
+ stdout_as_json=False)
+ if code != 0:
+ # pylint: disable=C0301
+ link = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path' # noqa: E501
+ msg = (f'Error creating role with code {code}: '
+ 'Looks like the document has a wrong format.'
+ f' For more information about the format look at {link}')
+ raise DashboardException(msg=msg, component='rgw')
+
+ def perform_validations(self, retention_period_days, retention_period_years, mode):
+ try:
+ retention_period_days = int(retention_period_days) if retention_period_days else 0
+ retention_period_years = int(retention_period_years) if retention_period_years else 0
+ if retention_period_days < 0 or retention_period_years < 0:
+ raise ValueError
+ except (TypeError, ValueError):
+ msg = "Retention period must be a positive integer."
+ raise DashboardException(msg=msg, component='rgw')
+ if retention_period_days and retention_period_years:
+ # https://docs.aws.amazon.com/AmazonS3/latest/API/archive-RESTBucketPUTObjectLockConfiguration.html
+ msg = "Retention period requires either Days or Years. "\
+ "You can't specify both at the same time."
+ raise DashboardException(msg=msg, component='rgw')
+ if not retention_period_days and not retention_period_years:
+ msg = "Retention period requires either Days or Years. "\
+ "You must specify at least one."
+ raise DashboardException(msg=msg, component='rgw')
+ if not isinstance(mode, str) or mode.upper() not in ['COMPLIANCE', 'GOVERNANCE']:
+ msg = "Retention mode must be either COMPLIANCE or GOVERNANCE."
+ raise DashboardException(msg=msg, component='rgw')
+ return retention_period_days, retention_period_years
+
+
+class RgwMultisite:
+ def migrate_to_multisite(self, realm_name: str, zonegroup_name: str, zone_name: str,
+ zonegroup_endpoints: str, zone_endpoints: str, access_key: str,
+ secret_key: str):
+ rgw_realm_create_cmd = ['realm', 'create', '--rgw-realm', realm_name, '--default']
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to create realm',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default',
+ '--zonegroup-new-name', zonegroup_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone',
+ 'default', '--zone-new-name', zone_name,
+ '--rgw-zonegroup', zonegroup_name]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(zone_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
+ '--rgw-realm', realm_name,
+ '--rgw-zonegroup', zonegroup_name]
+ if zonegroup_endpoints:
+ rgw_zonegroup_modify_cmd.append('--endpoints')
+ rgw_zonegroup_modify_cmd.append(zonegroup_endpoints)
+ rgw_zonegroup_modify_cmd.append('--master')
+ rgw_zonegroup_modify_cmd.append('--default')
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-realm', realm_name,
+ '--rgw-zonegroup', zonegroup_name,
+ '--rgw-zone', zone_name]
+ if zone_endpoints:
+ rgw_zone_modify_cmd.append('--endpoints')
+ rgw_zone_modify_cmd.append(zone_endpoints)
+ rgw_zone_modify_cmd.append('--master')
+ rgw_zone_modify_cmd.append('--default')
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zone',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
+ if access_key and secret_key:
+ rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name,
+ '--access-key', access_key, '--secret', secret_key]
+ try:
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
+ if exit_code > 0:
+ raise DashboardException(e=err, msg='Unable to modify zone',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
def create_realm(self, realm_name: str, default: bool):
rgw_realm_create_cmd = ['realm', 'create']
cmd_create_realm_options = ['--rgw-realm', realm_name]
@@ -652,26 +1000,6 @@ class RgwClient(RestClient):
all_realms_info['default_realm'] = '' # type: ignore
return all_realms_info
- def delete_realm(self, realm_name: str):
- rgw_delete_realm_cmd = ['realm', 'rm', '--rgw-realm', realm_name]
- try:
- exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_realm_cmd)
- if exit_code > 0:
- raise DashboardException(msg='Unable to delete realm',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- def update_period(self):
- rgw_update_period_cmd = ['period', 'update', '--commit']
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_update_period_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to update period',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
def edit_realm(self, realm_name: str, new_realm_name: str, default: str = ''):
rgw_realm_edit_cmd = []
if new_realm_name != realm_name:
@@ -694,8 +1022,18 @@ class RgwClient(RestClient):
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
+ def delete_realm(self, realm_name: str):
+ rgw_delete_realm_cmd = ['realm', 'rm', '--rgw-realm', realm_name]
+ try:
+ exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_realm_cmd)
+ if exit_code > 0:
+ raise DashboardException(msg='Unable to delete realm',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+
def create_zonegroup(self, realm_name: str, zonegroup_name: str,
- default: bool, master: bool, endpoints: List[str]):
+ default: bool, master: bool, endpoints: str):
rgw_zonegroup_create_cmd = ['zonegroup', 'create']
cmd_create_zonegroup_options = ['--rgw-zonegroup', zonegroup_name]
if realm_name != 'null':
@@ -705,13 +1043,9 @@ class RgwClient(RestClient):
cmd_create_zonegroup_options.append('--default')
if master != 'false':
cmd_create_zonegroup_options.append('--master')
- if endpoints != 'null': # type: ignore
- if isinstance(endpoints, list) and len(endpoints) > 1:
- endpoint = ','.join(endpoints)
- else:
- endpoint = endpoints # type: ignore
+ if endpoints:
cmd_create_zonegroup_options.append('--endpoints')
- cmd_create_zonegroup_options.append(endpoint)
+ cmd_create_zonegroup_options.append(endpoints)
rgw_zonegroup_create_cmd += cmd_create_zonegroup_options
try:
exit_code, out, err = mgr.send_rgwadmin_command(rgw_zonegroup_create_cmd)
@@ -722,19 +1056,79 @@ class RgwClient(RestClient):
raise DashboardException(error, http_status_code=500, component='rgw')
return out
+ def list_zonegroups(self):
+ rgw_zonegroup_list = {}
+ rgw_zonegroup_list_cmd = ['zonegroup', 'list']
+ try:
+ exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_list_cmd)
+ if exit_code > 0:
+ raise DashboardException(msg='Unable to fetch zonegroup list',
+ http_status_code=500, component='rgw')
+ rgw_zonegroup_list = out
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ return rgw_zonegroup_list
+
+ def get_zonegroup(self, zonegroup_name: str):
+ zonegroup_info = {}
+ if zonegroup_name != 'default':
+ rgw_zonegroup_info_cmd = ['zonegroup', 'get', '--rgw-zonegroup', zonegroup_name]
+ else:
+ rgw_zonegroup_info_cmd = ['zonegroup', 'get', '--rgw-zonegroup',
+ zonegroup_name, '--rgw-realm', 'default']
+ try:
+ exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_info_cmd)
+ if exit_code > 0:
+ raise DashboardException('Unable to get zonegroup info',
+ http_status_code=500, component='rgw')
+ zonegroup_info = out
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ return zonegroup_info
+
+ def get_all_zonegroups_info(self):
+ all_zonegroups_info = {}
+ zonegroups_info = []
+ rgw_zonegroup_list = self.list_zonegroups()
+ if 'zonegroups' in rgw_zonegroup_list:
+ if rgw_zonegroup_list['zonegroups'] != []:
+ for rgw_zonegroup in rgw_zonegroup_list['zonegroups']:
+ zonegroup_info = self.get_zonegroup(rgw_zonegroup)
+ zonegroups_info.append(zonegroup_info)
+ all_zonegroups_info['zonegroups'] = zonegroups_info # type: ignore
+ else:
+ all_zonegroups_info['zonegroups'] = [] # type: ignore
+ if 'default_info' in rgw_zonegroup_list and rgw_zonegroup_list['default_info'] != '':
+ all_zonegroups_info['default_zonegroup'] = rgw_zonegroup_list['default_info']
+ else:
+ all_zonegroups_info['default_zonegroup'] = '' # type: ignore
+ return all_zonegroups_info
+
+ def delete_zonegroup(self, zonegroup_name: str, delete_pools: str, pools: List[str]):
+ if delete_pools == 'true':
+ zonegroup_info = self.get_zonegroup(zonegroup_name)
+ rgw_delete_zonegroup_cmd = ['zonegroup', 'delete', '--rgw-zonegroup', zonegroup_name]
+ try:
+ exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_zonegroup_cmd)
+ if exit_code > 0:
+ raise DashboardException(msg='Unable to delete zonegroup',
+ http_status_code=500, component='rgw')
+ except SubprocessError as error:
+ raise DashboardException(error, http_status_code=500, component='rgw')
+ self.update_period()
+ if delete_pools == 'true':
+ for zone in zonegroup_info['zones']:
+ self.delete_zone(zone['name'], 'true', pools)
+
def modify_zonegroup(self, realm_name: str, zonegroup_name: str, default: str, master: str,
- endpoints: List[str]):
- if realm_name:
- rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
- '--rgw-realm', realm_name,
- '--rgw-zonegroup', zonegroup_name]
+ endpoints: str):
+
+ rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
+ '--rgw-realm', realm_name,
+ '--rgw-zonegroup', zonegroup_name]
if endpoints:
- if len(endpoints) > 1:
- endpoint = ','.join(str(e) for e in endpoints)
- else:
- endpoint = endpoints[0]
rgw_zonegroup_modify_cmd.append('--endpoints')
- rgw_zonegroup_modify_cmd.append(endpoint)
+ rgw_zonegroup_modify_cmd.append(endpoints)
if master and str_to_bool(master):
rgw_zonegroup_modify_cmd.append('--master')
if default and str_to_bool(default):
@@ -852,7 +1246,7 @@ class RgwClient(RestClient):
# pylint: disable=W0102
def edit_zonegroup(self, realm_name: str, zonegroup_name: str, new_zonegroup_name: str,
- default: str = '', master: str = '', endpoints: List[str] = [],
+ default: str = '', master: str = '', endpoints: str = '',
add_zones: List[str] = [], remove_zones: List[str] = [],
placement_targets: List[Dict[str, str]] = []):
rgw_zonegroup_edit_cmd = []
@@ -883,73 +1277,18 @@ class RgwClient(RestClient):
else:
self.add_placement_targets(new_zonegroup_name, placement_targets)
- def list_zonegroups(self):
- rgw_zonegroup_list = {}
- rgw_zonegroup_list_cmd = ['zonegroup', 'list']
- try:
- exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_list_cmd)
- if exit_code > 0:
- raise DashboardException(msg='Unable to fetch zonegroup list',
- http_status_code=500, component='rgw')
- rgw_zonegroup_list = out
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- return rgw_zonegroup_list
-
- def get_zonegroup(self, zonegroup_name: str):
- zonegroup_info = {}
- rgw_zonegroup_info_cmd = ['zonegroup', 'get', '--rgw-zonegroup', zonegroup_name]
- try:
- exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_info_cmd)
- if exit_code > 0:
- raise DashboardException('Unable to get zonegroup info',
- http_status_code=500, component='rgw')
- zonegroup_info = out
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- return zonegroup_info
-
- def get_all_zonegroups_info(self):
- all_zonegroups_info = {}
- zonegroups_info = []
- rgw_zonegroup_list = self.list_zonegroups()
- if 'zonegroups' in rgw_zonegroup_list:
- if rgw_zonegroup_list['zonegroups'] != []:
- for rgw_zonegroup in rgw_zonegroup_list['zonegroups']:
- zonegroup_info = self.get_zonegroup(rgw_zonegroup)
- zonegroups_info.append(zonegroup_info)
- all_zonegroups_info['zonegroups'] = zonegroups_info # type: ignore
- else:
- all_zonegroups_info['zonegroups'] = [] # type: ignore
- if 'default_info' in rgw_zonegroup_list and rgw_zonegroup_list['default_info'] != '':
- all_zonegroups_info['default_zonegroup'] = rgw_zonegroup_list['default_info']
- else:
- all_zonegroups_info['default_zonegroup'] = '' # type: ignore
- return all_zonegroups_info
-
- def delete_zonegroup(self, zonegroup_name: str, delete_pools: str, pools: List[str]):
- if delete_pools == 'true':
- zonegroup_info = self.get_zonegroup(zonegroup_name)
- rgw_delete_zonegroup_cmd = ['zonegroup', 'delete', '--rgw-zonegroup', zonegroup_name]
+ def update_period(self):
+ rgw_update_period_cmd = ['period', 'update', '--commit']
try:
- exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_zonegroup_cmd)
+ exit_code, _, err = mgr.send_rgwadmin_command(rgw_update_period_cmd)
if exit_code > 0:
- raise DashboardException(msg='Unable to delete zonegroup',
+ raise DashboardException(e=err, msg='Unable to update period',
http_status_code=500, component='rgw')
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
- self.update_period()
- if delete_pools == 'true':
- for zone in zonegroup_info['zones']:
- self.delete_zone(zone['name'], 'true', pools)
- def create_zone(self, zone_name, zonegroup_name, default, master, endpoints, user,
- createSystemUser, master_zone_of_master_zonegroup):
- if user != 'null':
- access_key, secret_key = self.get_rgw_user_keys(user, master_zone_of_master_zonegroup)
- else:
- access_key = None # type: ignore
- secret_key = None # type: ignore
+ def create_zone(self, zone_name, zonegroup_name, default, master, endpoints, access_key,
+ secret_key):
rgw_zone_create_cmd = ['zone', 'create']
cmd_create_zone_options = ['--rgw-zone', zone_name]
if zonegroup_name != 'null':
@@ -978,36 +1317,8 @@ class RgwClient(RestClient):
raise DashboardException(error, http_status_code=500, component='rgw')
self.update_period()
-
- if createSystemUser == 'true':
- self.create_system_user(user, zone_name)
- access_key, secret_key = self.get_rgw_user_keys(user, zone_name)
- rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name,
- '--access-key', access_key, '--secret', secret_key]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zone',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- self.update_period()
-
return out
- def get_rgw_user_keys(self, user, zone_name):
- access_key = ''
- secret_key = ''
- rgw_user_info_cmd = ['user', 'info', '--uid', user, '--rgw-zone', zone_name]
- try:
- _, out, _ = mgr.send_rgwadmin_command(rgw_user_info_cmd)
- if out:
- access_key, secret_key = self.parse_secrets(user, out)
- except SubprocessError as error:
- logger.exception(error)
-
- return access_key, secret_key
-
def parse_secrets(self, user, data):
for key in data.get('keys', []):
if key.get('user') == user:
@@ -1017,21 +1328,12 @@ class RgwClient(RestClient):
return '', ''
def modify_zone(self, zone_name: str, zonegroup_name: str, default: str, master: str,
- endpoints: List[str], user: str, master_zone_of_master_zonegroup):
- if user:
- access_key, secret_key = self.get_rgw_user_keys(user, master_zone_of_master_zonegroup)
- else:
- access_key = None
- secret_key = None
+ endpoints: str, access_key: str, secret_key: str):
rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zonegroup',
zonegroup_name, '--rgw-zone', zone_name]
if endpoints:
- if len(endpoints) > 1:
- endpoint = ','.join(str(e) for e in endpoints)
- else:
- endpoint = endpoints[0]
rgw_zone_modify_cmd.append('--endpoints')
- rgw_zone_modify_cmd.append(endpoint)
+ rgw_zone_modify_cmd.append(endpoints)
if default and str_to_bool(default):
rgw_zone_modify_cmd.append('--default')
if master and str_to_bool(master):
@@ -1083,10 +1385,10 @@ class RgwClient(RestClient):
self.update_period()
def edit_zone(self, zone_name: str, new_zone_name: str, zonegroup_name: str, default: str = '',
- master: str = '', endpoints: List[str] = [], user: str = '',
+ 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 = '', master_zone_of_master_zonegroup=None):
+ compression: str = ''):
if new_zone_name != zone_name:
rgw_zone_rename_cmd = ['zone', 'rename', '--rgw-zone',
zone_name, '--zone-new-name', new_zone_name]
@@ -1098,8 +1400,8 @@ class RgwClient(RestClient):
except SubprocessError as error:
raise DashboardException(error, http_status_code=500, component='rgw')
self.update_period()
- self.modify_zone(new_zone_name, zonegroup_name, default, master, endpoints, user,
- master_zone_of_master_zonegroup)
+ self.modify_zone(new_zone_name, zonegroup_name, default, master, endpoints, access_key,
+ secret_key)
self.add_placement_targets_zone(new_zone_name, placement_target,
data_pool, index_pool, data_extra_pool)
self.add_storage_class_zone(new_zone_name, placement_target, storage_class,
@@ -1179,29 +1481,6 @@ class RgwClient(RestClient):
if mgr.rados.pool_exists(pool):
mgr.rados.delete_pool(pool)
- def get_multisite_status(self):
- is_multisite_configured = True
- 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:
- is_multisite_configured = False
- return is_multisite_configured
-
- def get_multisite_sync_status(self):
- sync_status = ''
- rgw_sync_status_cmd = ['sync', 'status']
- try:
- exit_code, out, _ = mgr.send_rgwadmin_command(rgw_sync_status_cmd, False)
- if exit_code > 0:
- raise DashboardException('Unable to get sync status',
- http_status_code=500, component='rgw')
- sync_status = out
- except subprocess.TimeoutExpired:
- sync_status = 'Timeout Expired'
- return sync_status
-
def create_system_user(self, userName: str, zoneName: str):
rgw_user_create_cmd = ['user', 'create', '--uid', userName,
'--display-name', userName, '--rgw-zone', zoneName, '--system']
@@ -1240,358 +1519,12 @@ class RgwClient(RestClient):
raise DashboardException(error, http_status_code=500, component='rgw')
return all_users_info
- def migrate_to_multisite(self, realm_name: str, zonegroup_name: str, zone_name: str,
- zonegroup_endpoints: List[str], zone_endpoints: List[str], user: str):
- rgw_realm_create_cmd = ['realm', 'create', '--rgw-realm', realm_name, '--default']
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to create realm',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default',
- '--zonegroup-new-name', zonegroup_name]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone',
- 'default', '--zone-new-name', zone_name,
- '--rgw-zonegroup', zonegroup_name]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(zone_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
- '--rgw-realm', realm_name,
- '--rgw-zonegroup', zonegroup_name]
- if zonegroup_endpoints:
- if len(zonegroup_endpoints) > 1:
- endpoint = ','.join(str(e) for e in zonegroup_endpoints)
- else:
- endpoint = zonegroup_endpoints[0]
- rgw_zonegroup_modify_cmd.append('--endpoints')
- rgw_zonegroup_modify_cmd.append(endpoint)
- rgw_zonegroup_modify_cmd.append('--master')
- rgw_zonegroup_modify_cmd.append('--default')
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name), # noqa E501 #pylint: disable=line-too-long
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-realm', realm_name,
- '--rgw-zonegroup', zonegroup_name,
- '--rgw-zone', zone_name]
- if zone_endpoints:
- if len(zone_endpoints) > 1:
- endpoint = ','.join(str(e) for e in zone_endpoints)
- else:
- endpoint = zone_endpoints[0]
- rgw_zone_modify_cmd.append('--endpoints')
- rgw_zone_modify_cmd.append(endpoint)
- rgw_zone_modify_cmd.append('--master')
- rgw_zone_modify_cmd.append('--default')
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zone',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
-
- if user:
- access_key, secret_key = self.get_rgw_user_keys(user, zone_name)
- rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name,
- '--access-key', access_key, '--secret', secret_key]
- try:
- exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
- if exit_code > 0:
- raise DashboardException(e=err, msg='Unable to modify zone',
- http_status_code=500, component='rgw')
- except SubprocessError as error:
- raise DashboardException(error, http_status_code=500, component='rgw')
- self.update_period()
-
- @RestClient.api_get('/{bucket_name}?versioning')
- def get_bucket_versioning(self, bucket_name, request=None):
- """
- Get bucket versioning.
- :param str bucket_name: the name of the bucket.
- :return: versioning info
- :rtype: Dict
- """
- # pylint: disable=unused-argument
- result = request()
- if 'Status' not in result:
- result['Status'] = 'Suspended'
- if 'MfaDelete' not in result:
- result['MfaDelete'] = 'Disabled'
- return result
-
- @RestClient.api_put('/{bucket_name}?versioning')
- def set_bucket_versioning(self, bucket_name, versioning_state, mfa_delete,
- mfa_token_serial, mfa_token_pin, request=None):
- """
- Set bucket versioning.
- :param str bucket_name: the name of the bucket.
- :param str versioning_state:
- https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTVersioningStatus.html
- :param str mfa_delete: MFA Delete state.
- :param str mfa_token_serial:
- https://docs.ceph.com/docs/master/radosgw/mfa/
- :param str mfa_token_pin: value of a TOTP token at a certain time (auth code)
- :return: None
- """
- # pylint: disable=unused-argument
- versioning_configuration = ET.Element('VersioningConfiguration')
- status_element = ET.SubElement(versioning_configuration, 'Status')
- status_element.text = versioning_state
-
- headers = {}
- if mfa_delete and mfa_token_serial and mfa_token_pin:
- headers['x-amz-mfa'] = '{} {}'.format(mfa_token_serial, mfa_token_pin)
- mfa_delete_element = ET.SubElement(versioning_configuration, 'MfaDelete')
- mfa_delete_element.text = mfa_delete
-
- data = ET.tostring(versioning_configuration, encoding='unicode')
-
- try:
- request(data=data, headers=headers)
- except RequestException as error:
- msg = str(error)
- if mfa_delete and mfa_token_serial and mfa_token_pin \
- and 'AccessDenied' in error.content.decode():
- msg = 'Bad MFA credentials: {}'.format(msg)
- raise DashboardException(msg=msg,
- http_status_code=error.status_code,
- component='rgw')
-
- @RestClient.api_get('/{bucket_name}?encryption')
- def get_bucket_encryption(self, bucket_name, request=None):
- # pylint: disable=unused-argument
- try:
- result = request() # type: ignore
- result['Status'] = 'Enabled'
- return result
- except RequestException as e:
- if e.content:
- content = json_str_to_object(e.content)
- if content.get(
- 'Code') == 'ServerSideEncryptionConfigurationNotFoundError':
- return {
- 'Status': 'Disabled',
- }
- raise e
-
- @RestClient.api_delete('/{bucket_name}?encryption')
- def delete_bucket_encryption(self, bucket_name, request=None):
- # pylint: disable=unused-argument
- result = request() # type: ignore
- return result
-
- @RestClient.api_put('/{bucket_name}?encryption')
- def set_bucket_encryption(self, bucket_name, key_id,
- sse_algorithm, request: Optional[object] = None):
- # pylint: disable=unused-argument
- encryption_configuration = ET.Element('ServerSideEncryptionConfiguration')
- rule_element = ET.SubElement(encryption_configuration, 'Rule')
- default_encryption_element = ET.SubElement(rule_element,
- 'ApplyServerSideEncryptionByDefault')
- sse_algo_element = ET.SubElement(default_encryption_element,
- 'SSEAlgorithm')
- sse_algo_element.text = sse_algorithm
- if sse_algorithm == 'aws:kms':
- kms_master_key_element = ET.SubElement(default_encryption_element,
- 'KMSMasterKeyID')
- kms_master_key_element.text = key_id
- data = ET.tostring(encryption_configuration, encoding='unicode')
- try:
- _ = request(data=data) # type: ignore
- except RequestException as e:
- raise DashboardException(msg=str(e), component='rgw')
-
- @RestClient.api_get('/{bucket_name}?object-lock')
- def get_bucket_locking(self, bucket_name, request=None):
- # type: (str, Optional[object]) -> dict
- """
- Gets the locking configuration for a bucket. The locking
- configuration will be applied by default to every new object
- placed in the specified bucket.
- :param bucket_name: The name of the bucket.
- :type bucket_name: str
- :return: The locking configuration.
- :rtype: Dict
- """
- # pylint: disable=unused-argument
-
- # Try to get the Object Lock configuration. If there is none,
- # then return default values.
- try:
- result = request() # type: ignore
- return {
- 'lock_enabled': dict_get(result, 'ObjectLockEnabled') == 'Enabled',
- 'lock_mode': dict_get(result, 'Rule.DefaultRetention.Mode'),
- 'lock_retention_period_days': dict_get(result, 'Rule.DefaultRetention.Days', 0),
- 'lock_retention_period_years': dict_get(result, 'Rule.DefaultRetention.Years', 0)
- }
- except RequestException as e:
- if e.content:
- content = json_str_to_object(e.content)
- if content.get(
- 'Code') == 'ObjectLockConfigurationNotFoundError':
- return {
- 'lock_enabled': False,
- 'lock_mode': 'compliance',
- 'lock_retention_period_days': None,
- 'lock_retention_period_years': None
- }
- raise e
-
- @RestClient.api_put('/{bucket_name}?object-lock')
- def set_bucket_locking(self,
- bucket_name: str,
- mode: str,
- retention_period_days: Optional[Union[int, str]] = None,
- retention_period_years: Optional[Union[int, str]] = None,
- request: Optional[object] = None) -> None:
- """
- Places the locking configuration on the specified bucket. The
- locking configuration will be applied by default to every new
- object placed in the specified bucket.
- :param bucket_name: The name of the bucket.
- :type bucket_name: str
- :param mode: The lock mode, e.g. `COMPLIANCE` or `GOVERNANCE`.
- :type mode: str
- :param retention_period_days:
- :type retention_period_days: int
- :param retention_period_years:
- :type retention_period_years: int
- :rtype: None
- """
- # pylint: disable=unused-argument
-
- retention_period_days, retention_period_years = self.perform_validations(
- retention_period_days, retention_period_years, mode)
-
- # Generate the XML data like this:
- # <ObjectLockConfiguration>
- # <ObjectLockEnabled>string</ObjectLockEnabled>
- # <Rule>
- # <DefaultRetention>
- # <Days>integer</Days>
- # <Mode>string</Mode>
- # <Years>integer</Years>
- # </DefaultRetention>
- # </Rule>
- # </ObjectLockConfiguration>
- locking_configuration = ET.Element('ObjectLockConfiguration')
- enabled_element = ET.SubElement(locking_configuration,
- 'ObjectLockEnabled')
- enabled_element.text = 'Enabled' # Locking can't be disabled.
- rule_element = ET.SubElement(locking_configuration, 'Rule')
- default_retention_element = ET.SubElement(rule_element,
- 'DefaultRetention')
- mode_element = ET.SubElement(default_retention_element, 'Mode')
- mode_element.text = mode.upper()
- if retention_period_days:
- days_element = ET.SubElement(default_retention_element, 'Days')
- days_element.text = str(retention_period_days)
- if retention_period_years:
- years_element = ET.SubElement(default_retention_element, 'Years')
- years_element.text = str(retention_period_years)
-
- data = ET.tostring(locking_configuration, encoding='unicode')
-
- try:
- _ = request(data=data) # type: ignore
- except RequestException as e:
- raise DashboardException(msg=str(e), component='rgw')
-
- def list_roles(self) -> List[Dict[str, Any]]:
- rgw_list_roles_command = ['role', 'list']
- code, roles, err = mgr.send_rgwadmin_command(rgw_list_roles_command)
- if code < 0:
- logger.warning('Error listing roles with code %d: %s', code, err)
- return []
-
- return roles
-
- def create_role(self, role_name: str, role_path: str, role_assume_policy_doc: str) -> None:
- try:
- json.loads(role_assume_policy_doc)
- except: # noqa: E722
- raise DashboardException('Assume role policy document is not a valid json')
-
- # valid values:
- # pylint: disable=C0301
- # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path # noqa: E501
- if len(role_name) > 64:
- raise DashboardException(
- f'Role name "{role_name}" is invalid. Should be 64 characters or less')
-
- role_name_regex = '[0-9a-zA-Z_+=,.@-]+'
- if not re.fullmatch(role_name_regex, role_name):
- raise DashboardException(
- f'Role name "{role_name}" is invalid. Valid characters are "{role_name_regex}"')
-
- if not os.path.isabs(role_path):
- raise DashboardException(
- f'Role path "{role_path}" is invalid. It should be an absolute path')
- if role_path[-1] != '/':
- raise DashboardException(
- f'Role path "{role_path}" is invalid. It should start and end with a slash')
- path_regex = '(\u002F)|(\u002F[\u0021-\u007E]+\u002F)'
- if not re.fullmatch(path_regex, role_path):
- raise DashboardException(
- (f'Role path "{role_path}" is invalid.'
- f'Role path should follow the pattern "{path_regex}"'))
-
- rgw_create_role_command = ['role', 'create', '--role-name', role_name, '--path', role_path]
- if role_assume_policy_doc:
- rgw_create_role_command += ['--assume-role-policy-doc', f"{role_assume_policy_doc}"]
-
- code, _roles, _err = mgr.send_rgwadmin_command(rgw_create_role_command,
- stdout_as_json=False)
- if code != 0:
- # pylint: disable=C0301
- link = 'https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-role.html#cfn-iam-role-path' # noqa: E501
- msg = (f'Error creating role with code {code}: '
- 'Looks like the document has a wrong format.'
- f' For more information about the format look at {link}')
- raise DashboardException(msg=msg, component='rgw')
-
- def perform_validations(self, retention_period_days, retention_period_years, mode):
- try:
- retention_period_days = int(retention_period_days) if retention_period_days else 0
- retention_period_years = int(retention_period_years) if retention_period_years else 0
- if retention_period_days < 0 or retention_period_years < 0:
- raise ValueError
- except (TypeError, ValueError):
- msg = "Retention period must be a positive integer."
- raise DashboardException(msg=msg, component='rgw')
- if retention_period_days and retention_period_years:
- # https://docs.aws.amazon.com/AmazonS3/latest/API/archive-RESTBucketPUTObjectLockConfiguration.html
- msg = "Retention period requires either Days or Years. "\
- "You can't specify both at the same time."
- raise DashboardException(msg=msg, component='rgw')
- if not retention_period_days and not retention_period_years:
- msg = "Retention period requires either Days or Years. "\
- "You must specify at least one."
- raise DashboardException(msg=msg, component='rgw')
- if not isinstance(mode, str) or mode.upper() not in ['COMPLIANCE', 'GOVERNANCE']:
- msg = "Retention mode must be either COMPLIANCE or GOVERNANCE."
- raise DashboardException(msg=msg, component='rgw')
- return retention_period_days, retention_period_years
+ def get_multisite_status(self):
+ is_multisite_configured = True
+ 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:
+ is_multisite_configured = False
+ return is_multisite_configured
diff --git a/src/pybind/mgr/rgw/module.py b/src/pybind/mgr/rgw/module.py
index bb1f9460025..079e7e817ca 100644
--- a/src/pybind/mgr/rgw/module.py
+++ b/src/pybind/mgr/rgw/module.py
@@ -249,26 +249,30 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
@CLICommand('rgw realm tokens', perm='r')
def list_realm_tokens(self) -> HandleCommandResult:
try:
- realms_info = []
- for realm_info in RGWAM(self.env).get_realms_info():
- if not realm_info['master_zone_id']:
- realms_info.append({'realm': realm_info['realm_name'], 'token': 'realm has no master zone'})
- elif not realm_info['endpoint']:
- realms_info.append({'realm': realm_info['realm_name'], 'token': 'master zone has no endpoint'})
- elif not (realm_info['access_key'] and realm_info['secret']):
- realms_info.append({'realm': realm_info['realm_name'], 'token': 'master zone has no access/secret keys'})
- else:
- keys = ['realm_name', 'realm_id', 'endpoint', 'access_key', 'secret']
- realm_token = RealmToken(**{k: realm_info[k] for k in keys})
- realm_token_b = realm_token.to_json().encode('utf-8')
- realm_token_s = base64.b64encode(realm_token_b).decode('utf-8')
- realms_info.append({'realm': realm_info['realm_name'], 'token': realm_token_s})
+ realms_info = self.get_realm_tokens()
except RGWAMException as e:
self.log.error(f'cmd run exception: ({e.retcode}) {e.message}')
return HandleCommandResult(retval=e.retcode, stdout=e.stdout, stderr=e.stderr)
return HandleCommandResult(retval=0, stdout=json.dumps(realms_info, indent=4), stderr='')
+ def get_realm_tokens(self) -> List[Dict]:
+ realms_info = []
+ for realm_info in RGWAM(self.env).get_realms_info():
+ if not realm_info['master_zone_id']:
+ realms_info.append({'realm': realm_info['realm_name'], 'token': 'realm has no master zone'})
+ elif not realm_info['endpoint']:
+ realms_info.append({'realm': realm_info['realm_name'], 'token': 'master zone has no endpoint'})
+ elif not (realm_info['access_key'] and realm_info['secret']):
+ realms_info.append({'realm': realm_info['realm_name'], 'token': 'master zone has no access/secret keys'})
+ else:
+ keys = ['realm_name', 'realm_id', 'endpoint', 'access_key', 'secret']
+ realm_token = RealmToken(**{k: realm_info[k] for k in keys})
+ realm_token_b = realm_token.to_json().encode('utf-8')
+ realm_token_s = base64.b64encode(realm_token_b).decode('utf-8')
+ realms_info.append({'realm': realm_info['realm_name'], 'token': realm_token_s})
+ return realms_info
+
@CLICommand('rgw zone modify', perm='rw')
def update_zone_info(self, realm_name: str, zonegroup_name: str, zone_name: str, realm_token: str, zone_endpoints: List[str]) -> HandleCommandResult:
try:
@@ -294,6 +298,19 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
inbuf: Optional[str] = None) -> HandleCommandResult:
"""Bootstrap new rgw zone that syncs with zone on another cluster in the same realm"""
+ created_zones = self.rgw_zone_create(zone_name, realm_token, port, placement,
+ start_radosgw, zone_endpoints, inbuf)
+
+ return HandleCommandResult(retval=0, stdout=f"Zones {', '.join(created_zones)} created successfully")
+
+ def rgw_zone_create(self,
+ zone_name: Optional[str] = None,
+ realm_token: Optional[str] = None,
+ port: Optional[int] = None,
+ placement: Optional[str] = None,
+ start_radosgw: Optional[bool] = True,
+ zone_endpoints: Optional[str] = None,
+ inbuf: Optional[str] = None) -> Any:
if inbuf:
try:
rgw_specs = self._parse_rgw_specs(inbuf)
@@ -318,11 +335,11 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
RGWAM(self.env).zone_create(rgw_spec, start_radosgw)
if rgw_spec.rgw_zone is not None:
created_zones.append(rgw_spec.rgw_zone)
+ return created_zones
except RGWAMException as e:
self.log.error('cmd run exception: (%d) %s' % (e.retcode, e.message))
return HandleCommandResult(retval=e.retcode, stdout=e.stdout, stderr=e.stderr)
-
- return HandleCommandResult(retval=0, stdout=f"Zones {', '.join(created_zones)} created successfully")
+ return created_zones
@CLICommand('rgw realm reconcile', perm='rw')
def _cmd_rgw_realm_reconcile(self,
@@ -349,3 +366,13 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
self.log.info('Stopping')
self.run = False
self.event.set()
+
+ def import_realm_token(self,
+ zone_name: Optional[str] = None,
+ realm_token: Optional[str] = None,
+ port: Optional[int] = None,
+ placement: Optional[str] = None,
+ start_radosgw: Optional[bool] = True,
+ zone_endpoints: Optional[str] = None) -> None:
+ self.rgw_zone_create(zone_name, realm_token, port, placement, start_radosgw,
+ zone_endpoints)