]> git.apps.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Allow the user to import and export multi-site configuration
authorAashish Sharma <aasharma@redhat.com>
Tue, 28 Mar 2023 08:04:25 +0000 (13:34 +0530)
committerAashish Sharma <aasharma@li-e74156cc-2f67-11b2-a85c-e98659a63c5c.ibm.com>
Thu, 10 Aug 2023 12:24:04 +0000 (17:54 +0530)
Fixes: https://tracker.ceph.com/issues/61776
Signed-off-by: Aashish Sharma <aasharma@redhat.com>
(cherry picked from commit 5415a2611de7c50cbf429e92b253ed7bbd2ab7ea)

42 files changed:
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-form/service-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/create-rgw-service-entities/create-rgw-service-entities.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zone-deletion-form/rgw-multisite-zone-deletion-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite-zonegroup-deletion-form/rgw-multisite-zonegroup-deletion-form.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/models/rgw-multisite.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.scss
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-details/rgw-multisite-details.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-export/rgw-multisite-export.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.html [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.scss [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.spec.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-import/rgw-multisite-import.component.ts [new file with mode: 0644]
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-migrate/rgw-multisite-migrate.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zone-form/rgw-multisite-zone-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-multisite-zonegroup-form/rgw-multisite-zonegroup-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw.module.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-daemon.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-multisite.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-realm.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zone.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-zonegroup.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/components/alert-panel/alert-panel.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/models/service.interface.ts
src/pybind/mgr/dashboard/openapi.yaml
src/pybind/mgr/dashboard/services/ceph_service.py
src/pybind/mgr/dashboard/services/rgw_client.py
src/pybind/mgr/rgw/module.py

index 02b71d5945f795b776df7b5f9b812c54e8210683..3ba4cf4923e4c70e89c32796dc16da24396c2f35 100644 (file)
@@ -12,14 +12,13 @@ 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
-from ._crud import CRUDMeta, Form, FormField, FormTaskInfo, Icon, MethodType, TableAction, \
-    Validator, VerticalContainer
+    RESTController, UIRouter, UpdatePermission, allow_empty_body
+from ._crud import CRUDMeta, Form, FormField, FormTaskInfo, Icon, MethodType, \
+    TableAction, Validator, VerticalContainer
 from ._version import APIVersion
 
 logger = logging.getLogger("controllers.rgw")
@@ -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
index 6c465d46998d8b682e2dc63ed9b473c9e84ea0ae..0c2392fce41852b48788877dfafabec9ab303d4a 100644 (file)
@@ -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"
             <span class="invalid-feedback"
                   *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">
index b2c965ee71b96a2d185ffe010175c715f35da83f..ebecec5cc3854e31fc36f5b1f61df1b509e01159 100644 (file)
@@ -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
index 5ae2dfa50b4c14e464163d0f78d9574289c76eb0..00276d771bb57b0decd1b46ed9c2ad2c373bc65b 100644 (file)
@@ -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');
+    });
+  }
 }
index 1234a684e6c9d16fac11de4da2a5af33dc3438ad..82a975c9df47ed58f6de09ec861a47b4e5a8ee0e 100644 (file)
@@ -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 (file)
index 0000000..b6e4f80
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..5e6621b
--- /dev/null
@@ -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 (file)
index 0000000..0419151
--- /dev/null
@@ -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`
+                );
+              });
+          });
+      });
+  }
+}
index 947ef3cbfbc50036b1ecc22fa85ec43140ec656b..8cdd79e6549b23d067b34b03bb9a616ab73df474 100644 (file)
@@ -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();
   });
 
index 3ee39f2e9fbdb942882378112fbf089969fac74a..2c4059f251a4cb2a96b7883270b16bc0bb297d09 100644 (file)
@@ -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();
   });
 
index fb0ce154900d8047c367b1b911ffe8a9eb14d37e..1729f6418b2d82f885893207cdee22044bbf3ac5 100644 (file)
@@ -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;
 }
index 757d5f78ae35a4ed066cd616daf31c17d4ad2a88..331ce22e9a2a69ad4de80fd45832e43514bce48e 100644 (file)
@@ -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"
                           [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"
                            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>
                       *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>
                *ngIf="metadata">
             <legend>{{ metadataTitle }}</legend>
             <cd-table-key-value cdTableDetail
-                                [data]="metadata"
-                                [renderObjects]="true"
-                                [customCss]="customCss">
+                                [data]="metadata">
             </cd-table-key-value>
           </div>
         </div>
index 7f1c0b19769b1a6ca4ae973d5e5166a50f98b19f..a2fd6c6f3314464505dca10b78f6335f794819b2 100644 (file)
@@ -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();
   });
 
index 29f9b9d9e9ceead2363f1d0b12ca55d9c37ec257..5a667cbe41f5986d5f8542878e9e190e18dcb159 100644 (file)
@@ -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 (file)
index 0000000..7798849
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..13c33d3
--- /dev/null
@@ -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 (file)
index 0000000..0b1b242
--- /dev/null
@@ -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 (file)
index 0000000..823e8e8
--- /dev/null
@@ -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 (file)
index 0000000..e69de29
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 (file)
index 0000000..8ceb420
--- /dev/null
@@ -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 (file)
index 0000000..5581a80
--- /dev/null
@@ -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 });
+      }
+    );
+  }
+}
index ceb0afc5d57bfe9ae312694ca135b9b9d27aec51..4e0cef63322382d05884a1d8789cccb7f786b4b8 100644 (file)
         </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>
index 786a30fc119b4f4a0392438e9c7018b18e91e3c5..4ede615a2e4a5b3d8bcb728b74636e09e6772740 100644 (file)
@@ -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;
-  }
 }
index 0c4ec560d38f0013100cd6cb196a268b02fa92be..8f4422051c4a7976b95f0de2f53cca2404005b11 100644 (file)
                 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">
index 1fe980e4cd5b0c7ad32533a5b34d20ccbe397f19..1fb9c178da155c891c651a52664633c99d25d1be 100644 (file)
@@ -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);
-    });
-  }
 }
index be9e3f64859117a042fa2b55fca7d39b502a09d4..01955cd49a1a66914192d6e541ffbb76cd0c9c6c 100644 (file)
@@ -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(
index 520cd349fb9f97a9e6581f50c6355f4a521c6e7d..5fb64600565989536860bfc9fec910dab26bb61c 100644 (file)
@@ -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 {}
index 5c513c7f1fa1c894790c8007bf35488d4d8a6fa8..a6007404681d1032ac52ffa7bc50fd509e38d060 100644 (file)
@@ -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 });
+    });
+  }
 }
index 0a601bf0fa13c4ff8cdf1758b4c5122775aa62f9..fbd2ad64ec45dad2149816d5e6890861c53ed336 100644 (file)
@@ -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 });
     });
   }
 }
index 63bb7b8c657f2cd260af951c013d3861deb32004..efa882c8b34cc4a0aa17e9bbd55f8dbd830e1b09 100644 (file)
@@ -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`);
+    });
+  }
 }
