]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: Fix various RGW issues 28210/head
authorVolker Theile <vtheile@suse.com>
Wed, 22 May 2019 09:30:50 +0000 (11:30 +0200)
committerVolker Theile <vtheile@suse.com>
Fri, 19 Jul 2019 14:59:25 +0000 (16:59 +0200)
* Fix handling of tenanted users
* Better exception handling if RGW backend is not available
* RGW user/bucket "max. size" should be hidden when "user/bucket quota" is not enabled
* Fix bug in loading user quota.

Fixes: https://tracker.ceph.com/issues/38800
partial manual backport of 61995970916cb11f66c3800bcf869755f8dae32c
partial manual backport of e6f130d47023c24f8a9c742f145abd34d7320cd8
partial manual backport of 945e790cab57785e1bc2dbf708d5ddafec618aba
partial manual backport of a98bca6a2f161140f78f34cfa5f5ebec07af2f82
partial manual backport of 975736aba546adb09ce4f38a1ae061621f7240cd

This fix is not cherry-picked because the code was completely re-worked for Nautilus (codebase is too far apart).

Signed-off-by: Volker Theile <vtheile@suse.com>
17 files changed:
.gitignore
qa/tasks/mgr/dashboard/helper.py
qa/tasks/mgr/dashboard/test_rgw.py
src/pybind/mgr/dashboard/controllers/rgw.py
src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-list/rgw-bucket-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-details/rgw-user-details.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-list/rgw-user-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts

index 7d42fb41f1934e226cc1495ca243ae8439d8b75d..e498c555309815ecae5bb9ece0170a774cb2977d 100644 (file)
@@ -61,3 +61,5 @@ GTAGS
 
 # editor backup files etc.
 \#*\#
+
+.idea
index 1be8243d34d45ef10129a48ca7d592af2674b5df..1f10ba8899b155e5f8dac4f38111e21a72a922e2 100644 (file)
@@ -95,6 +95,9 @@ class DashboardTestCase(MgrTestCase):
         else:
             assert False
         try:
+            if not cls._resp.ok:
+                # Output response for easier debugging.
+                log.error("Request response: %s", cls._resp.text)
             if cls._resp.text and cls._resp.text != "":
                 return cls._resp.json()
             return cls._resp.text
index 0c73b142a4cf68492e10036bbd516ccb7b45bdee..70a0f6fac474af6048da5ba698bf006d5963bc4b 100644 (file)
@@ -4,7 +4,7 @@ import urllib
 import logging
 logger = logging.getLogger(__name__)
 
-from .helper import DashboardTestCase, authenticate
+from .helper import DashboardTestCase, authenticate, JObj, JList, JLeaf
 
 
 class RgwControllerTest(DashboardTestCase):
@@ -53,7 +53,6 @@ class RgwProxyExceptionsTest(DashboardTestCase):
     @classmethod
     def setUpClass(cls):
         super(RgwProxyExceptionsTest, cls).setUpClass()
-
         cls._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', ''])
         cls._ceph_cmd(['dashboard', 'set-rgw-api-access-key', ''])
 
@@ -65,6 +64,7 @@ class RgwProxyExceptionsTest(DashboardTestCase):
 
 
 class RgwProxyTest(DashboardTestCase):
+
     @classmethod
     def setUpClass(cls):
         super(RgwProxyTest, cls).setUpClass()
@@ -101,14 +101,20 @@ class RgwProxyTest(DashboardTestCase):
         self.assertStatus(200)
 
         data = self._get(
-            '/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
+            '/api/rgw/proxy/user',
+            params={
+                'uid': 'teuth-test-user'
+            })
 
         self.assertStatus(200)
         self.assertEqual(data['user_id'], 'teuth-test-user')
 
     def _test_get(self):
         data = self._get(
-            '/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
+            '/api/rgw/proxy/user',
+            params={
+                'uid': 'teuth-test-user'
+            })
 
         self._assert_user_data(data)
         self.assertStatus(200)
@@ -128,10 +134,9 @@ class RgwProxyTest(DashboardTestCase):
         self.assertEqual(self._resp.json()['display_name'], 'new name')
 
     def _test_delete(self):