index 513747c31a20e91d7cd8d0bbab96a6000855188f..02877816102a5aa0e97f8bc48905ca1e42b1a669 100644 (file)
@@ -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 });
   }
 }
index 28b5cc676a82700477a7388650d32cdcc987fec2..7f795c1d1d890a57ab4f55b9a8f1e53f19ac7fba 100644 (file)
@@ -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;
   }
 }
index be8096427a6c442eccb793b05a8f4c913816ec35..30f8b530a59c5fc170688e0462f15f58dbbaf74f 100644 (file)
@@ -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>
index 51088840e3334f82f895553c935152abeb1bbf74..cc2024baa23359f5a83e88c7bec5e0dd40891738 100644 (file)
@@ -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
index f271c364c2673aa625a9bef2d35e0f6f50a3922e..177382c5350595624a60c3b82b8b24089bfee315 100644 (file)
@@ -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 {
index e2916900d9076f9b71216ed86ad66e1cd3523f2f..3348ccd4352c1c639506aeb28b7a6d0cb63ce34f 100644 (file)
@@ -8305,6 +8305,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:
@@ -8415,6 +8456,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:
@@ -8423,11 +8528,6 @@ paths:
         required: true
         schema:
           type: string
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
       responses:
         '202':
           content:
@@ -8517,37 +8617,6 @@ paths:
       - RgwRealm
   /api/rgw/realm/{realm_name}:
     delete:
-      parameters:
-      - in: path
-        name: realm_name
-        required: true
-        schema:
-          type: string
-      responses:
-        '202':
-          content:
-            application/vnd.ceph.api.v1.0+json:
-              type: object
-          description: Operation is still executing. Please check the task queue.
-        '204':
-          content:
-            application/vnd.ceph.api.v1.0+json:
-              type: object
-          description: Resource deleted.
-        '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
-    get:
       parameters:
       - in: path
         name: realm_name
@@ -8585,8 +8654,6 @@ paths:
           application/json:
             schema:
               properties:
-                daemon_name:
-                  type: string
                 default:
                   default: ''
                   type: string
@@ -9278,12 +9345,7 @@ paths:
       - RgwUser
   /api/rgw/zone:
     get:
-      parameters:
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
+      parameters: []
       responses:
         '200':
           content:
@@ -9310,10 +9372,7 @@ paths:
           application/json:
             schema:
               properties:
-                createSystemUser:
-                  default: false
-                  type: boolean
-                daemon_name:
+                access_key:
                   type: string
                 default:
                   default: false
@@ -9321,9 +9380,7 @@ paths:
                 master:
                   default: false
                   type: boolean
-                master_zone_of_master_zonegroup:
-                  type: string
-                user:
+                secret_key:
                   type: string
                 zone_endpoints:
                   type: string
@@ -9366,8 +9423,6 @@ paths:
           application/json:
             schema:
               properties:
-                daemon_name:
-                  type: string
                 userName:
                   type: string
                 zoneName:
@@ -9447,11 +9502,6 @@ paths:
   /api/rgw/zone/get_user_list:
     get:
       parameters:
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
       - allowEmptyValue: true
         in: query
         name: zoneName
@@ -9499,11 +9549,6 @@ paths:
         name: zonegroup_name
         schema:
           type: string
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
       responses:
         '202':
           content:
@@ -9535,11 +9580,6 @@ paths:
         required: true
         schema:
           type: string
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
       responses:
         '200':
           content:
@@ -9571,10 +9611,11 @@ paths:
           application/json:
             schema:
               properties:
-                compression:
+                access_key:
                   default: ''
                   type: string
-                daemon_name:
+                compression:
+                  default: ''
                   type: string
                 data_extra_pool:
                   default: ''
@@ -9594,21 +9635,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
@@ -9642,12 +9681,7 @@ paths:
       - RgwZone
   /api/rgw/zonegroup:
     get:
-      parameters:
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
+      parameters: []
       responses:
         '200':
           content:
@@ -9674,8 +9708,6 @@ paths:
           application/json:
             schema:
               properties:
-                daemon_name:
-                  type: string
                 default:
                   type: string
                 master:
@@ -9754,11 +9786,6 @@ paths:
         name: pools
         schema:
           type: string
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
       responses:
         '202':
           content:
@@ -9790,11 +9817,6 @@ paths:
         required: true
         schema:
           type: string
-      - allowEmptyValue: true
-        in: query
-        name: daemon_name
-        schema:
-          type: string
       responses:
         '200':
           content:
@@ -9829,8 +9851,6 @@ paths:
                 add_zones:
                   default: []
                   type: string
-                daemon_name:
-                  type: string
                 default:
                   default: ''
                   type: string
@@ -9848,7 +9868,7 @@ paths:
                   default: []
                   type: string
                 zonegroup_endpoints:
-                  default: []
+                  default: ''
                   type: string
               required:
               - realm_name
index f0e21c598989d692c11a525f82adc7ab4a070049..135f88ca2c974222ef6566e5a86e62fdedace22e 100644 (file)
@@ -293,6 +293,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
index 648afb133398df1df1137a47f3f25b66d8aad6f1..707612acf837b9f34dd4c1142eba91971ac462cd 100644 (file)
@@ -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,394 +593,345 @@ class RgwClient(RestClient):
                 return realm_info['name']
         return None
 
-    def create_realm(self, realm_name: str, default: bool):
-        rgw_realm_create_cmd = ['realm', 'create']
-        cmd_create_realm_options = ['--rgw-realm', realm_name]
-        if default != 'false':
-            cmd_create_realm_options.append('--default')
-        rgw_realm_create_cmd += cmd_create_realm_options
+    @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:
-            exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_create_cmd)
-            if exit_code > 0:
-                raise DashboardException(msg='Unable to create realm',
-                                         http_status_code=500, component='rgw')
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
+            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')
 