-        self._delete('/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
-        self.assertStatus(200)
-
-        self._delete('/api/rgw/proxy/user', params={'uid': 'teuth-test-user'})
+        self._delete('/api/rgw/user/teuth-test-user')
+        self.assertStatus(204)
+        self._delete('/api/rgw/user/teuth-test-user')
         self.assertStatus(500)
         resp = self._resp.json()
         self.assertIn('detail', resp)
@@ -140,6 +145,13 @@ class RgwProxyTest(DashboardTestCase):
         self.assertIn('"HostId"', resp['detail'])
         self.assertIn('"RequestId"', resp['detail'])
 
+    @authenticate
+    def test_user_list(self):
+        data = self._get('/api/rgw/proxy/metadata/user')
+        self.assertStatus(200)
+        self.assertGreaterEqual(len(data), 1)
+        self.assertIn('admin', data)
+
     @authenticate
     def test_rgw_proxy(self):
         """Test basic request types"""
@@ -156,3 +168,326 @@ class RgwProxyTest(DashboardTestCase):
 
         # DELETE - Delete the user
         self._test_delete()
+
+
+class RgwTestCase(DashboardTestCase):
+
+    maxDiff = None
+    create_test_user = False
+
+    @classmethod
+    def setUpClass(cls):
+        super(RgwTestCase, cls).setUpClass()
+        # Create the administrator account.
+        cls._radosgw_admin_cmd([
+            'user', 'create', '--uid=admin', '--display-name=admin',
+            '--system', '--access-key=admin', '--secret=admin'
+        ])
+        # Update the dashboard configuration.
+        cls._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
+        cls._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
+        # Create a test user?
+        if cls.create_test_user:
+            cls._radosgw_admin_cmd([
+                'user', 'create', '--uid', 'teuth-test-user', '--display-name',
+                'teuth-test-user'
+            ])
+            cls._radosgw_admin_cmd([
+                'caps', 'add', '--uid', 'teuth-test-user', '--caps',
+                'metadata=write'
+            ])
+            cls._radosgw_admin_cmd([
+                'subuser', 'create', '--uid', 'teuth-test-user', '--subuser',
+                'teuth-test-subuser', '--access', 'full', '--key-type', 's3',
+                '--access-key', 'xyz123'
+            ])
+            cls._radosgw_admin_cmd([
+                'subuser', 'create', '--uid', 'teuth-test-user', '--subuser',
+                'teuth-test-subuser2', '--access', 'full', '--key-type',
+                'swift'
+            ])
+
+    @classmethod
+    def tearDownClass(cls):
+        if cls.create_test_user:
+            cls._radosgw_admin_cmd(['user', 'rm', '--uid=teuth-test-user'])
+        super(RgwTestCase, cls).tearDownClass()
+
+
+class RgwBucketTest(RgwTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        cls.create_test_user = True
+        super(RgwBucketTest, cls).setUpClass()
+        # Create a tenanted user.
+        cls._radosgw_admin_cmd([
+            'user', 'create', '--tenant', 'testx', '--uid', 'teuth-test-user',
+            '--display-name', 'tenanted teuth-test-user'
+        ])
+
+    @classmethod
+    def tearDownClass(cls):
+        cls._radosgw_admin_cmd(
+            ['user', 'rm', '--tenant', 'testx', '--uid=teuth-test-user'])
+        super(RgwBucketTest, cls).tearDownClass()
+
+    @authenticate
+    def test_all(self):
+        # Create a new bucket.
+        self._post(
+            '/api/rgw/bucket',
+            params={
+                'bucket': 'teuth-test-bucket',
+                'uid': 'admin'
+            })
+        self.assertStatus(201)
+        data = self.jsonBody()
+        self.assertSchema(data, JObj(sub_elems={
+            'bucket_info': JObj(sub_elems={
+                'bucket': JObj(allow_unknown=True, sub_elems={
+                    'name': JLeaf(str),
+                    'bucket_id': JLeaf(str),
+                    'tenant': JLeaf(str)
+                }),
+                'quota': JObj(sub_elems={}, allow_unknown=True),
+                'creation_time': JLeaf(str)
+            }, allow_unknown=True)
+        }, allow_unknown=True))
+        data = data['bucket_info']['bucket']
+        self.assertEqual(data['name'], 'teuth-test-bucket')
+        self.assertEqual(data['tenant'], '')
+
+        # List all buckets.
+        data = self._get('/api/rgw/proxy/bucket')
+        self.assertStatus(200)
+        self.assertEqual(len(data), 1)
+        self.assertIn('teuth-test-bucket', data)
+
+        # Get the bucket.
+        data = self._get('/api/rgw/bucket/teuth-test-bucket')
+        self.assertStatus(200)
+        self.assertSchema(data, JObj(sub_elems={
+            'id': JLeaf(str),
+            'bid': JLeaf(str),
+            'tenant': JLeaf(str),
+            'bucket': JLeaf(str),
+            'bucket_quota': JObj(sub_elems={}, allow_unknown=True),
+            'owner': JLeaf(str)
+        }, allow_unknown=True))
+        self.assertEqual(data['bucket'], 'teuth-test-bucket')
+        self.assertEqual(data['owner'], 'admin')
+
+        # Update the bucket.
+        self._put(
+            '/api/rgw/proxy/bucket',
+            params={
+                'bucket': 'teuth-test-bucket',
+                'bucket-id': data['id'],
+                'uid': 'teuth-test-user'
+            })
+        self.assertStatus(200)
+        data = self._get('/api/rgw/bucket/teuth-test-bucket')
+        self.assertStatus(200)
+        self.assertSchema(data, JObj(sub_elems={
+            'owner': JLeaf(str),
+            'bid': JLeaf(str),
+            'tenant': JLeaf(str)
+        }, allow_unknown=True))
+        self.assertEqual(data['owner'], 'teuth-test-user')
+
+        # Delete the bucket.
+        self._delete('/api/rgw/proxy/bucket', params={
+            'bucket': 'teuth-test-bucket',
+            'purge-objects': 'true'
+        })
+        self.assertStatus(200)
+        data = self._get('/api/rgw/proxy/bucket')
+        self.assertStatus(200)
+        self.assertEqual(len(data), 0)
+
+    @authenticate
+    def test_create_get_update_delete_w_tenant(self):
+        # Create a new bucket. The tenant of the user is used when
+        # the bucket is created.
+        self._post(
+            '/api/rgw/bucket',
+            params={
+                'bucket': 'teuth-test-bucket',
+                'uid': 'testx$teuth-test-user'
+            })
+        self.assertStatus(201)
+        # It's not possible to validate the result because there
+        # IS NO result object returned by the RGW Admin OPS API
+        # when a tenanted bucket is created.
+        data = self.jsonBody()
+        self.assertIsNone(data)
+
+        # List all buckets.
+        data = self._get('/api/rgw/proxy/bucket')
+        self.assertStatus(200)
+        self.assertEqual(len(data), 1)
+        self.assertIn('testx/teuth-test-bucket', data)
+
+        # Get the bucket.
+        data = self._get('/api/rgw/bucket/{}'.format(
+            urllib.quote_plus('testx/teuth-test-bucket')))
+        self.assertStatus(200)
+        self.assertSchema(data, JObj(sub_elems={
+            'owner': JLeaf(str),
+            'bucket': JLeaf(str),
+            'tenant': JLeaf(str),
+            'bid': JLeaf(str)
+        }, allow_unknown=True))
+        self.assertEqual(data['owner'], 'testx$teuth-test-user')
+        self.assertEqual(data['bucket'], 'teuth-test-bucket')
+        self.assertEqual(data['tenant'], 'testx')
+        self.assertEqual(data['bid'], 'testx/teuth-test-bucket')
+
+        # Update the bucket.
+        # Currently it is not possible to link such a bucket that is
+        # owned by a tenanted user to a 'normal' user.
+        # See http://tracker.ceph.com/issues/39991
+        #self._put(
+        #    '/api/rgw/proxy/bucket',
+        #    params={
+        #        'bucket': urllib.quote_plus('testx/teuth-test-bucket'),
+        #        'bucket-id': data['id'],
+        #        'uid': 'admin'
+        #    })
+        #self.assertStatus(200)
+        #data = self._get('/api/rgw/bucket/{}'.format(
+        #    urllib.quote_plus('testx/teuth-test-bucket')))
+        #self.assertStatus(200)
+        #self.assertIn('owner', data)
+        #self.assertEqual(data['owner'], 'admin')
+
+        # Delete the bucket.
+        self._delete('/api/rgw/proxy/bucket', params={
+            'bucket': urllib.quote_plus('testx/teuth-test-bucket'),
+            'purge-objects': 'true'
+        })
+        self.assertStatus(200)
+        data = self._get('/api/rgw/proxy/bucket')
+        self.assertStatus(200)
+        self.assertEqual(len(data), 0)
+
+
+class RgwUserTest(RgwTestCase):
+
+    def _assert_user_data(self, data):
+        self.assertSchema(data, JObj(sub_elems={
+            'caps': JList(JObj(sub_elems={}, allow_unknown=True)),
+            'display_name': JLeaf(str),
+            'email': JLeaf(str),
+            'keys': JList(JObj(sub_elems={}, allow_unknown=True)),
+            'max_buckets': JLeaf(int),
+            'subusers': JList(JLeaf(str)),
+            'suspended': JLeaf(int),
+            'swift_keys': JList(JObj(sub_elems={}, allow_unknown=True)),
+            'tenant': JLeaf(str),
+            'user_id': JLeaf(str)
+        }, allow_unknown=True))
+        self.assertGreaterEqual(len(data['keys']), 1)
+
+    @authenticate
+    def test_get(self):
+        data = self._get('/api/rgw/user/admin')
+        self.assertStatus(200)
+        self._assert_user_data(data)
+        self.assertIn('uid', data)
+        self.assertEqual(data['user_id'], 'admin')
+
+    @authenticate
+    def test_create_get_update_delete(self):
+        # Create a new user.
+        self._put(
+            '/api/rgw/proxy/user',
+            params={
+                'uid': 'teuth-test-user',
+                'display-name': 'display name'
+            })
+        self.assertStatus(200)
+        data = self.jsonBody()
+        self._assert_user_data(data)
+        self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['display_name'], 'display name')
+
+        # Get the user.
+        data = self._get('/api/rgw/user/teuth-test-user')
+        self.assertStatus(200)
+        self._assert_user_data(data)
+        self.assertEqual(data['tenant'], '')
+        self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['uid'], 'teuth-test-user')
+
+        # Update the user.
+        self._post(
+            '/api/rgw/proxy/user',
+            params={
+                'uid': 'teuth-test-user',
+                'display-name': 'new name',
+            })
+        self.assertStatus(200)
+        data = self.jsonBody()
+        self._assert_user_data(data)
+        self.assertEqual(data['display_name'], 'new name')
+
+        # Delete the user.
+        self._delete('/api/rgw/user/teuth-test-user')
+        self.assertStatus(204)
+        self._get('/api/rgw/user/teuth-test-user')
+        self.assertStatus(500)
+        resp = self.jsonBody()
+        self.assertIn('detail', resp)
+        self.assertIn('failed request with status code 404', resp['detail'])
+        self.assertIn('"Code":"NoSuchUser"', resp['detail'])
+        self.assertIn('"HostId"', resp['detail'])
+        self.assertIn('"RequestId"', resp['detail'])
+
+    @authenticate
+    def test_create_get_update_delete_w_tenant(self):
+        # Create a new user.
+        self._put(
+            '/api/rgw/proxy/user',
+            params={
+                'uid': 'test01$teuth-test-user',
+                'display-name': 'display name'
+            })
+        self.assertStatus(200)
+        data = self.jsonBody()
+        self._assert_user_data(data)
+        self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['display_name'], 'display name')
+
+        # Get the user.
+        data = self._get('/api/rgw/user/test01$teuth-test-user')
+        self.assertStatus(200)
+        self._assert_user_data(data)
+        self.assertEqual(data['tenant'], 'test01')
+        self.assertEqual(data['user_id'], 'teuth-test-user')
+        self.assertEqual(data['uid'], 'test01$teuth-test-user')
+
+        # Update the user.
+        self._post(
+            '/api/rgw/proxy/user',
+            params={
+                'uid': 'test01$teuth-test-user',
+                'display-name': 'new name'
+            })
+        self.assertStatus(200)
+        data = self.jsonBody()
+        self._assert_user_data(data)
+        self.assertEqual(data['display_name'], 'new name')
+
+        # Delete the user.
+        self._delete('/api/rgw/user/test01$teuth-test-user')
+        self.assertStatus(204)
+        self._get('/api/rgw/user/test01$teuth-test-user')
+        self.assertStatus(500)
+        resp = self.jsonBody()
+        self.assertIn('detail', resp)
+        self.assertIn('failed request with status code 404', resp['detail'])
+        self.assertIn('"Code":"NoSuchUser"', resp['detail'])
+        self.assertIn('"HostId"', resp['detail'])
+        self.assertIn('"RequestId"', resp['detail'])
index 0bc0daf02367c942709d92c743c476956edf7430..9951d24f58b18e86f9e1e010531665d052b6324f 100644 (file)
@@ -2,6 +2,7 @@
 from __future__ import absolute_import
 
 import json