-    def list_realms(self):
-        rgw_realm_list = {}
-        rgw_realm_list_cmd = ['realm', 'list']
+    @RestClient.api_get('/{bucket_name}?encryption')
+    def get_bucket_encryption(self, bucket_name, request=None):
+        # pylint: disable=unused-argument
         try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_list_cmd)
-            if exit_code > 0:
-                raise DashboardException(msg='Unable to fetch realm list',
-                                         http_status_code=500, component='rgw')
-            rgw_realm_list = out
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
-        return rgw_realm_list
+            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
 
-    def get_realm(self, realm_name: str):
-        realm_info = {}
-        rgw_realm_info_cmd = ['realm', 'get', '--rgw-realm', realm_name]
+    @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:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_info_cmd)
-            if exit_code > 0:
-                raise DashboardException('Unable to get realm info',
-                                         http_status_code=500, component='rgw')
-            realm_info = out
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
-        return realm_info
+            _ = request(data=data)  # type: ignore
+        except RequestException as e:
+            raise DashboardException(msg=str(e), component='rgw')
 
-    def get_all_realms_info(self):
-        all_realms_info = {}
-        realms_info = []
-        rgw_realm_list = self.list_realms()
-        if 'realms' in rgw_realm_list:
-            if rgw_realm_list['realms'] != []:
-                for rgw_realm in rgw_realm_list['realms']:
-                    realm_info = self.get_realm(rgw_realm)
-                    realms_info.append(realm_info)
-                    all_realms_info['realms'] = realms_info  # type: ignore
-            else:
-                all_realms_info['realms'] = []  # type: ignore
-        if 'default_info' in rgw_realm_list and rgw_realm_list['default_info'] != '':
-            all_realms_info['default_realm'] = rgw_realm_list['default_info']  # type: ignore
-        else:
-            all_realms_info['default_realm'] = ''  # type: ignore
-        return all_realms_info
+    @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
 
-    def delete_realm(self, realm_name: str):
-        rgw_delete_realm_cmd = ['realm', 'rm', '--rgw-realm', realm_name]
+        # Try to get the Object Lock configuration. If there is none,
+        # then return default values.
         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')
+            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')
 
-    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')
+        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}"')
 
-    def edit_realm(self, realm_name: str, new_realm_name: str, default: str = ''):
-        rgw_realm_edit_cmd = []
-        if new_realm_name != realm_name:
-            rgw_realm_edit_cmd = ['realm', 'rename', '--rgw-realm',
-                                  realm_name, '--realm-new-name', new_realm_name]
-            try:
-                exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_edit_cmd, False)
-                if exit_code > 0:
-                    raise DashboardException(e=err, msg='Unable to edit realm',
-                                             http_status_code=500, component='rgw')
-            except SubprocessError as error:
-                raise DashboardException(error, http_status_code=500, component='rgw')
-        if default and str_to_bool(default):
-            rgw_realm_edit_cmd = ['realm', 'default', '--rgw-realm', new_realm_name]
-            try:
-                exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_edit_cmd, False)
-                if exit_code > 0:
-                    raise DashboardException(msg='Unable to set {} as default realm'.format(new_realm_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')
+        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}"'))
 
-    def create_zonegroup(self, realm_name: str, zonegroup_name: str,
-                         default: bool, master: bool, endpoints: List[str]):
-        rgw_zonegroup_create_cmd = ['zonegroup', 'create']
-        cmd_create_zonegroup_options = ['--rgw-zonegroup', zonegroup_name]
-        if realm_name != 'null':
-            cmd_create_zonegroup_options.append('--rgw-realm')
-            cmd_create_zonegroup_options.append(realm_name)
-        if default != 'false':
-            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
-            cmd_create_zonegroup_options.append('--endpoints')
-            cmd_create_zonegroup_options.append(endpoint)
-        rgw_zonegroup_create_cmd += cmd_create_zonegroup_options
-        try:
-            exit_code, out, err = mgr.send_rgwadmin_command(rgw_zonegroup_create_cmd)
-            if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to get realm info',
-                                         http_status_code=500, component='rgw')
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
-        return out
+        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}"]
 
-    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]
-        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)
-        if master and str_to_bool(master):
-            rgw_zonegroup_modify_cmd.append('--master')
-        if default and str_to_bool(default):
-            rgw_zonegroup_modify_cmd.append('--default')
+        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:
-            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')
-        self.update_period()
+            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 add_or_remove_zone(self, zonegroup_name: str, zone_name: str, action: str):
-        if action == 'add':
-            rgw_zonegroup_add_zone_cmd = ['zonegroup', 'add', '--rgw-zonegroup',
-                                          zonegroup_name, '--rgw-zone', zone_name]
-            try:
-                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_add_zone_cmd)
-                if exit_code > 0:
-                    raise DashboardException(e=err, msg='Unable to add zone {} to zonegroup {}'.format(zone_name, 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')
-            self.update_period()
-        if action == 'remove':
-            rgw_zonegroup_rm_zone_cmd = ['zonegroup', 'remove',
-                                         '--rgw-zonegroup', zonegroup_name, '--rgw-zone', zone_name]
-            try:
-                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_rm_zone_cmd)
-                if exit_code > 0:
-                    raise DashboardException(e=err, msg='Unable to remove zone {} from zonegroup {}'.format(zone_name, 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')
-            self.update_period()
 
-    def get_placement_targets_by_zonegroup(self, zonegroup_name: str):
-        rgw_get_placement_cmd = ['zonegroup', 'placement',
-                                 'list', '--rgw-zonegroup', zonegroup_name]
+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, out, err = mgr.send_rgwadmin_command(rgw_get_placement_cmd)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to get placement targets',
+                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')
-        return out
-
-    def add_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
-        rgw_add_placement_cmd = ['zonegroup', 'placement', 'add']
-        for placement_target in placement_targets:
-            cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
-                                         '--placement-id', placement_target['placement_id']]
-            if placement_target['tags']:
-                cmd_add_placement_options += ['--tags', placement_target['tags']]
-            rgw_add_placement_cmd += cmd_add_placement_options
-            try:
-                exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
-                if exit_code > 0:
-                    raise DashboardException(e=err,
-                                             msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
-            self.update_period()
-            storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else []  # noqa E501  #pylint: disable=line-too-long
-            if storage_classes:
-                for sc in storage_classes:
-                    cmd_add_placement_options = ['--storage-class', sc]
-                    try:
-                        exit_code, _, err = mgr.send_rgwadmin_command(
-                            rgw_add_placement_cmd + cmd_add_placement_options)
-                        if exit_code > 0:
-                            raise DashboardException(e=err,
-                                                     msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
-                    self.update_period()
-
-    def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
-        rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify']
-        for placement_target in placement_targets:
-            cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
-                                         '--placement-id', placement_target['placement_id']]
-            if placement_target['tags']:
-                cmd_add_placement_options += ['--tags', placement_target['tags']]
-            rgw_add_placement_cmd += cmd_add_placement_options
-            storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else []  # noqa E501  #pylint: disable=line-too-long
-            if storage_classes:
-                for sc in storage_classes:
-                    cmd_add_placement_options = []
-                    cmd_add_placement_options = ['--storage-class', sc]
-                    try:
-                        exit_code, _, err = mgr.send_rgwadmin_command(
-                            rgw_add_placement_cmd + cmd_add_placement_options)
-                        if exit_code > 0:
-                            raise DashboardException(e=err,
-                                                     msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
-                    self.update_period()
-            else:
-                try:
-                    exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
-                    if exit_code > 0:
-                        raise DashboardException(e=err,
-                                                 msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
-                self.update_period()
-
-    # pylint: disable=W0102
-    def edit_zonegroup(self, realm_name: str, zonegroup_name: str, new_zonegroup_name: str,
-                       default: str = '', master: str = '', endpoints: List[str] = [],
-                       add_zones: List[str] = [], remove_zones: List[str] = [],
-                       placement_targets: List[Dict[str, str]] = []):
-        rgw_zonegroup_edit_cmd = []
-        if new_zonegroup_name != zonegroup_name:
-            rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', zonegroup_name,
-                                      '--zonegroup-new-name', new_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(new_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')
-            self.update_period()
-        self.modify_zonegroup(realm_name, new_zonegroup_name, default, master, endpoints)
-        if add_zones:
-            for zone_name in add_zones:
-                self.add_or_remove_zone(new_zonegroup_name, zone_name, 'add')
-        if remove_zones:
-            for zone_name in remove_zones:
-                self.add_or_remove_zone(new_zonegroup_name, zone_name, 'remove')
-        existing_placement_targets = self.get_placement_targets_by_zonegroup(new_zonegroup_name)
-        existing_placement_targets_ids = [pt['key'] for pt in existing_placement_targets]
-        if placement_targets:
-            for pt in placement_targets:
-                if pt['placement_id'] in existing_placement_targets_ids:
-                    self.modify_placement_targets(new_zonegroup_name, placement_targets)
-                else:
-                    self.add_placement_targets(new_zonegroup_name, placement_targets)
 
-    def list_zonegroups(self):
-        rgw_zonegroup_list = {}
-        rgw_zonegroup_list_cmd = ['zonegroup', 'list']
+        rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default',
+                                  '--zonegroup-new-name', zonegroup_name]
         try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_list_cmd)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
             if exit_code > 0:
-                raise DashboardException(msg='Unable to fetch zonegroup list',
+                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')
-            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]
+        rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone',
+                             'default', '--zone-new-name', zone_name,
+                             '--rgw-zonegroup', zonegroup_name]
         try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_info_cmd)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False)
             if exit_code > 0:
-                raise DashboardException('Unable to get zonegroup info',
+                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')
-            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]
+        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, _, _ = mgr.send_rgwadmin_command(rgw_delete_zonegroup_cmd)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
             if exit_code > 0:
-                raise DashboardException(msg='Unable to delete zonegroup',
+                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')
-        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
-        rgw_zone_create_cmd = ['zone', 'create']
-        cmd_create_zone_options = ['--rgw-zone', zone_name]
-        if zonegroup_name != 'null':
-            cmd_create_zone_options.append('--rgw-zonegroup')
-            cmd_create_zone_options.append(zonegroup_name)
-        if default != 'false':
-            cmd_create_zone_options.append('--default')
-        if master != 'false':
-            cmd_create_zone_options.append('--master')
-        if endpoints != 'null':
-            cmd_create_zone_options.append('--endpoints')
-            cmd_create_zone_options.append(endpoints)
-        if access_key is not None:
-            cmd_create_zone_options.append('--access-key')
-            cmd_create_zone_options.append(access_key)
-        if secret_key is not None:
-            cmd_create_zone_options.append('--secret')
-            cmd_create_zone_options.append(secret_key)
-        rgw_zone_create_cmd += cmd_create_zone_options
+        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, out, err = mgr.send_rgwadmin_command(rgw_zone_create_cmd)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to create zone',
+                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()
-
-        if createSystemUser == 'true':
-            self.create_system_user(user, zone_name)
-            access_key, secret_key = self.get_rgw_user_keys(user, zone_name)
+        if access_key and secret_key:
             rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zone', zone_name,
                                    '--access-key', access_key, '--secret', secret_key]
             try:
@@ -991,607 +941,590 @@ class RgwClient(RestClient):
                                              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:
-                access_key = key.get('access_key')
-                secret_key = key.get('secret_key')
-                return access_key, secret_key
-        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
-        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)
-        if default and str_to_bool(default):
-            rgw_zone_modify_cmd.append('--default')
-        if master and str_to_bool(master):
-            rgw_zone_modify_cmd.append('--master')
-        if access_key is not None:
-            rgw_zone_modify_cmd.append('--access-key')
-            rgw_zone_modify_cmd.append(access_key)
-        if secret_key is not None:
-            rgw_zone_modify_cmd.append('--secret')
-            rgw_zone_modify_cmd.append(secret_key)
+    def create_realm(self, realm_name: str, default: bool):
+        rgw_realm_create_cmd = ['realm', 'create']
+        cmd_create_realm_options = ['--rgw-realm', realm_name]
+        if default != 'false':
+            cmd_create_realm_options.append('--default')
+        rgw_realm_create_cmd += cmd_create_realm_options
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
+            exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_create_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to modify zone',
+                raise DashboardException(msg='Unable to create realm',
                                          http_status_code=500, component='rgw')
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
-        self.update_period()
 