+import sys
 import cherrypy
 
 from . import ApiController, BaseController, RESTController, AuthRequired
@@ -10,6 +11,11 @@ from ..services.ceph_service import CephService
 from ..services.rgw_client import RgwClient
 from ..rest_client import RequestException
 
+if sys.version_info >= (3, 0):
+    from urllib.parse import unquote  # pylint: disable=no-name-in-module,import-error
+else:
+    from urllib import unquote  # pylint: disable=no-name-in-module
+
 
 @ApiController('rgw')
 @AuthRequired()
@@ -32,8 +38,8 @@ class Rgw(RESTController):
                     instance.userid)
                 raise RequestException(status['message'])
             status['available'] = True
-        except RequestException:
-            pass
+        except (RequestException, LookupError) as ex:
+            status['message'] = str(ex)
         return status
 
 
@@ -99,6 +105,12 @@ class RgwProxy(BaseController):
             if cherrypy.request.body.length:
                 data = cherrypy.request.body.read()
 
+            for key, value in params.items():
+                # pylint: disable=undefined-variable
+                if (sys.version_info < (3, 0) and isinstance(value, unicode)) \
+                        or isinstance(value, str):
+                    params[key] = unquote(value)
+
             return rgw_client.proxy(method, path, params, data)
         except RequestException as e:
             # Always use status code 500 and NOT the status that may delivered