-    def add_placement_targets_zone(self, zone_name: str, placement_target: str, data_pool: str,
-                                   index_pool: str, data_extra_pool: str):
-        rgw_zone_add_placement_cmd = ['zone', 'placement', 'add', '--rgw-zone', zone_name,
-                                      '--placement-id', placement_target, '--data-pool', data_pool,
-                                      '--index-pool', index_pool,
-                                      '--data-extra-pool', data_extra_pool]
+    def list_realms(self):
+        rgw_realm_list = {}
+        rgw_realm_list_cmd = ['realm', 'list']
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_add_placement_cmd)
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_list_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to add placement target {} to zone {}'.format(placement_target, zone_name),  # noqa E501 #pylint: disable=line-too-long
+                raise DashboardException(msg='Unable to fetch realm list',
                                          http_status_code=500, component='rgw')
+            rgw_realm_list = out
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
-        self.update_period()
+        return rgw_realm_list
 
-    def add_storage_class_zone(self, zone_name: str, placement_target: str, storage_class: str,
-                               data_pool: str, compression: str):
-        rgw_zone_add_storage_class_cmd = ['zone', 'placement', 'add', '--rgw-zone', zone_name,
-                                          '--placement-id', placement_target,
-                                          '--storage-class', storage_class,
-                                          '--data-pool', data_pool,
-                                          '--compression', compression]
+    def get_realm(self, realm_name: str):
+        realm_info = {}
+        rgw_realm_info_cmd = ['realm', 'get', '--rgw-realm', realm_name]
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_add_storage_class_cmd)
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_realm_info_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to add storage class {} to zone {}'.format(storage_class, zone_name),  # noqa E501 #pylint: disable=line-too-long
+                raise DashboardException('Unable to get realm info',
                                          http_status_code=500, component='rgw')
+            realm_info = out
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
-        self.update_period()
+        return realm_info
 
-    def edit_zone(self, zone_name: str, new_zone_name: str, zonegroup_name: str, default: str = '',
-                  master: str = '', endpoints: List[str] = [], user: 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):
-        if new_zone_name != zone_name:
-            rgw_zone_rename_cmd = ['zone', 'rename', '--rgw-zone',
-                                   zone_name, '--zone-new-name', new_zone_name]
+    def get_all_realms_info(self):
+        all_realms_info = {}
+        realms_info = []
+        rgw_realm_list = self.list_realms()
+        if 'realms' in rgw_realm_list:
+            if rgw_realm_list['realms'] != []:
+                for rgw_realm in rgw_realm_list['realms']:
+                    realm_info = self.get_realm(rgw_realm)
+                    realms_info.append(realm_info)
+                    all_realms_info['realms'] = realms_info  # type: ignore
+            else:
+                all_realms_info['realms'] = []  # type: ignore
+        if 'default_info' in rgw_realm_list and rgw_realm_list['default_info'] != '':
+            all_realms_info['default_realm'] = rgw_realm_list['default_info']  # type: ignore
+        else:
+            all_realms_info['default_realm'] = ''  # type: ignore
+        return all_realms_info
+
+    def edit_realm(self, realm_name: str, new_realm_name: str, default: str = ''):
+        rgw_realm_edit_cmd = []
+        if new_realm_name != realm_name:
+            rgw_realm_edit_cmd = ['realm', 'rename', '--rgw-realm',
+                                  realm_name, '--realm-new-name', new_realm_name]
             try:
-                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_rename_cmd, False)
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_realm_edit_cmd, False)
                 if exit_code > 0:
-                    raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(new_zone_name),  # noqa E501 #pylint: disable=line-too-long
+                    raise DashboardException(e=err, msg='Unable to edit realm',
                                              http_status_code=500, component='rgw')
             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.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,
-                                    data_pool_class, compression)
-
-    def list_zones(self):
-        rgw_zone_list = {}
-        rgw_zone_list_cmd = ['zone', 'list']
-        try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zone_list_cmd)
-            if exit_code > 0:
-                raise DashboardException(msg='Unable to fetch zone list',
-                                         http_status_code=500, component='rgw')
-            rgw_zone_list = out
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
-        return rgw_zone_list
-
-    def get_zone(self, zone_name: str):
-        zone_info = {}
-        rgw_zone_info_cmd = ['zone', 'get', '--rgw-zone', zone_name]
-        try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zone_info_cmd)
-            if exit_code > 0:
-                raise DashboardException('Unable to get zone info',
-                                         http_status_code=500, component='rgw')
-            zone_info = out
-        except SubprocessError as error:
-            raise DashboardException(error, http_status_code=500, component='rgw')
-        return zone_info
-
-    def get_all_zones_info(self):
-        all_zones_info = {}
-        zones_info = []
-        rgw_zone_list = self.list_zones()
-        if 'zones' in rgw_zone_list:
-            if rgw_zone_list['zones'] != []:
-                for rgw_zone in rgw_zone_list['zones']:
-                    zone_info = self.get_zone(rgw_zone)
-                    zones_info.append(zone_info)
-                    all_zones_info['zones'] = zones_info  # type: ignore
-            else:
-                all_zones_info['zones'] = []
-        if 'default_info' in rgw_zone_list and rgw_zone_list['default_info'] != '':
-            all_zones_info['default_zone'] = rgw_zone_list['default_info']  # type: ignore
-        else:
-            all_zones_info['default_zone'] = ''  # type: ignore
-        return all_zones_info
-
-    def delete_zone(self, zone_name: str, delete_pools: str, pools: List[str],
-                    zonegroup_name: str = '',):
-        rgw_remove_zone_from_zonegroup_cmd = ['zonegroup', 'remove', '--rgw-zonegroup',
-                                              zonegroup_name, '--rgw-zone', zone_name]
-        rgw_delete_zone_cmd = ['zone', 'delete', '--rgw-zone', zone_name]
-        if zonegroup_name:
+        if default and str_to_bool(default):
+            rgw_realm_edit_cmd = ['realm', 'default', '--rgw-realm', new_realm_name]
             try:
-                exit_code, _, _ = mgr.send_rgwadmin_command(rgw_remove_zone_from_zonegroup_cmd)
+                exit_code, _, _ = mgr.send_rgwadmin_command(rgw_realm_edit_cmd, False)
                 if exit_code > 0:
-                    raise DashboardException(msg='Unable to remove zone from zonegroup',
+                    raise DashboardException(msg='Unable to set {} as default realm'.format(new_realm_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')
-            self.update_period()
+
+    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_zone_cmd)
+            exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_realm_cmd)
             if exit_code > 0:
-                raise DashboardException(msg='Unable to delete zone',
+                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')
-        self.update_period()
-        if delete_pools == 'true':
-            self.delete_pools(pools)
-
-    def delete_pools(self, pools):
-        for pool in pools:
-            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']
+    def create_zonegroup(self, realm_name: str, zonegroup_name: 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':
+            cmd_create_zonegroup_options.append('--rgw-realm')
+            cmd_create_zonegroup_options.append(realm_name)
+        if default != 'false':
+            cmd_create_zonegroup_options.append('--default')
+        if master != 'false':
+            cmd_create_zonegroup_options.append('--master')
+        if endpoints:
+            cmd_create_zonegroup_options.append('--endpoints')
+            cmd_create_zonegroup_options.append(endpoints)
+        rgw_zonegroup_create_cmd += cmd_create_zonegroup_options
         try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_sync_status_cmd, False)
+            exit_code, out, err = mgr.send_rgwadmin_command(rgw_zonegroup_create_cmd)
             if exit_code > 0:
-                raise DashboardException('Unable to get sync status',
+                raise DashboardException(e=err, msg='Unable to get realm info',
                                          http_status_code=500, component='rgw')
-            sync_status = out
-        except subprocess.TimeoutExpired:
-            sync_status = 'Timeout Expired'
-        return sync_status
+        except SubprocessError as error:
+            raise DashboardException(error, http_status_code=500, component='rgw')
+        return out
 
-    def create_system_user(self, userName: str, zoneName: str):
-        rgw_user_create_cmd = ['user', 'create', '--uid', userName,
-                               '--display-name', userName, '--rgw-zone', zoneName, '--system']
+    def list_zonegroups(self):
+        rgw_zonegroup_list = {}
+        rgw_zonegroup_list_cmd = ['zonegroup', 'list']
         try:
-            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_create_cmd)
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_list_cmd)
             if exit_code > 0:
-                raise DashboardException(msg='Unable to create system user',
+                raise DashboardException(msg='Unable to fetch zonegroup list',
                                          http_status_code=500, component='rgw')
-            return out
+            rgw_zonegroup_list = out
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
+        return rgw_zonegroup_list
 
-    def get_user_list(self, zoneName: str):
-        all_users_info = []
-        user_list = []
-        rgw_user_list_cmd = ['user', 'list', '--rgw-zone', zoneName]
+    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_user_list_cmd)
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zonegroup_info_cmd)
             if exit_code > 0:
-                raise DashboardException('Unable to get user list',
+                raise DashboardException('Unable to get zonegroup info',
                                          http_status_code=500, component='rgw')
-            user_list = out
+            zonegroup_info = out
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
+        return zonegroup_info
 
-        if len(user_list) > 0:
-            for user_name in user_list:
-                rgw_user_info_cmd = ['user', 'info', '--uid', user_name, '--rgw-zone', zoneName]
-                try:
-                    exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_info_cmd)
-                    if exit_code > 0:
-                        raise DashboardException('Unable to get user info',
-                                                 http_status_code=500, component='rgw')
-                    all_users_info.append(out)
-                except SubprocessError as error:
-                    raise DashboardException(error, http_status_code=500, component='rgw')
-        return all_users_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 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']
+    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, _, err = mgr.send_rgwadmin_command(rgw_realm_create_cmd, False)
+            exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_zonegroup_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to create realm',
+                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)
 
-        rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', 'default',
-                                  '--zonegroup-new-name', zonegroup_name]
+    def modify_zonegroup(self, realm_name: str, zonegroup_name: str, default: str, master: str,
+                         endpoints: str):
+
+        rgw_zonegroup_modify_cmd = ['zonegroup', 'modify',
+                                    '--rgw-realm', realm_name,
+                                    '--rgw-zonegroup', zonegroup_name]
+        if endpoints:
+            rgw_zonegroup_modify_cmd.append('--endpoints')
+            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):
+            rgw_zonegroup_modify_cmd.append('--default')
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_edit_cmd, False)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to rename zonegroup to {}'.format(zonegroup_name),  # noqa E501  #pylint: disable=line-too-long
+                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')
+        self.update_period()
 