@@ -109,9 +121,49 @@ class RgwProxy(BaseController):
             return json.dumps({'detail': str(e)}).encode('utf-8')
 
 
+class RgwRESTController(RESTController):
+
+    @staticmethod
+    def proxy(method, path, params=None, json_response=True):
+        instance = RgwClient.admin_instance()
+        result = instance.proxy(method, path, params, None)
+        if json_response:
+            result = result.decode('utf-8')
+            if result != '':
+                result = json.loads(result)
+            else:
+                result = {}
+        return result
+
+
 @ApiController('rgw/bucket')
 @AuthRequired()
-class RgwBucket(RESTController):
+class RgwBucket(RgwRESTController):
+
+    @staticmethod
+    def _append_bid(bucket):
+        """
+        Append the bucket identifier that looks like [<tenant>/]<bucket>.
+        See http://docs.ceph.com/docs/nautilus/radosgw/multitenancy/ for
+        more information.
+        :param bucket: The bucket parameters.
+        :type bucket: dict
+        :return: The modified bucket parameters including the 'bid' parameter.
+        :rtype: dict
+        """
+        if isinstance(bucket, dict):
+            bucket['bid'] = '{}/{}'.format(bucket['tenant'], bucket['bucket']) \
+                if bucket['tenant'] else bucket['bucket']
+        return bucket
+
+    def get(self, bucket):
+        try:
+            result = self.proxy('GET', 'bucket', {'bucket': bucket})
+            return self._append_bid(result)
+        except RequestException as e:
+            cherrypy.response.headers['Content-Type'] = 'application/json'
+            cherrypy.response.status = 500
+            return {'detail': str(e)}
 
     def create(self, bucket, uid):
         try:
@@ -125,20 +177,43 @@ class RgwBucket(RESTController):
 
 @ApiController('rgw/user')
 @AuthRequired()
-class RgwUser(RESTController):
+class RgwUser(RgwRESTController):
+
+    @staticmethod
+    def _append_uid(user):
+        """
+        Append the user identifier that looks like [<tenant>$]<user>.
+        See http://docs.ceph.com/docs/jewel/radosgw/multitenancy/ for
+        more information.
+        :param user: The user parameters.
+        :type user: dict
+        :return: The modified user parameters including the 'uid' parameter.
+        :rtype: dict
+        """
+        if isinstance(user, dict):
+            user['uid'] = '{}${}'.format(user['tenant'], user['user_id']) \
+                if user['tenant'] else user['user_id']
+        return user
+
+    def get(self, uid):
+        try:
+            result = self.proxy('GET', 'user', {'uid': uid})
+            return self._append_uid(result)
+        except RequestException as e:
+            cherrypy.response.headers['Content-Type'] = 'application/json'
+            cherrypy.response.status = 500
+            return {'detail': str(e)}
 
     def delete(self, uid):
         try:
             rgw_client = RgwClient.admin_instance()
-
             # Ensure the user is not configured to access the Object Gateway.
             if rgw_client.userid == uid:
                 raise RequestException('Unable to delete "{}" - this user '
                                        'account is required for managing the '
                                        'Object Gateway'.format(uid))
-
             # Finally redirect request to the RGW proxy.
-            return rgw_client.proxy('DELETE', 'user', cherrypy.request.params, None)
+            return self.proxy('DELETE', 'user', cherrypy.request.params)
         except RequestException as e:
             cherrypy.response.headers['Content-Type'] = 'application/json'
             cherrypy.response.status = 500
index bca500fff68a0e9344f92821827da34e3152cd44..1a324a51e07b20516499f1ebc3361fac67b0a654 100644 (file)
@@ -75,7 +75,7 @@ const routes: Routes = [
         canActivate: [AuthGuardService]
       },
       {
-        path: 'bucket/edit/:bucket',
+        path: 'bucket/edit/:bid',
         component: RgwBucketFormComponent,
         canActivate: [AuthGuardService]
       }
index 306d6de9d02bb4ba2b00a1eb83316ada3551ada7..3ccb9cb684f061a82a40face882dd12272e93775 100644 (file)
@@ -6,7 +6,7 @@
           <tr>
             <td i18n
                 class="bold col-sm-1">Name</td>
-            <td class="col-sm-3">{{ bucket.bucket }}</td>
+            <td class="col-sm-3">{{ bucket.bid }}</td>
           </tr>
           <tr>
             <td i18n
index 1682b433b7f17c6d770a14d890f07048f27e30bc..29b37b35ec10f188b99cf2ba7f315a846772504e 100644 (file)
 
         <!-- Name -->
         <div class="form-group"
-             [ngClass]="{'has-error': (frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.invalid}">
+             [ngClass]="{'has-error': (frm.submitted || bucketForm.controls.bid.dirty) && bucketForm.controls.bid.invalid}">
           <label i18n
                  class="control-label col-sm-3"
-                 for="bucket">Name
+                 for="bid">Name
             <span class="required"
                   *ngIf="!editing"></span>
           </label>
           <div class="col-sm-9">
-            <input id="bucket"
-                   name="bucket"
+            <input id="bid"
                    class="form-control"
                    type="text"
                    i18n-placeholder
                    placeholder="Name..."
-                   formControlName="bucket"
+                   formControlName="bid"
                    [readonly]="editing"
                    autofocus>
             <span i18n
                   class="help-block"
-                  *ngIf="(frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.hasError('required')">
+                  *ngIf="(frm.submitted || bucketForm.controls.bid.dirty) && bucketForm.controls.bid.hasError('required')">
               This field is required.
             </span>
             <span i18n
                   class="help-block"
-                  *ngIf="(frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.hasError('bucketNameInvalid')">
+                  *ngIf="(frm.submitted || bucketForm.controls.bid.dirty) && bucketForm.controls.bid.hasError('bucketNameInvalid')">
               The value is not valid.
             </span>
             <span i18n
                   class="help-block"
-                  *ngIf="(frm.submitted || bucketForm.controls.bucket.dirty) && bucketForm.controls.bucket.hasError('bucketNameExists')">
+                  *ngIf="(frm.submitted || bucketForm.controls.bid.dirty) && bucketForm.controls.bid.hasError('bucketNameExists')">
               The chosen name is already in use.
             </span>
           </div>