-        rgw_zone_edit_cmd = ['zone', 'rename', '--rgw-zone',
-                             'default', '--zone-new-name', zone_name,
-                             '--rgw-zonegroup', zonegroup_name]
+    def add_or_remove_zone(self, zonegroup_name: str, zone_name: str, action: str):
+        if action == 'add':
+            rgw_zonegroup_add_zone_cmd = ['zonegroup', 'add', '--rgw-zonegroup',
+                                          zonegroup_name, '--rgw-zone', zone_name]
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_add_zone_cmd)
+                if exit_code > 0:
+                    raise DashboardException(e=err, msg='Unable to add zone {} to zonegroup {}'.format(zone_name, 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')
+            self.update_period()
+        if action == 'remove':
+            rgw_zonegroup_rm_zone_cmd = ['zonegroup', 'remove',
+                                         '--rgw-zonegroup', zonegroup_name, '--rgw-zone', zone_name]
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_rm_zone_cmd)
+                if exit_code > 0:
+                    raise DashboardException(e=err, msg='Unable to remove zone {} from zonegroup {}'.format(zone_name, 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')
+            self.update_period()
+
+    def get_placement_targets_by_zonegroup(self, zonegroup_name: str):
+        rgw_get_placement_cmd = ['zonegroup', 'placement',
+                                 'list', '--rgw-zonegroup', zonegroup_name]
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_edit_cmd, False)
+            exit_code, out, err = mgr.send_rgwadmin_command(rgw_get_placement_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(zone_name),  # noqa E501 #pylint: disable=line-too-long
+                raise DashboardException(e=err, msg='Unable to get placement targets',
                                          http_status_code=500, component='rgw')
         except SubprocessError as error:
             raise DashboardException(error, http_status_code=500, component='rgw')
+        return out
+
+    def add_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
+        rgw_add_placement_cmd = ['zonegroup', 'placement', 'add']
+        for placement_target in placement_targets:
+            cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
+                                         '--placement-id', placement_target['placement_id']]
+            if placement_target['tags']:
+                cmd_add_placement_options += ['--tags', placement_target['tags']]
+            rgw_add_placement_cmd += cmd_add_placement_options
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
+                if exit_code > 0:
+                    raise DashboardException(e=err,
+                                             msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
+            self.update_period()
+            storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else []  # noqa E501  #pylint: disable=line-too-long
+            if storage_classes:
+                for sc in storage_classes:
+                    cmd_add_placement_options = ['--storage-class', sc]
+                    try:
+                        exit_code, _, err = mgr.send_rgwadmin_command(
+                            rgw_add_placement_cmd + cmd_add_placement_options)
+                        if exit_code > 0:
+                            raise DashboardException(e=err,
+                                                     msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
+                    self.update_period()
+
+    def modify_placement_targets(self, zonegroup_name: str, placement_targets: List[Dict]):
+        rgw_add_placement_cmd = ['zonegroup', 'placement', 'modify']
+        for placement_target in placement_targets:
+            cmd_add_placement_options = ['--rgw-zonegroup', zonegroup_name,
+                                         '--placement-id', placement_target['placement_id']]
+            if placement_target['tags']:
+                cmd_add_placement_options += ['--tags', placement_target['tags']]
+            rgw_add_placement_cmd += cmd_add_placement_options
+            storage_classes = placement_target['storage_class'].split(",") if placement_target['storage_class'] else []  # noqa E501  #pylint: disable=line-too-long
+            if storage_classes:
+                for sc in storage_classes:
+                    cmd_add_placement_options = []
+                    cmd_add_placement_options = ['--storage-class', sc]
+                    try:
+                        exit_code, _, err = mgr.send_rgwadmin_command(
+                            rgw_add_placement_cmd + cmd_add_placement_options)
+                        if exit_code > 0:
+                            raise DashboardException(e=err,
+                                                     msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
+                    self.update_period()
+            else:
+                try:
+                    exit_code, _, err = mgr.send_rgwadmin_command(rgw_add_placement_cmd)
+                    if exit_code > 0:
+                        raise DashboardException(e=err,
+                                                 msg='Unable to add placement target {} to zonegroup {}'.format(placement_target['placement_id'], 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')
+                self.update_period()
+
+    # pylint: disable=W0102
+    def edit_zonegroup(self, realm_name: str, zonegroup_name: str, new_zonegroup_name: 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 = []
+        if new_zonegroup_name != zonegroup_name:
+            rgw_zonegroup_edit_cmd = ['zonegroup', 'rename', '--rgw-zonegroup', zonegroup_name,
+                                      '--zonegroup-new-name', new_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(new_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')
+            self.update_period()
+        self.modify_zonegroup(realm_name, new_zonegroup_name, default, master, endpoints)
+        if add_zones:
+            for zone_name in add_zones:
+                self.add_or_remove_zone(new_zonegroup_name, zone_name, 'add')
+        if remove_zones:
+            for zone_name in remove_zones:
+                self.add_or_remove_zone(new_zonegroup_name, zone_name, 'remove')
+        existing_placement_targets = self.get_placement_targets_by_zonegroup(new_zonegroup_name)
+        existing_placement_targets_ids = [pt['key'] for pt in existing_placement_targets]
+        if placement_targets:
+            for pt in placement_targets:
+                if pt['placement_id'] in existing_placement_targets_ids:
+                    self.modify_placement_targets(new_zonegroup_name, placement_targets)
+                else:
+                    self.add_placement_targets(new_zonegroup_name, placement_targets)
 
-        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')
+    def update_period(self):
+        rgw_update_period_cmd = ['period', 'update', '--commit']
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zonegroup_modify_cmd)
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_update_period_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to modify zonegroup {}'.format(zonegroup_name),  # noqa E501  #pylint: disable=line-too-long
+                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')
 
-        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')
+    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':
+            cmd_create_zone_options.append('--rgw-zonegroup')
+            cmd_create_zone_options.append(zonegroup_name)
+        if default != 'false':
+            cmd_create_zone_options.append('--default')
+        if master != 'false':
+            cmd_create_zone_options.append('--master')
+        if endpoints != 'null':
+            cmd_create_zone_options.append('--endpoints')
+            cmd_create_zone_options.append(endpoints)
+        if access_key is not None:
+            cmd_create_zone_options.append('--access-key')
+            cmd_create_zone_options.append(access_key)
+        if secret_key is not None:
+            cmd_create_zone_options.append('--secret')
+            cmd_create_zone_options.append(secret_key)
+        rgw_zone_create_cmd += cmd_create_zone_options
         try:
-            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_modify_cmd)
+            exit_code, out, err = mgr.send_rgwadmin_command(rgw_zone_create_cmd)
             if exit_code > 0:
-                raise DashboardException(e=err, msg='Unable to modify zone',
+                raise DashboardException(e=err, msg='Unable to create 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()
+        return out
 
-    @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)
+    def parse_secrets(self, user, data):
+        for key in data.get('keys', []):
+            if key.get('user') == user:
+                access_key = key.get('access_key')
+                secret_key = key.get('secret_key')
+                return access_key, secret_key
+        return '', ''
 
-        data = ET.tostring(locking_configuration, encoding='unicode')
+    def modify_zone(self, zone_name: str, zonegroup_name: str, default: str, master: str,
+                    endpoints: str, access_key: str, secret_key: str):
+        rgw_zone_modify_cmd = ['zone', 'modify', '--rgw-zonegroup',
+                               zonegroup_name, '--rgw-zone', zone_name]
+        if endpoints:
+            rgw_zone_modify_cmd.append('--endpoints')
+            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):
+            rgw_zone_modify_cmd.append('--master')
+        if access_key is not None:
+            rgw_zone_modify_cmd.append('--access-key')
+            rgw_zone_modify_cmd.append(access_key)
+        if secret_key is not None:
+            rgw_zone_modify_cmd.append('--secret')
+            rgw_zone_modify_cmd.append(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()
 
+    def add_placement_targets_zone(self, zone_name: str, placement_target: str, data_pool: str,
+                                   index_pool: str, data_extra_pool: str):
+        rgw_zone_add_placement_cmd = ['zone', 'placement', 'add', '--rgw-zone', zone_name,
+                                      '--placement-id', placement_target, '--data-pool', data_pool,
+                                      '--index-pool', index_pool,
+                                      '--data-extra-pool', data_extra_pool]
         try:
-            _ = request(data=data)  # type: ignore
-        except RequestException as e:
-            raise DashboardException(msg=str(e), component='rgw')
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_add_placement_cmd)
+            if exit_code > 0:
+                raise DashboardException(e=err, msg='Unable to add placement target {} to zone {}'.format(placement_target, 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')
+        self.update_period()
 
-    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 []
+    def add_storage_class_zone(self, zone_name: str, placement_target: str, storage_class: str,
+                               data_pool: str, compression: str):
+        rgw_zone_add_storage_class_cmd = ['zone', 'placement', 'add', '--rgw-zone', zone_name,
+                                          '--placement-id', placement_target,
+                                          '--storage-class', storage_class,
+                                          '--data-pool', data_pool,
+                                          '--compression', compression]
+        try:
+            exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_add_storage_class_cmd)
+            if exit_code > 0:
+                raise DashboardException(e=err, msg='Unable to add storage class {} to zone {}'.format(storage_class, 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')
+        self.update_period()
 
-        return roles
+    def edit_zone(self, zone_name: str, new_zone_name: str, zonegroup_name: str, default: 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 = ''):
+        if new_zone_name != zone_name:
+            rgw_zone_rename_cmd = ['zone', 'rename', '--rgw-zone',
+                                   zone_name, '--zone-new-name', new_zone_name]
+            try:
+                exit_code, _, err = mgr.send_rgwadmin_command(rgw_zone_rename_cmd, False)
+                if exit_code > 0:
+                    raise DashboardException(e=err, msg='Unable to rename zone to {}'.format(new_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')
+            self.update_period()
+        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,
+                                    data_pool_class, compression)
 
-    def create_role(self, role_name: str, role_path: str, role_assume_policy_doc: str) -> None:
+    def list_zones(self):
+        rgw_zone_list = {}
+        rgw_zone_list_cmd = ['zone', 'list']
         try:
-            json.loads(role_assume_policy_doc)
-        except:  # noqa: E722
-            raise DashboardException('Assume role policy document is not a valid json')
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zone_list_cmd)
+            if exit_code > 0:
+                raise DashboardException(msg='Unable to fetch zone list',
+                                         http_status_code=500, component='rgw')
+            rgw_zone_list = out
+        except SubprocessError as error:
+            raise DashboardException(error, http_status_code=500, component='rgw')
+        return rgw_zone_list
 
-        # 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')
+    def get_zone(self, zone_name: str):
+        zone_info = {}
+        rgw_zone_info_cmd = ['zone', 'get', '--rgw-zone', zone_name]
+        try:
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_zone_info_cmd)
+            if exit_code > 0:
+                raise DashboardException('Unable to get zone info',
+                                         http_status_code=500, component='rgw')
+            zone_info = out
+        except SubprocessError as error:
+            raise DashboardException(error, http_status_code=500, component='rgw')
+        return zone_info
 
-        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}"')
+    def get_all_zones_info(self):
+        all_zones_info = {}
+        zones_info = []
+        rgw_zone_list = self.list_zones()
+        if 'zones' in rgw_zone_list:
+            if rgw_zone_list['zones'] != []:
+                for rgw_zone in rgw_zone_list['zones']:
+                    zone_info = self.get_zone(rgw_zone)
+                    zones_info.append(zone_info)
+                    all_zones_info['zones'] = zones_info  # type: ignore
+            else:
+                all_zones_info['zones'] = []
+        if 'default_info' in rgw_zone_list and rgw_zone_list['default_info'] != '':
+            all_zones_info['default_zone'] = rgw_zone_list['default_info']  # type: ignore
+        else:
+            all_zones_info['default_zone'] = ''  # type: ignore
+        return all_zones_info
 
-        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}"'))
+    def delete_zone(self, zone_name: str, delete_pools: str, pools: List[str],
+                    zonegroup_name: str = '',):
+        rgw_remove_zone_from_zonegroup_cmd = ['zonegroup', 'remove', '--rgw-zonegroup',
+                                              zonegroup_name, '--rgw-zone', zone_name]
+        rgw_delete_zone_cmd = ['zone', 'delete', '--rgw-zone', zone_name]
+        if zonegroup_name:
+            try:
+                exit_code, _, _ = mgr.send_rgwadmin_command(rgw_remove_zone_from_zonegroup_cmd)
+                if exit_code > 0:
+                    raise DashboardException(msg='Unable to remove zone from zonegroup',
+                                             http_status_code=500, component='rgw')
+            except SubprocessError as error:
+                raise DashboardException(error, http_status_code=500, component='rgw')
+            self.update_period()
+        try:
+            exit_code, _, _ = mgr.send_rgwadmin_command(rgw_delete_zone_cmd)
+            if exit_code > 0:
+                raise DashboardException(msg='Unable to delete zone',
+                                         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':
+            self.delete_pools(pools)
 
-        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}"]
+    def delete_pools(self, pools):
+        for pool in pools:
+            if mgr.rados.pool_exists(pool):
+                mgr.rados.delete_pool(pool)
 
-        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 create_system_user(self, userName: str, zoneName: str):
+        rgw_user_create_cmd = ['user', 'create', '--uid', userName,
+                               '--display-name', userName, '--rgw-zone', zoneName, '--system']
+        try:
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_create_cmd)
+            if exit_code > 0:
+                raise DashboardException(msg='Unable to create system user',
+                                         http_status_code=500, component='rgw')
+            return out
+        except SubprocessError as error:
+            raise DashboardException(error, http_status_code=500, component='rgw')
 
-    def perform_validations(self, retention_period_days, retention_period_years, mode):
+    def get_user_list(self, zoneName: str):
+        all_users_info = []
+        user_list = []
+        rgw_user_list_cmd = ['user', 'list', '--rgw-zone', zoneName]
         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
+            exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_list_cmd)
+            if exit_code > 0:
+                raise DashboardException('Unable to get user list',
+                                         http_status_code=500, component='rgw')
+            user_list = out
+        except SubprocessError as error:
+            raise DashboardException(error, http_status_code=500, component='rgw')
+
+        if len(user_list) > 0:
+            for user_name in user_list:
+                rgw_user_info_cmd = ['user', 'info', '--uid', user_name, '--rgw-zone', zoneName]
+                try:
+                    exit_code, out, _ = mgr.send_rgwadmin_command(rgw_user_info_cmd)
+                    if exit_code > 0:
+                        raise DashboardException('Unable to get user info',
+                                                 http_status_code=500, component='rgw')
+                    all_users_info.append(out)
+                except SubprocessError as error:
+                    raise DashboardException(error, http_status_code=500, component='rgw')
+        return all_users_info
+
+    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
index bb1f9460025780c9726d604d645af67de95cd759..079e7e817ca55a80810c3baa13cd0d6891bc9bf8 100644 (file)
@@ -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)