index 6d70a176c6349b94ccf2657881c7f1524bf18ae8..4ec17395b55ad729b83a48bff3f561f3220d4810 100644 (file)
@@ -39,7 +39,7 @@ export class RgwBucketFormComponent implements OnInit {
   createForm() {
     this.bucketForm = this.formBuilder.group({
       id: [null],
-      bucket: [null, [Validators.required], [this.bucketNameValidator()]],
+      bid: [null, [Validators.required], [this.bucketNameValidator()]],
       owner: [null, [Validators.required]]
     });
   }
@@ -52,15 +52,15 @@ export class RgwBucketFormComponent implements OnInit {
 
     // Process route parameters.
     this.route.params.subscribe(
-      (params: { bucket: string }) => {
-        if (!params.hasOwnProperty('bucket')) {
+      (params: { bid: string }) => {
+        if (!params.hasOwnProperty('bid')) {
           return;
         }
-        params.bucket = decodeURIComponent(params.bucket);
+        const bid = decodeURIComponent(params.bid);
         this.loading = true;
         // Load the bucket data in 'edit' mode.
         this.editing = true;
-        this.rgwBucketService.get(params.bucket).subscribe((resp: object) => {
+        this.rgwBucketService.get(bid).subscribe((resp: object) => {
           this.loading = false;
           // Get the default values.
           const defaults = _.clone(this.bucketForm.value);
@@ -87,7 +87,7 @@ export class RgwBucketFormComponent implements OnInit {
     if (this.bucketForm.pristine) {
       this.goToListView();
     }
-    const bucketCtl = this.bucketForm.get('bucket');
+    const bucketCtl = this.bucketForm.get('bid');
     const ownerCtl = this.bucketForm.get('owner');
     if (this.editing) {
       // Edit
index 4355083213a63fc7d8ebeb53b76cd30165d8436e..59215b6c1b7cb4046fae29ad5d14dffda219e936 100644 (file)
@@ -29,7 +29,7 @@
       <button type="button"
               class="btn btn-sm btn-primary"
               *ngIf="selection.hasSingleSelection"
-              routerLink="/rgw/bucket/edit/{{ selection.first()?.bucket | encodeUri }}">
+              routerLink="/rgw/bucket/edit/{{ selection.first()?.bid | encodeUri }}">
         <i class="fa fa-fw fa-pencil"></i>
         <ng-container i18n>Edit</ng-container>
       </button>
@@ -60,7 +60,7 @@
         <li role="menuitem"
             [ngClass]="{'disabled': !selection.hasSingleSelection}">
           <a class="dropdown-item"
-             routerLink="/rgw/bucket/edit/{{ selection.first()?.bucket | encodeUri }}"
+             routerLink="/rgw/bucket/edit/{{ selection.first()?.bid | encodeUri }}"
              i18n>
             <i class="fa fa-fw fa-pencil"></i>
             Edit
index 749460968e6f9bc08de681138e8323e2d9fbac3a..944499f9398bfddc1e06b8bb2740a6770aefd169 100644 (file)
@@ -34,7 +34,7 @@ export class RgwBucketListComponent {
     this.columns = [
       {
         name: 'Name',
-        prop: 'bucket',
+        prop: 'bid',
         flexGrow: 1
       },
       {
index ca51f56970b2d9e3cdd76861827d94e63719d69f..dae4103a2cd502b44b6553842539091dece25187 100644 (file)
@@ -6,7 +6,7 @@
           <tr>
             <td i18n
                 class="bold col-sm-1">Username</td>
-            <td class="col-sm-3">{{ user.user_id }}</td>
+            <td class="col-sm-3">{{ user.uid }}</td>
           </tr>
           <tr>
             <td i18n
index 5c852d55c1529e84ce37d7fa9d8b5be4f6f7ddd4..c0d9e59e80373dca110ba5bbb9d769467c8a64ed 100644 (file)
 
         <!-- Username -->
         <div class="form-group"
-             [ngClass]="{'has-error': (frm.submitted || userForm.controls.user_id.dirty) && userForm.controls.user_id.invalid}">
+             [ngClass]="{'has-error': (frm.submitted || userForm.controls.uid.dirty) && userForm.controls.uid.invalid}">
           <label class="control-label col-sm-3"
-                 for="user_id"
+                 for="uid"
                  i18n>Username
             <span class="required"
                   *ngIf="!editing">
             </span>
           </label>
           <div class="col-sm-9">
-            <input id="user_id"
+            <input id="uid"
                    class="form-control"
                    type="text"
-                   formControlName="user_id"
+                   formControlName="uid"
                    [readonly]="editing"
                    autofocus>
             <span class="help-block"
-                  *ngIf="(frm.submitted || userForm.controls.user_id.dirty) && userForm.controls.user_id.hasError('required')"
+                  *ngIf="(frm.submitted || userForm.controls.uid.dirty) && userForm.controls.uid.hasError('required')"
                   i18n>
               This field is required.
             </span>
             <span class="help-block"
-                  *ngIf="(frm.submitted || userForm.controls.user_id.dirty) && userForm.controls.user_id.hasError('userIdExists')"
+                  *ngIf="(frm.submitted || userForm.controls.uid.dirty) && userForm.controls.uid.hasError('userIdExists')"
                   i18n>
               The chosen user ID is already in use.
             </span>
           <!-- Maximum size -->
           <div class="form-group"
                [ngClass]="{'has-error': (frm.submitted || userForm.controls.user_quota_max_size.dirty) && userForm.controls.user_quota_max_size.invalid}"
-               *ngIf="!userForm.controls.user_quota_max_size_unlimited.value">
+               *ngIf="userForm.controls.user_quota_enabled.value && !userForm.controls.user_quota_max_size_unlimited.value">
             <label class="control-label col-sm-3"
                    for="user_quota_max_size"
                    i18n>Max. size
           <!-- Maximum objects -->
           <div class="form-group"
                [ngClass]="{'has-error': (frm.submitted || userForm.controls.user_quota_max_objects.dirty) && userForm.controls.user_quota_max_objects.invalid}"
-               *ngIf="!userForm.controls.user_quota_max_objects_unlimited.value">
+               *ngIf="userForm.controls.user_quota_enabled.value && !userForm.controls.user_quota_max_objects_unlimited.value">
             <label class="control-label col-sm-3"
                    for="user_quota_max_objects"
                    i18n>Max. objects
           <!-- Maximum size -->
           <div class="form-group"
                [ngClass]="{'has-error': (frm.submitted || userForm.controls.bucket_quota_max_size.dirty) && userForm.controls.bucket_quota_max_size.invalid}"
-               *ngIf="!userForm.controls.bucket_quota_max_size_unlimited.value">
+               *ngIf="userForm.controls.bucket_quota_enabled.value && !userForm.controls.bucket_quota_max_size_unlimited.value">
             <label class="control-label col-sm-3"
                    for="bucket_quota_max_size"
                    i18n>Max. size
           <!-- Maximum objects -->
           <div class="form-group"
                [ngClass]="{'has-error': (frm.submitted || userForm.controls.bucket_quota_max_objects.dirty) && userForm.controls.bucket_quota_max_objects.invalid}"
-               *ngIf="!userForm.controls.bucket_quota_max_objects_unlimited.value">
+               *ngIf="userForm.controls.bucket_quota_enabled.value && !userForm.controls.bucket_quota_max_objects_unlimited.value">
             <label class="control-label col-sm-3"
                    for="bucket_quota_max_objects"
                    i18n>Max. objects
index 850303bd2b30240f3f5d832a18a946e59a7290f0..4656560d1d2e3850a44e585ce356ab41f2ff3d6d 100644 (file)
@@ -57,7 +57,7 @@ export class RgwUserFormComponent implements OnInit {
   createForm() {
     this.userForm = this.formBuilder.group({
       // General
-      user_id: [null, [Validators.required], [this.userIdValidator()]],
+      uid: [null, [Validators.required], [this.userIdValidator()]],
       display_name: [null, [Validators.required]],
       email: [null, [CdValidators.email]],
       max_buckets: [null, [Validators.min(0)]],
@@ -156,13 +156,14 @@ export class RgwUserFormComponent implements OnInit {
       if (!params.hasOwnProperty('uid')) {
         return;
       }
+      const uid = decodeURIComponent(params.uid);
       this.loading = true;
       // Load the user data in 'edit' mode.
       this.editing = true;
       // Load the user and quota information.
       const observables = [];
-      observables.push(this.rgwUserService.get(params.uid));
-      observables.push(this.rgwUserService.getQuota(params.uid));
+      observables.push(this.rgwUserService.get(uid));
+      observables.push(this.rgwUserService.getQuota(uid));
       Observable.forkJoin(observables).subscribe(
         (resp: any[]) => {
           this.loading = false;
@@ -182,8 +183,8 @@ export class RgwUserFormComponent implements OnInit {
               value[type + '_quota_max_size'] = `${quota.max_size} B`;
             }
             if (quota.max_objects < 0) {
-              value[type + '_quota_max_size_unlimited'] = true;
-              value[type + '_quota_max_size'] = null;
+              value[type + '_quota_max_objects_unlimited'] = true;
+              value[type + '_quota_max_objects'] = null;
             } else {
               value[type + '_quota_max_objects_unlimited'] = false;
               value[type + '_quota_max_objects'] = quota.max_objects;
@@ -310,7 +311,7 @@ export class RgwUserFormComponent implements OnInit {
       // Create an observable to modify the subuser when the form is submitted.
       this.submitObservables.push(
         this.rgwUserService.addSubuser(
-          this.userForm.get('user_id').value,
+          this.userForm.get('uid').value,
           subuser.id,
           subuser.permissions,
           subuser.secret_key,
@@ -323,7 +324,7 @@ export class RgwUserFormComponent implements OnInit {
       // Create an observable to add the subuser when the form is submitted.
       this.submitObservables.push(
         this.rgwUserService.addSubuser(
-          this.userForm.get('user_id').value,
+          this.userForm.get('uid').value,
           subuser.id,
           subuser.permissions,
           subuser.secret_key,
@@ -350,7 +351,7 @@ export class RgwUserFormComponent implements OnInit {
     const subuser = this.subusers[index];
     // Create an observable to delete the subuser when the form is submitted.
     this.submitObservables.push(
-      this.rgwUserService.deleteSubuser(this.userForm.get('user_id').value, subuser.id)
+      this.rgwUserService.deleteSubuser(this.userForm.get('uid').value, subuser.id)
     );
     // Remove the associated S3 keys.
     this.s3Keys = this.s3Keys.filter((key) => {
@@ -370,7 +371,7 @@ export class RgwUserFormComponent implements OnInit {
    * Add/Update a capability.
    */
   setCapability(cap: RgwUserCapability, index?: number) {
-    const uid = this.userForm.get('user_id').value;
+    const uid = this.userForm.get('uid').value;
     if (_.isNumber(index)) {
       // Modify
       const oldCap = this.capabilities[index];
@@ -402,7 +403,7 @@ export class RgwUserFormComponent implements OnInit {
     const cap = this.capabilities[index];
     // Create an observable to delete the capability when the form is submitted.
     this.submitObservables.push(
-      this.rgwUserService.deleteCapability(this.userForm.get('user_id').value, cap.type, cap.perm)
+      this.rgwUserService.deleteCapability(this.userForm.get('uid').value, cap.type, cap.perm)
     );
     // Remove the capability to update the UI.
     this.capabilities.splice(index, 1);
@@ -451,7 +452,7 @@ export class RgwUserFormComponent implements OnInit {
     const key = this.s3Keys[index];
     // Create an observable to delete the S3 key when the form is submitted.
     this.submitObservables.push(
-      this.rgwUserService.deleteS3Key(this.userForm.get('user_id').value, key.access_key)
+      this.rgwUserService.deleteS3Key(this.userForm.get('uid').value, key.access_key)
     );
     // Remove the S3 key to update the UI.
     this.s3Keys.splice(index, 1);
@@ -464,7 +465,7 @@ export class RgwUserFormComponent implements OnInit {
    * @param {number | undefined} index The subuser to show.
    */
   showSubuserModal(index?: number) {
-    const uid = this.userForm.get('user_id').value;
+    const uid = this.userForm.get('uid').value;
     const modalRef = this.bsModalService.show(RgwUserSubuserModalComponent);
     if (_.isNumber(index)) {
       // Edit
@@ -583,7 +584,7 @@ export class RgwUserFormComponent implements OnInit {
    */
   private _getApiPutArgs() {
     const result = {
-      uid: this.userForm.get('user_id').value,
+      uid: this.userForm.get('uid').value,
       'display-name': this.userForm.get('display_name').value
     };
     const suspendedCtl = this.userForm.get('suspended');
@@ -616,7 +617,7 @@ export class RgwUserFormComponent implements OnInit {
    */
   private _getApiPostArgs() {
     const result = {
-      uid: this.userForm.get('user_id').value
+      uid: this.userForm.get('uid').value
     };
     const argsMap = {
       'display-name': 'display_name',
@@ -639,7 +640,7 @@ export class RgwUserFormComponent implements OnInit {
    */
   private _getApiUserQuotaArgs(): object {
     const result = {
-      uid: this.userForm.get('user_id').value,
+      uid: this.userForm.get('uid').value,
       'quota-type': 'user',
       enabled: this.userForm.get('user_quota_enabled').value,
       'max-size-kb': -1,
@@ -663,7 +664,7 @@ export class RgwUserFormComponent implements OnInit {
    */
   private _getApiBucketQuotaArgs(): object {
     const result = {
-      uid: this.userForm.get('user_id').value,
+      uid: this.userForm.get('uid').value,
       'quota-type': 'bucket',
       enabled: this.userForm.get('bucket_quota_enabled').value,
       'max-size-kb': -1,
@@ -690,9 +691,9 @@ export class RgwUserFormComponent implements OnInit {
   private _getS3KeyUserCandidates() {
     let result = [];
     // Add the current user id.
-    const user_id = this.userForm.get('user_id').value;
-    if (_.isString(user_id) && !_.isEmpty(user_id)) {
-      result.push(user_id);
+    const uid = this.userForm.get('uid').value;
+    if (_.isString(uid) && !_.isEmpty(uid)) {
+      result.push(uid);
     }
     // Append the subusers.
     this.subusers.forEach((subUser) => {
index b39c007a3e587be7f825ff907767e3b1605c0121..fca896a924cfcb3d161752ca5646d993c0275acc 100644 (file)
@@ -14,7 +14,7 @@
           columnMode="flex"
           selectionType="multi"
           (updateSelection)="updateSelection($event)"
-          identifier="user_id"
+          identifier="uid"
           (fetchData)="getUserList()">
   <div class="table-actions">
     <div class="btn-group" dropdown>
@@ -28,7 +28,7 @@
       <button type="button"
               class="btn btn-sm btn-primary"
               *ngIf="selection.hasSingleSelection"
-              routerLink="/rgw/user/edit/{{ selection.first()?.user_id }}">
+              routerLink="/rgw/user/edit/{{ selection.first()?.uid | encodeUri }}">
         <i class="fa fa-fw fa-pencil"></i>
         <ng-container i18n>Edit</ng-container>
       </button>
@@ -59,7 +59,7 @@
         <li role="menuitem"
             [ngClass]="{'disabled': !selection.hasSingleSelection}">
           <a class="dropdown-item"
-             routerLink="/rgw/user/edit/{{ selection.first()?.user_id }}"
+             routerLink="/rgw/user/edit/{{ selection.first()?.uid | encodeUri }}"
              i18n>
             <i class="fa fa-fw fa-pencil"></i>
             Edit
index 0e6b129f323230c59ba9f2b85a144b5535c4ade5..ae2f2f2936f104b2052eb351c1377881dd2e5936 100644 (file)
@@ -35,7 +35,7 @@ export class RgwUserListComponent {
     this.columns = [
       {
         name: 'Username',
-        prop: 'user_id',
+        prop: 'uid',
         flexGrow: 1
       },
       {
@@ -88,7 +88,7 @@ export class RgwUserListComponent {
           // Delete all selected data table rows.
           Observable.forkJoin(
             this.selection.selected.map((user: any) => {
-              return this.rgwUserService.delete(user.user_id);
+              return this.rgwUserService.delete(user.uid);
             })
           ).subscribe(
             null,
index 2db300583cf55d08fd4cbf329acb5e1b094d2202..148049ca80565e732d7fdf87700af40730d64d86 100644 (file)
@@ -42,17 +42,14 @@ export class RgwBucketService {
   }
 
   get(bucket: string) {
-    let params = new HttpParams();
-    params = params.append('bucket', bucket);
-    return this.http.get(this.url, {params: params});
+    return this.http.get(`api/rgw/bucket/${bucket}`);
   }
 
   create(bucket: string, uid: string) {
-    const body = {
-      'bucket': bucket,
-      'uid': uid
-    };
-    return this.http.post('api/rgw/bucket', body);
+    let params = new HttpParams();
+    params = params.append('bucket', bucket);
+    params = params.append('uid', uid);
+    return this.http.post('api/rgw/bucket', null, {params: params});
   }
 
   update(bucketId: string, bucket: string, uid: string) {
@@ -60,7 +57,7 @@ export class RgwBucketService {
     params = params.append('bucket', bucket);
     params = params.append('bucket-id', bucketId as string);
     params = params.append('uid', uid);
-    return this.http.put(this.url, null, { params: params });
+    return this.http.put(this.url, null, {params: params});
   }
 
   delete(bucket: string, purgeObjects = true) {
index f518f53e84d060bcf5f5e4e67ce33c5ae4efeacf..9fcd87fab06247d80db58210f61b3aa821cda938 100644 (file)
@@ -42,9 +42,7 @@ export class RgwUserService {
   }
 
   get(uid: string) {
-    let params = new HttpParams();
-    params = params.append('uid', uid);
-    return this.http.get(this.url, {params: params});
+    return this.http.get(`api/rgw/user/${uid}`);
   }
 
   getQuota(uid: string) {