diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py
index eeaf4c761dc..b913b22aa3d 100644
--- a/qa/tasks/mgr/dashboard/test_rgw.py
+++ b/qa/tasks/mgr/dashboard/test_rgw.py
@@ -7,51 +7,90 @@ logger = logging.getLogger(__name__)
from .helper import DashboardTestCase, authenticate
-class RgwControllerTest(DashboardTestCase):
- @authenticate
- def test_rgw_daemon_list(self):
- data = self._get('/api/rgw/daemon')
- self.assertStatus(200)
+class RgwTestCase(DashboardTestCase):
- self.assertEqual(len(data), 1)
- data = data[0]
- self.assertIn('id', data)
- self.assertIn('version', data)
- self.assertIn('server_hostname', data)
+ maxDiff = None
+ create_test_user = False
- @authenticate
- def test_rgw_daemon_get(self):
- data = self._get('/api/rgw/daemon')
- self.assertStatus(200)
- data = self._get('/api/rgw/daemon/{}'.format(data[0]['id']))
- self.assertStatus(200)
-
- self.assertIn('rgw_metadata', data)
- self.assertIn('rgw_id', data)
- self.assertIn('rgw_status', data)
- self.assertTrue(data['rgw_metadata'])
-
-
-class RgwApiUserTest(DashboardTestCase):
@classmethod
def setUpClass(cls):
- super(RgwApiUserTest, cls).setUpClass()
+ 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'
+ '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(DashboardTestCase, cls).tearDownClass()
+
+ def get_rgw_user(self, uid):
+ return self._get('/api/rgw/user/{}'.format(uid))
+
+ def find_in_list(self, key, value, data):
+ """
+ Helper function to find an object with the specified key/value
+ in a list.
+ :param key: The name of the key.
+ :param value: The value to search for.
+ :param data: The list to process.
+ :return: Returns the found object or None.
+ """
+ return next(iter(filter(lambda x: x[key] == value, data)), None)
+
+
+class RgwApiCredentialsTest(RgwTestCase):
def setUp(self):
# Restart the Dashboard module to ensure that the connection to the
# RGW Admin Ops API is re-established with the new credentials.
self._ceph_cmd(['mgr', 'module', 'disable', 'dashboard'])
self._ceph_cmd(['mgr', 'module', 'enable', 'dashboard', '--force'])
+ # Set the default credentials.
+ self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', ''])
+ self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
+ self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
+
+ @authenticate
+ def test_no_access_secret_key(self):
+ self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', ''])
+ self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', ''])
+ resp = self._get('/api/rgw/user')
+ self.assertStatus(500)
+ self.assertIn('detail', resp)
+ self.assertIn('component', resp)
+ self.assertIn('No RGW credentials found', resp['detail'])
+ self.assertEquals(resp['component'], 'rgw')
@authenticate
def test_success(self):
- self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', ''])
data = self._get('/api/rgw/status')
self.assertStatus(200)
self.assertIn('available', data)
@@ -59,7 +98,7 @@ class RgwApiUserTest(DashboardTestCase):
self.assertTrue(data['available'])
@authenticate
- def test_failure(self):
+ def test_invalid_user_id(self):
self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', 'xyz'])
data = self._get('/api/rgw/status')
self.assertStatus(200)
@@ -70,32 +109,115 @@ class RgwApiUserTest(DashboardTestCase):
data['message'])
-class RgwProxyExceptionsTest(DashboardTestCase):
+class RgwBucketTest(RgwTestCase):
@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', ''])
+ cls.create_test_user = True
+ super(RgwBucketTest, cls).setUpClass()
@authenticate
- def test_no_credentials_exception(self):
- resp = self._get('/api/rgw/proxy/status')
- self.assertStatus(500)
- self.assertIn('detail', resp)
+ 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.assertIn('bucket_info', data)
+ data = data['bucket_info']
+ self.assertIn('bucket', data)
+ self.assertIn('quota', data)
+ self.assertIn('creation_time', data)
+ self.assertIn('name', data['bucket'])
+ self.assertIn('bucket_id', data['bucket'])
+ self.assertEquals(data['bucket']['name'], 'teuth-test-bucket')
+
+ # List all buckets.
+ data = self._get('/api/rgw/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.assertIn('id', data)
+ self.assertIn('bucket', data)
+ self.assertIn('bucket_quota', data)
+ self.assertIn('owner', data)
+ self.assertEquals(data['bucket'], 'teuth-test-bucket')
+ self.assertEquals(data['owner'], 'admin')
+
+ # Update the bucket.
+ self._put(
+ '/api/rgw/bucket/teuth-test-bucket',
+ params={
+ 'bucket_id': data['id'],
+ 'uid': 'teuth-test-user'
+ })
+ self.assertStatus(200)
+ data = self._get('/api/rgw/bucket/teuth-test-bucket')
+ self.assertStatus(200)
+ self.assertEquals(data['owner'], 'teuth-test-user')
+
+ # Delete the bucket.
+ self._delete('/api/rgw/bucket/teuth-test-bucket')
+ self.assertStatus(204)
+ data = self._get('/api/rgw/bucket')
+ self.assertStatus(200)
+ self.assertEqual(len(data), 0)
-class RgwProxyTest(DashboardTestCase):
- @classmethod
- def setUpClass(cls):
- super(RgwProxyTest, cls).setUpClass()
- cls._radosgw_admin_cmd([
+class RgwDaemonTest(DashboardTestCase):
+
+ @authenticate
+ def test_list(self):
+ data = self._get('/api/rgw/daemon')
+ self.assertStatus(200)
+ self.assertEqual(len(data), 1)
+ data = data[0]
+ self.assertIn('id', data)
+ self.assertIn('version', data)
+ self.assertIn('server_hostname', data)
+
+ @authenticate
+ def test_get(self):
+ data = self._get('/api/rgw/daemon')
+ self.assertStatus(200)
+
+ data = self._get('/api/rgw/daemon/{}'.format(data[0]['id']))
+ self.assertStatus(200)
+ self.assertIn('rgw_metadata', data)
+ self.assertIn('rgw_id', data)
+ self.assertIn('rgw_status', data)
+ self.assertTrue(data['rgw_metadata'])
+
+ @authenticate
+ def test_status(self):
+ self._radosgw_admin_cmd([
'user', 'create', '--uid=admin', '--display-name=admin',
'--system', '--access-key=admin', '--secret=admin'
])
- cls._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
- cls._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
+ self._ceph_cmd(['dashboard', 'set-rgw-api-user-id', 'admin'])
+ self._ceph_cmd(['dashboard', 'set-rgw-api-secret-key', 'admin'])
+ self._ceph_cmd(['dashboard', 'set-rgw-api-access-key', 'admin'])
+
+ data = self._get('/api/rgw/status')
+ self.assertStatus(200)
+ self.assertIn('available', data)
+ self.assertIn('message', data)
+ self.assertTrue(data['available'])
+
+
+class RgwUserTest(RgwTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ super(RgwUserTest, cls).setUpClass()
def _assert_user_data(self, data):
self.assertIn('caps', data)
@@ -110,71 +232,308 @@ class RgwProxyTest(DashboardTestCase):
self.assertIn('tenant', data)
self.assertIn('user_id', data)
- def _test_put(self):
- self._put(
- '/api/rgw/proxy/user',
- params={
- 'uid': 'teuth-test-user',
- 'display-name': 'display name',
- })
- data = self._resp.json()
-
+ @authenticate
+ def test_get(self):
+ data = self.get_rgw_user('admin')
+ self.assertStatus(200)
self._assert_user_data(data)
+ self.assertEquals(data['user_id'], 'admin')
+
+ @authenticate
+ def test_list(self):
+ data = self._get('/api/rgw/user')
self.assertStatus(200)
+ self.assertGreaterEqual(len(data), 1)
+ self.assertIn('admin', data)
- data = self._get(
- '/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'})
-
+ @authenticate
+ def test_create_update_delete(self):
+ # Create a new user.
+ self._post('/api/rgw/user', params={
+ 'uid': 'teuth-test-user',
+ 'display_name': 'display name'
+ })
+ self.assertStatus(201)
+ data = self.jsonBody()
self._assert_user_data(data)
+ self.assertEquals(data['user_id'], 'teuth-test-user')
+ self.assertEquals(data['display_name'], 'display name')
+
+ # Get the user.
+ data = self.get_rgw_user('teuth-test-user')
self.assertStatus(200)
+ self._assert_user_data(data)
self.assertEquals(data['user_id'], 'teuth-test-user')
- def _test_post(self):
- """Updates the user"""
- self._post(
- '/api/rgw/proxy/user',
+ # Update the user.
+ self._put(
+ '/api/rgw/user/teuth-test-user',
params={
- 'uid': 'teuth-test-user',
- 'display-name': 'new name'
+ 'display_name': 'new name'
})
-
self.assertStatus(200)
- self._assert_user_data(self._resp.json())
- self.assertEqual(self._resp.json()['display_name'], 'new name')
+ data = self.jsonBody()
+ self._assert_user_data(data)
+ self.assertEqual(data['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'})
+ # Delete the user.
+ self._delete('/api/rgw/user/teuth-test-user')
+ self.assertStatus(204)
+ self.get_rgw_user('teuth-test-user')
self.assertStatus(500)
- resp = self._resp.json()
+ 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'])
+
+class RgwUserCapabilityTest(RgwTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.create_test_user = True
+ super(RgwUserCapabilityTest, cls).setUpClass()
+
@authenticate
- def test_rgw_proxy(self):
- """Test basic request types"""
- self.maxDiff = None
+ def test_set(self):
+ self._post(
+ '/api/rgw/user/teuth-test-user/capability',
+ params={
+ 'type': 'usage',
+ 'perm': 'read'
+ })
+ self.assertStatus(201)
+ data = self.jsonBody()
+ self.assertEqual(len(data), 1)
+ data = data[0]
+ self.assertEqual(data['type'], 'usage')
+ self.assertEqual(data['perm'], 'read')
- # PUT - Create a user
- self._test_put()
+ # Get the user data to validate the capabilities.
+ data = self.get_rgw_user('teuth-test-user')
+ self.assertStatus(200)
+ self.assertGreaterEqual(len(data['caps']), 1)
+ self.assertEqual(data['caps'][0]['type'], 'usage')
+ self.assertEqual(data['caps'][0]['perm'], 'read')
- # GET - Get the user details
- self._test_get()
+ @authenticate
+ def test_delete(self):
+ self._delete(
+ '/api/rgw/user/teuth-test-user/capability',
+ params={
+ 'type': 'metadata',
+ 'perm': 'write'
+ })
+ self.assertStatus(204)
- # POST - Update the user details
- self._test_post()
+ # Get the user data to validate the capabilities.
+ data = self.get_rgw_user('teuth-test-user')
+ self.assertStatus(200)
+ self.assertEqual(len(data['caps']), 0)
- # DELETE - Delete the user
- self._test_delete()
+
+class RgwUserKeyTest(RgwTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.create_test_user = True
+ super(RgwUserKeyTest, cls).setUpClass()
+
+ @authenticate
+ def test_create_s3(self):
+ self._post(
+ '/api/rgw/user/teuth-test-user/key',
+ params={
+ 'key_type': 's3',
+ 'generate_key': 'false',
+ 'access_key': 'abc987',
+ 'secret_key': 'aaabbbccc'
+ })
+ data = self.jsonBody()
+ self.assertStatus(201)
+ self.assertGreaterEqual(len(data), 3)
+ key = self.find_in_list('access_key', 'abc987', data)
+ self.assertIsInstance(key, object)
+ self.assertEqual(key['secret_key'], 'aaabbbccc')
+
+ @authenticate
+ def test_create_swift(self):
+ self._post(
+ '/api/rgw/user/teuth-test-user/key',
+ params={
+ 'key_type': 'swift',
+ 'subuser': 'teuth-test-subuser',
+ 'generate_key': 'false',
+ 'secret_key': 'xxxyyyzzz'
+ })
+ data = self.jsonBody()
+ self.assertStatus(201)
+ self.assertGreaterEqual(len(data), 2)
+ key = self.find_in_list('secret_key', 'xxxyyyzzz', data)
+ self.assertIsInstance(key, object)
+
+ @authenticate
+ def test_delete_s3(self):
+ self._delete(
+ '/api/rgw/user/teuth-test-user/key',
+ params={
+ 'key_type': 's3',
+ 'access_key': 'xyz123'
+ })
+ self.assertStatus(204)
+
+ @authenticate
+ def test_delete_swift(self):
+ self._delete(
+ '/api/rgw/user/teuth-test-user/key',
+ params={
+ 'key_type': 'swift',
+ 'subuser': 'teuth-test-user:teuth-test-subuser2'
+ })
+ self.assertStatus(204)
+
+
+class RgwUserQuotaTest(RgwTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.create_test_user = True
+ super(RgwUserQuotaTest, cls).setUpClass()
+
+ def _assert_quota(self, data):
+ self.assertIn('user_quota', data)
+ self.assertIn('max_objects', data['user_quota'])
+ self.assertIn('enabled', data['user_quota'])
+ self.assertIn('max_size_kb', data['user_quota'])
+ self.assertIn('max_size', data['user_quota'])
+ self.assertIn('bucket_quota', data)
+ self.assertIn('max_objects', data['bucket_quota'])
+ self.assertIn('enabled', data['bucket_quota'])
+ self.assertIn('max_size_kb', data['bucket_quota'])
+ self.assertIn('max_size', data['bucket_quota'])
+
+ @authenticate
+ def test_get_quota(self):
+ data = self._get('/api/rgw/user/teuth-test-user/quota')
+ self.assertStatus(200)
+ self._assert_quota(data)
+
+ @authenticate
+ def test_set_user_quota(self):
+ self._put(
+ '/api/rgw/user/teuth-test-user/quota',
+ params={
+ 'quota_type': 'user',
+ 'enabled': 'true',
+ 'max_size_kb': 2048,
+ 'max_objects': 101
+ })
+ self.assertStatus(200)
+
+ data = self._get('/api/rgw/user/teuth-test-user/quota')
+ self.assertStatus(200)
+ self._assert_quota(data)
+ self.assertEqual(data['user_quota']['max_objects'], 101)
+ self.assertTrue(data['user_quota']['enabled'])
+ self.assertEqual(data['user_quota']['max_size_kb'], 2048)
+
+ @authenticate
+ def test_set_bucket_quota(self):
+ self._put(
+ '/api/rgw/user/teuth-test-user/quota',
+ params={
+ 'quota_type': 'bucket',
+ 'enabled': 'false',
+ 'max_size_kb': 4096,
+ 'max_objects': 2000
+ })
+ self.assertStatus(200)
+
+ data = self._get('/api/rgw/user/teuth-test-user/quota')
+ self.assertStatus(200)
+ self._assert_quota(data)
+ self.assertEqual(data['bucket_quota']['max_objects'], 2000)
+ self.assertFalse(data['bucket_quota']['enabled'])
+ self.assertEqual(data['bucket_quota']['max_size_kb'], 4096)
+
+
+class RgwUserSubuserTest(RgwTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ cls.create_test_user = True
+ super(RgwUserSubuserTest, cls).setUpClass()
+
+ @authenticate
+ def test_create_swift(self):
+ self._post(
+ '/api/rgw/user/teuth-test-user/subuser',
+ params={
+ 'subuser': 'tux',
+ 'access': 'readwrite',
+ 'key_type': 'swift'
+ })
+ self.assertStatus(201)
+ data = self.jsonBody()
+ subuser = self.find_in_list('id', 'teuth-test-user:tux', data)
+ self.assertIsInstance(subuser, object)
+ self.assertEqual(subuser['permissions'], 'read-write')
+
+ # Get the user data to validate the keys.
+ data = self.get_rgw_user('teuth-test-user')
+ self.assertStatus(200)
+ key = self.find_in_list('user', 'teuth-test-user:tux', data['swift_keys'])
+ self.assertIsInstance(key, object)
+
+ @authenticate
+ def test_create_s3(self):
+ self._post(
+ '/api/rgw/user/teuth-test-user/subuser',
+ params={
+ 'subuser': 'hugo',
+ 'access': 'write',
+ 'generate_secret': 'false',
+ 'access_key': 'yyy',
+ 'secret_key': 'xxx'
+ })
+ self.assertStatus(201)
+ data = self.jsonBody()
+ subuser = self.find_in_list('id', 'teuth-test-user:hugo', data)
+ self.assertIsInstance(subuser, object)
+ self.assertEqual(subuser['permissions'], 'write')
+
+ # Get the user data to validate the keys.
+ data = self.get_rgw_user('teuth-test-user')
+ self.assertStatus(200)
+ key = self.find_in_list('user', 'teuth-test-user:hugo', data['keys'])
+ self.assertIsInstance(key, object)
+ self.assertEqual(key['secret_key'], 'xxx')
+
+ @authenticate
+ def test_delete_w_purge(self):
+ self._delete(
+ '/api/rgw/user/teuth-test-user/subuser/teuth-test-subuser2')
+ self.assertStatus(204)
+
+ # Get the user data to check that the keys don't exist anymore.
+ data = self.get_rgw_user('teuth-test-user')
+ self.assertStatus(200)
+ key = self.find_in_list('user', 'teuth-test-user:teuth-test-subuser2',
+ data['swift_keys'])
+ self.assertIsNone(key)
+
+ @authenticate
+ def test_delete_wo_purge(self):
+ self._delete(
+ '/api/rgw/user/teuth-test-user/subuser/teuth-test-subuser',
+ params={'purge_keys': 'false'})
+ self.assertStatus(204)
+
+ # Get the user data to check whether they keys still exist.
+ data = self.get_rgw_user('teuth-test-user')
+ self.assertStatus(200)
+ key = self.find_in_list('user', 'teuth-test-user:teuth-test-subuser',
+ data['keys'])
+ self.assertIsInstance(key, object)
diff --git a/src/pybind/mgr/dashboard/controllers/__init__.py b/src/pybind/mgr/dashboard/controllers/__init__.py
index 4d00178be7a..a86c753cff5 100644
--- a/src/pybind/mgr/dashboard/controllers/__init__.py
+++ b/src/pybind/mgr/dashboard/controllers/__init__.py
@@ -509,19 +509,19 @@ class BaseController(object):
content_length = int(cherrypy.request.headers['Content-Length'])
body = cherrypy.request.body.read(content_length)
if not body:
- return func(*args, **kwargs)
-
- try:
- data = json.loads(body.decode('utf-8'))
- except Exception as e:
- raise cherrypy.HTTPError(400, 'Failed to decode JSON: {}'
- .format(str(e)))
- kwargs.update(data.items())
- ret = func(*args, **kwargs)
+ ret = func(*args, **kwargs)
+ else:
+ try:
+ data = json.loads(body.decode('utf-8'))
+ except Exception as e:
+ raise cherrypy.HTTPError(400, 'Failed to decode JSON: {}'
+ .format(str(e)))
+ kwargs.update(data.items())
+ ret = func(*args, **kwargs)
if json_response:
cherrypy.response.headers['Content-Type'] = 'application/json'
- return json.dumps(ret).encode('utf8')
+ ret = json.dumps(ret).encode('utf8')
return ret
return inner
diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py
index 26f4dce5045..e1b551aafe6 100644
--- a/src/pybind/mgr/dashboard/controllers/rgw.py
+++ b/src/pybind/mgr/dashboard/controllers/rgw.py
@@ -4,12 +4,12 @@ from __future__ import absolute_import
import json
import cherrypy
-from . import ApiController, BaseController, RESTController, AuthRequired, \
- Endpoint, Proxy
+from . import ApiController, BaseController, RESTController, AuthRequired, Endpoint
from .. import logger
from ..services.ceph_service import CephService
from ..services.rgw_client import RgwClient
from ..rest_client import RequestException
+from ..exceptions import DashboardException
@ApiController('/rgw')
@@ -73,7 +73,7 @@ class RgwDaemon(RESTController):
try:
status = json.loads(status['json'])
except ValueError:
- logger.warning("%s had invalid status json", service['id'])
+ logger.warning('%s had invalid status json', service['id'])
status = {}
else:
logger.warning('%s has no key "json" in status', service['id'])
@@ -83,40 +83,179 @@ class RgwDaemon(RESTController):
return daemon
-@ApiController('/rgw/proxy')
-@AuthRequired()
-class RgwProxy(BaseController):
+class RgwRESTController(RESTController):
- @Proxy()
- def __call__(self, path, **params):
+ def proxy(self, method, path, params=None, json_response=True):
try:
- rgw_client = RgwClient.admin_instance()
-
- method = cherrypy.request.method
- data = None
-
- if cherrypy.request.body.length:
- data = cherrypy.request.body.read()
-
- return rgw_client.proxy(method, path, params, data)
- except RequestException as e:
- # Always use status code 500 and NOT the status that may delivered
- # by the exception. That's because we do not want to forward e.g.
- # 401 or 404 that may trigger unwanted actions in the UI.
- cherrypy.response.headers['Content-Type'] = 'application/json'
- cherrypy.response.status = 500
- return json.dumps({'detail': str(e)}).encode('utf-8')
+ instance = RgwClient.admin_instance()
+ result = instance.proxy(method, path, params, None)
+ if json_response and result != '':
+ result = json.loads(result.decode('utf-8'))
+ return result
+ except (DashboardException, RequestException) as e:
+ raise DashboardException(e, http_status_code=500, component='rgw')
@ApiController('/rgw/bucket')
@AuthRequired()
-class RgwBucket(RESTController):
+class RgwBucket(RgwRESTController):
+
+ def list(self):
+ return self.proxy('GET', 'bucket')
+
+ def get(self, bucket):
+ return self.proxy('GET', 'bucket', {'bucket': bucket})
def create(self, bucket, uid):
try:
rgw_client = RgwClient.instance(uid)
return rgw_client.create_bucket(bucket)
except RequestException as e:
- cherrypy.response.headers['Content-Type'] = 'application/json'
- cherrypy.response.status = 500
- return {'detail': str(e)}
+ raise DashboardException(e, http_status_code=500, component='rgw')
+
+ def set(self, bucket, bucket_id, uid):
+ return self.proxy('PUT', 'bucket', {
+ 'bucket': bucket,
+ 'bucket-id': bucket_id,
+ 'uid': uid
+ }, json_response=False)
+
+ def delete(self, bucket, purge_objects='true'):
+ return self.proxy('DELETE', 'bucket', {
+ 'bucket': bucket,
+ 'purge-objects': purge_objects
+ }, json_response=False)
+
+
+@ApiController('/rgw/user')
+@AuthRequired()
+class RgwUser(RgwRESTController):
+
+ def list(self):
+ return self.proxy('GET', 'metadata/user')
+
+ def get(self, uid):
+ return self.proxy('GET', 'user', {'uid': uid})
+
+ def create(self, uid, display_name, email=None, max_buckets=None,
+ suspended=None, generate_key=None, access_key=None,
+ secret_key=None):
+ params = {'uid': uid}
+ if display_name is not None:
+ params['display-name'] = display_name
+ if email is not None:
+ params['email'] = email
+ if max_buckets is not None:
+ params['max-buckets'] = max_buckets
+ if suspended is not None:
+ params['suspended'] = suspended
+ if generate_key is not None:
+ params['generate-key'] = generate_key
+ if access_key is not None:
+ params['access-key'] = access_key
+ if secret_key is not None:
+ params['secret-key'] = secret_key
+ return self.proxy('PUT', 'user', params)
+
+ def set(self, uid, display_name=None, email=None, max_buckets=None,
+ suspended=None):
+ params = {'uid': uid}
+ if display_name is not None:
+ params['display-name'] = display_name
+ if email is not None:
+ params['email'] = email
+ if max_buckets is not None:
+ params['max-buckets'] = max_buckets
+ if suspended is not None:
+ params['suspended'] = suspended
+ return self.proxy('POST', 'user', params)
+
+ def delete(self, uid):
+ try:
+ instance = RgwClient.admin_instance()
+ # Ensure the user is not configured to access the RGW Object Gateway.
+ if instance.userid == uid:
+ raise DashboardException(msg='Unable to delete "{}" - this user '
+ 'account is required for managing the '
+ 'Object Gateway'.format(uid))
+ # Finally redirect request to the RGW proxy.
+ return self.proxy('DELETE', 'user', {'uid': uid}, json_response=False)
+ except (DashboardException, RequestException) as e:
+ raise DashboardException(e, component='rgw')
+
+ # pylint: disable=redefined-builtin
+ @RESTController.Resource(method='POST', path='/capability', status=201)
+ def create_cap(self, uid, type, perm):
+ return self.proxy('PUT', 'user?caps', {
+ 'uid': uid,
+ 'user-caps': '{}={}'.format(type, perm)
+ })
+
+ # pylint: disable=redefined-builtin
+ @RESTController.Resource(method='DELETE', path='/capability', status=204)
+ def delete_cap(self, uid, type, perm):
+ return self.proxy('DELETE', 'user?caps', {
+ 'uid': uid,
+ 'user-caps': '{}={}'.format(type, perm)
+ })
+
+ @RESTController.Resource(method='POST', path='/key', status=201)
+ def create_key(self, uid, key_type='s3', subuser=None, generate_key='true',
+ access_key=None, secret_key=None):
+ params = {'uid': uid, 'key-type': key_type, 'generate-key': generate_key}
+ if subuser is not None:
+ params['subuser'] = subuser
+ if access_key is not None:
+ params['access-key'] = access_key
+ if secret_key is not None:
+ params['secret-key'] = secret_key
+ return self.proxy('PUT', 'user?key', params)
+
+ @RESTController.Resource(method='DELETE', path='/key', status=204)
+ def delete_key(self, uid, key_type='s3', subuser=None, access_key=None):
+ params = {'uid': uid, 'key-type': key_type}
+ if subuser is not None:
+ params['subuser'] = subuser
+ if access_key is not None:
+ params['access-key'] = access_key
+ return self.proxy('DELETE', 'user?key', params, json_response=False)
+
+ @RESTController.Resource(method='GET', path='/quota')
+ def get_quota(self, uid):
+ return self.proxy('GET', 'user?quota', {'uid': uid})
+
+ @RESTController.Resource(method='PUT', path='/quota')
+ def set_quota(self, uid, quota_type, enabled, max_size_kb, max_objects):
+ return self.proxy('PUT', 'user?quota', {
+ 'uid': uid,
+ 'quota-type': quota_type,
+ 'enabled': enabled,
+ 'max-size-kb': max_size_kb,
+ 'max-objects': max_objects
+ }, json_response=False)
+
+ @RESTController.Resource(method='POST', path='/subuser', status=201)
+ def create_subuser(self, uid, subuser, access, key_type='s3',
+ generate_secret='true', access_key=None,
+ secret_key=None):
+ return self.proxy('PUT', 'user', {
+ 'uid': uid,
+ 'subuser': subuser,
+ 'key-type': key_type,
+ 'access': access,
+ 'generate-secret': generate_secret,
+ 'access-key': access_key,
+ 'secret-key': secret_key
+ })
+
+ @RESTController.Resource(method='DELETE', path='/subuser/{subuser}', status=204)
+ def delete_subuser(self, uid, subuser, purge_keys='true'):
+ """
+ :param purge_keys: Set to False to do not purge the keys.
+ Note, this only works for s3 subusers.
+ """
+ return self.proxy('DELETE', 'user', {
+ 'uid': uid,
+ 'subuser': subuser,
+ 'purge-keys': purge_keys
+ }, json_response=False)
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
index b12eae02ca0..6710cb1c4ab 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.ts
@@ -91,7 +91,7 @@ export class RgwBucketFormComponent implements OnInit {
if (this.editing) {
// Edit
const idCtl = this.bucketForm.get('id');
- this.rgwBucketService.update(idCtl.value, bucketCtl.value, ownerCtl.value).subscribe(
+ this.rgwBucketService.update(bucketCtl.value, idCtl.value, ownerCtl.value).subscribe(
() => {
this.goToListView();
},
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html
index 93ba8f44605..98aba62679d 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.html
@@ -117,12 +117,18 @@
+
+ This field is required.
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
index f58ca81fedb..bca02882ecc 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.spec.ts
@@ -9,6 +9,7 @@ import { of as observableOf } from 'rxjs';
import { RgwUserService } from '../../../shared/api/rgw-user.service';
import { SharedModule } from '../../../shared/shared.module';
import { configureTestBed } from '../../../shared/unit-test-helper';
+import { RgwUserS3Key } from '../models/rgw-user-s3-key';
import { RgwUserFormComponent } from './rgw-user-form.component';
describe('RgwUserFormComponent', () => {
@@ -46,6 +47,53 @@ describe('RgwUserFormComponent', () => {
expect(component).toBeTruthy();
});
+ describe('s3 key management', () => {
+ let rgwUserService: RgwUserService;
+
+ beforeEach(() => {
+ rgwUserService = TestBed.get(RgwUserService);
+ spyOn(rgwUserService, 'addS3Key').and.stub();
+ });
+
+ it('should not update key', () => {
+ component.setS3Key(new RgwUserS3Key(), 3);
+ expect(component.s3Keys.length).toBe(0);
+ expect(rgwUserService.addS3Key).not.toHaveBeenCalled();
+ });
+
+ it('should set key', () => {
+ const key = new RgwUserS3Key();
+ key.user = 'test1:subuser2';
+ component.setS3Key(key);
+ expect(component.s3Keys.length).toBe(1);
+ expect(component.s3Keys[0].user).toBe('test1:subuser2');
+ expect(rgwUserService.addS3Key).toHaveBeenCalledWith(
+ 'test1', {
+ subuser: 'subuser2',
+ generate_key: 'false',
+ access_key: undefined,
+ secret_key: undefined
+ }
+ );
+ });
+
+ it('should set key w/o subuser', () => {
+ const key = new RgwUserS3Key();
+ key.user = 'test1';
+ component.setS3Key(key);
+ expect(component.s3Keys.length).toBe(1);
+ expect(component.s3Keys[0].user).toBe('test1');
+ expect(rgwUserService.addS3Key).toHaveBeenCalledWith(
+ 'test1', {
+ subuser: '',
+ generate_key: 'false',
+ access_key: undefined,
+ secret_key: undefined
+ }
+ );
+ });
+ });
+
describe('quotaMaxSizeValidator', () => {
it('should validate max size (1/7)', () => {
const resp = component.quotaMaxSizeValidator(new FormControl(''));
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
index 66a44720680..01073a59e7f 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-user-form/rgw-user-form.component.ts
@@ -59,7 +59,7 @@ export class RgwUserFormComponent implements OnInit {
user_id: [null, [Validators.required], [this.userIdValidator()]],
display_name: [null, [Validators.required]],
email: [null, [CdValidators.email]],
- max_buckets: [null, [Validators.min(0)]],
+ max_buckets: [1000, [Validators.required, Validators.min(0)]],
suspended: [false],
// S3 key
generate_key: [true],
@@ -181,8 +181,8 @@ export class RgwUserFormComponent implements OnInit {
value[type + '_quota_max_size'] = quota.max_size;
}
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;
@@ -225,26 +225,27 @@ export class RgwUserFormComponent implements OnInit {
if (this.userForm.pristine) {
this.goToListView();
}
+ const uid = this.userForm.get('user_id').value;
if (this.editing) {
// Edit
if (this._isGeneralDirty()) {
- const args = this._getApiPostArgs();
- this.submitObservables.push(this.rgwUserService.post(args));
+ const args = this._getUpdateArgs();
+ this.submitObservables.push(this.rgwUserService.update(uid, args));
}
} else {
// Add
- const args = this._getApiPutArgs();
- this.submitObservables.push(this.rgwUserService.put(args));
+ const args = this._getCreateArgs();
+ this.submitObservables.push(this.rgwUserService.create(args));
}
// Check if user quota has been modified.
if (this._isUserQuotaDirty()) {
- const userQuotaArgs = this._getApiUserQuotaArgs();
- this.submitObservables.push(this.rgwUserService.putQuota(userQuotaArgs));
+ const userQuotaArgs = this._getUserQuotaArgs();
+ this.submitObservables.push(this.rgwUserService.updateQuota(uid, userQuotaArgs));
}
// Check if bucket quota has been modified.
if (this._isBucketQuotaDirty()) {
- const bucketQuotaArgs = this._getApiBucketQuotaArgs();
- this.submitObservables.push(this.rgwUserService.putQuota(bucketQuotaArgs));
+ const bucketQuotaArgs = this._getBucketQuotaArgs();
+ this.submitObservables.push(this.rgwUserService.updateQuota(uid, bucketQuotaArgs));
}
// Finally execute all observables.
observableForkJoin(this.submitObservables).subscribe(
@@ -304,31 +305,29 @@ export class RgwUserFormComponent implements OnInit {
* Add/Update a subuser.
*/
setSubuser(subuser: RgwUserSubuser, index?: number) {
+ const mapPermissions = {
+ 'full-control': 'full',
+ 'read-write': 'readwrite'
+ };
+ const uid = this.userForm.get('user_id').value;
+ const args = {
+ subuser: subuser.id,
+ access:
+ subuser.permissions in mapPermissions
+ ? mapPermissions[subuser.permissions]
+ : subuser.permissions,
+ key_type: 'swift',
+ secret_key: subuser.secret_key,
+ generate_secret: subuser.generate_secret ? 'true' : 'false'
+ };
+ this.submitObservables.push(this.rgwUserService.createSubuser(uid, args));
if (_.isNumber(index)) {
// Modify
// Create an observable to modify the subuser when the form is submitted.
- this.submitObservables.push(
- this.rgwUserService.addSubuser(
- this.userForm.get('user_id').value,
- subuser.id,
- subuser.permissions,
- subuser.secret_key,
- subuser.generate_secret
- )
- );
this.subusers[index] = subuser;
} else {
// Add
// Create an observable to add the subuser when the form is submitted.
- this.submitObservables.push(
- this.rgwUserService.addSubuser(
- this.userForm.get('user_id').value,
- subuser.id,
- subuser.permissions,
- subuser.secret_key,
- subuser.generate_secret
- )
- );
this.subusers.push(subuser);
// Add a Swift key. If the secret key is auto-generated, then visualize
// this to the user by displaying a notification instead of the key.
@@ -418,16 +417,17 @@ export class RgwUserFormComponent implements OnInit {
// Nothing to do here at the moment.
} else {
// Add
+ // Split the key's user name into its user and subuser parts.
+ const userMatches = key.user.match(/([^:]+)(:(.+))?/);
// Create an observable to add the S3 key when the form is submitted.
- this.submitObservables.push(
- this.rgwUserService.addS3Key(
- this.userForm.get('user_id').value,
- key.user,
- key.access_key,
- key.secret_key,
- key.generate_key
- )
- );
+ const uid = userMatches[1];
+ const args = {
+ subuser: userMatches[2] ? userMatches[3] : '',
+ generate_key: key.generate_key ? 'true' : 'false',
+ access_key: key.access_key,
+ secret_key: key.secret_key
+ };
+ this.submitObservables.push(this.rgwUserService.addS3Key(uid, args));
// If the access and the secret key are auto-generated, then visualize
// this to the user by displaying a notification instead of the key.
this.s3Keys.push({
@@ -578,31 +578,28 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments of the API request when a new
* user is created.
*/
- private _getApiPutArgs() {
+ private _getCreateArgs() {
const result = {
uid: this.userForm.get('user_id').value,
- 'display-name': this.userForm.get('display_name').value
+ display_name: this.userForm.get('display_name').value,
+ suspended: this.userForm.get('suspended').value,
+ email: '',
+ max_buckets: this.userForm.get('max_buckets').value,
+ generate_key: this.userForm.get('generate_key').value,
+ access_key: '',
+ secret_key: ''
};
- const suspendedCtl = this.userForm.get('suspended');
- if (suspendedCtl.value) {
- _.extend(result, { suspended: suspendedCtl.value });
- }
const emailCtl = this.userForm.get('email');
if (_.isString(emailCtl.value) && emailCtl.value.length > 0) {
- _.extend(result, { email: emailCtl.value });
- }
- const maxBucketsCtl = this.userForm.get('max_buckets');
- if (maxBucketsCtl.value > 0) {
- _.extend(result, { 'max-buckets': maxBucketsCtl.value });
+ _.merge(result, { email: emailCtl.value });
}
const generateKeyCtl = this.userForm.get('generate_key');
if (!generateKeyCtl.value) {
- _.extend(result, {
- 'access-key': this.userForm.get('access_key').value,
- 'secret-key': this.userForm.get('secret_key').value
+ _.merge(result, {
+ generate_key: false,
+ access_key: this.userForm.get('access_key').value,
+ secret_key: this.userForm.get('secret_key').value
});
- } else {
- _.extend(result, { 'generate-key': true });
}
return result;
}
@@ -611,21 +608,12 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments for the API request when the user
* configuration has been modified.
*/
- private _getApiPostArgs() {
- const result = {
- uid: this.userForm.get('user_id').value
- };
- const argsMap = {
- 'display-name': 'display_name',
- email: 'email',
- 'max-buckets': 'max_buckets',
- suspended: 'suspended'
- };
- for (const key of Object.keys(argsMap)) {
- const ctl = this.userForm.get(argsMap[key]);
- if (ctl.dirty) {
- result[key] = ctl.value;
- }
+ private _getUpdateArgs() {
+ const result = {};
+ const keys = ['display_name', 'email', 'max_buckets', 'suspended'];
+ for (const key of keys) {
+ const ctl = this.userForm.get(key);
+ result[key] = ctl.value;
}
return result;
}
@@ -634,22 +622,21 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments for the API request when the user
* quota configuration has been modified.
*/
- private _getApiUserQuotaArgs(): object {
+ private _getUserQuotaArgs(): object {
const result = {
- uid: this.userForm.get('user_id').value,
- 'quota-type': 'user',
+ quota_type: 'user',
enabled: this.userForm.get('user_quota_enabled').value,
- 'max-size-kb': -1,
- 'max-objects': -1
+ max_size_kb: -1,
+ max_objects: -1
};
if (!this.userForm.get('user_quota_max_size_unlimited').value) {
// Convert the given value to bytes.
const bytes = new FormatterService().toBytes(this.userForm.get('user_quota_max_size').value);
// Finally convert the value to KiB.
- result['max-size-kb'] = (bytes / 1024).toFixed(0) as any;
+ result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
}
if (!this.userForm.get('user_quota_max_objects_unlimited').value) {
- result['max-objects'] = this.userForm.get('user_quota_max_objects').value;
+ result['max_objects'] = this.userForm.get('user_quota_max_objects').value;
}
return result;
}
@@ -658,13 +645,12 @@ export class RgwUserFormComponent implements OnInit {
* Helper function to get the arguments for the API request when the bucket
* quota configuration has been modified.
*/
- private _getApiBucketQuotaArgs(): object {
+ private _getBucketQuotaArgs(): object {
const result = {
- uid: this.userForm.get('user_id').value,
- 'quota-type': 'bucket',
+ quota_type: 'bucket',
enabled: this.userForm.get('bucket_quota_enabled').value,
- 'max-size-kb': -1,
- 'max-objects': -1
+ max_size_kb: -1,
+ max_objects: -1
};
if (!this.userForm.get('bucket_quota_max_size_unlimited').value) {
// Convert the given value to bytes.
@@ -672,10 +658,10 @@ export class RgwUserFormComponent implements OnInit {
this.userForm.get('bucket_quota_max_size').value
);
// Finally convert the value to KiB.
- result['max-size-kb'] = (bytes / 1024).toFixed(0) as any;
+ result['max_size_kb'] = (bytes / 1024).toFixed(0) as any;
}
if (!this.userForm.get('bucket_quota_max_objects_unlimited').value) {
- result['max-objects'] = this.userForm.get('bucket_quota_max_objects').value;
+ result['max_objects'] = this.userForm.get('bucket_quota_max_objects').value;
}
return result;
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts
index 1a7c7c73173..a57b1be688d 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.spec.ts
@@ -31,7 +31,7 @@ describe('RgwBucketService', () => {
service.list().subscribe((resp) => {
result = resp;
});
- const req = httpTesting.expectOne('/api/rgw/proxy/bucket');
+ const req = httpTesting.expectOne('/api/rgw/bucket');
req.flush([]);
expect(req.request.method).toBe('GET');
expect(result).toEqual([]);
@@ -42,13 +42,13 @@ describe('RgwBucketService', () => {
service.list().subscribe((resp) => {
result = resp;
});
- let req = httpTesting.expectOne('/api/rgw/proxy/bucket');
+ let req = httpTesting.expectOne('/api/rgw/bucket');
req.flush(['foo', 'bar']);
- req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo');
+ req = httpTesting.expectOne('/api/rgw/bucket/foo');
req.flush({ name: 'foo' });
- req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=bar');
+ req = httpTesting.expectOne('/api/rgw/bucket/bar');
req.flush({ name: 'bar' });
expect(req.request.method).toBe('GET');
@@ -57,35 +57,31 @@ describe('RgwBucketService', () => {
it('should call get', () => {
service.get('foo').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo');
+ const req = httpTesting.expectOne('/api/rgw/bucket/foo');
expect(req.request.method).toBe('GET');
});
it('should call create', () => {
service.create('foo', 'bar').subscribe();
- const req = httpTesting.expectOne('/api/rgw/bucket');
+ const req = httpTesting.expectOne('/api/rgw/bucket?bucket=foo&uid=bar');
expect(req.request.method).toBe('POST');
- expect(req.request.body).toEqual({
- bucket: 'foo',
- uid: 'bar'
- });
});
it('should call update', () => {
service.update('foo', 'bar', 'baz').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=bar&bucket-id=foo&uid=baz');
+ const req = httpTesting.expectOne('/api/rgw/bucket/foo?bucket_id=bar&uid=baz');
expect(req.request.method).toBe('PUT');
});
it('should call delete, with purgeObjects = true', () => {
service.delete('foo').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo&purge-objects=true');
+ const req = httpTesting.expectOne('/api/rgw/bucket/foo?purge_objects=true');
expect(req.request.method).toBe('DELETE');
});
it('should call delete, with purgeObjects = false', () => {
service.delete('foo', false).subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/bucket?bucket=foo&purge-objects=false');
+ const req = httpTesting.expectOne('/api/rgw/bucket/foo?purge_objects=false');
expect(req.request.method).toBe('DELETE');
});
@@ -94,7 +90,7 @@ describe('RgwBucketService', () => {
service.exists('foo').subscribe((resp) => {
result = resp;
});
- const req = httpTesting.expectOne('/api/rgw/proxy/bucket');
+ const req = httpTesting.expectOne('/api/rgw/bucket');
expect(req.request.method).toBe('GET');
req.flush(['foo', 'bar']);
expect(result).toBe(true);
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts
index e6b4062a5a1..baa609218b8 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-bucket.service.ts
@@ -11,7 +11,7 @@ import { ApiModule } from './api.module';
providedIn: ApiModule
})
export class RgwBucketService {
- private url = '/api/rgw/proxy/bucket';
+ private url = '/api/rgw/bucket';
constructor(private http: HttpClient) {}
@@ -26,10 +26,12 @@ export class RgwBucketService {
return observableForkJoin(
buckets.map((bucket: string) => {
return this.get(bucket);
- }));
+ })
+ );
}
return observableOf([]);
- }));
+ })
+ );
}
/**
@@ -41,32 +43,27 @@ 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(`${this.url}/${bucket}`);
}
create(bucket: string, uid: string) {
- const body = {
- bucket: bucket,
- uid: uid
- };
- return this.http.post('/api/rgw/bucket', body);
- }
-
- update(bucketId: string, bucket: string, uid: string) {
let params = new HttpParams();
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.post(this.url, null, { params: params });
+ }
+
+ update(bucket: string, bucketId: string, uid: string) {
+ let params = new HttpParams();
+ params = params.append('bucket_id', bucketId);
+ params = params.append('uid', uid);
+ return this.http.put(`${this.url}/${bucket}`, null, { params: params});
}
delete(bucket: string, purgeObjects = true) {
let params = new HttpParams();
- params = params.append('bucket', bucket);
- params = params.append('purge-objects', purgeObjects ? 'true' : 'false');
- return this.http.delete(this.url, { params: params });
+ params = params.append('purge_objects', purgeObjects ? 'true' : 'false');
+ return this.http.delete(`${this.url}/${bucket}`, { params: params });
}
/**
@@ -79,6 +76,7 @@ export class RgwBucketService {
mergeMap((resp: string[]) => {
const index = _.indexOf(resp, bucket);
return observableOf(-1 !== index);
- }));
+ })
+ );
}
}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts
index e9569ef19ec..b9acad14d48 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.spec.ts
@@ -33,7 +33,7 @@ describe('RgwUserService', () => {
service.list().subscribe((resp) => {
result = resp;
});
- const req = httpTesting.expectOne('/api/rgw/proxy/metadata/user');
+ const req = httpTesting.expectOne('/api/rgw/user');
expect(req.request.method).toBe('GET');
req.flush([]);
expect(result).toEqual([]);
@@ -45,15 +45,15 @@ describe('RgwUserService', () => {
result = resp;
});
- let req = httpTesting.expectOne('/api/rgw/proxy/metadata/user');
+ let req = httpTesting.expectOne('/api/rgw/user');
expect(req.request.method).toBe('GET');
req.flush(['foo', 'bar']);
- req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo');
+ req = httpTesting.expectOne('/api/rgw/user/foo');
expect(req.request.method).toBe('GET');
req.flush({ name: 'foo' });
- req = httpTesting.expectOne('/api/rgw/proxy/user?uid=bar');
+ req = httpTesting.expectOne('/api/rgw/user/bar');
expect(req.request.method).toBe('GET');
req.flush({ name: 'bar' });
@@ -62,100 +62,79 @@ describe('RgwUserService', () => {
it('should call enumerate', () => {
service.enumerate().subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/metadata/user');
+ const req = httpTesting.expectOne('/api/rgw/user');
expect(req.request.method).toBe('GET');
});
it('should call get', () => {
service.get('foo').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo');
+ const req = httpTesting.expectOne('/api/rgw/user/foo');
expect(req.request.method).toBe('GET');
});
it('should call getQuota', () => {
service.getQuota('foo').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?quota&uid=foo');
+ const req = httpTesting.expectOne('/api/rgw/user/foo/quota');
expect(req.request.method).toBe('GET');
});
- it('should call put', () => {
- service.put({ foo: 'bar' }).subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?foo=bar');
+ it('should call update', () => {
+ service.update('foo', { xxx: 'yyy' }).subscribe();
+ const req = httpTesting.expectOne('/api/rgw/user/foo?xxx=yyy');
expect(req.request.method).toBe('PUT');
});
- it('should call putQuota', () => {
- service.putQuota({ foo: 'bar' }).subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?quota&foo=bar');
+ it('should call updateQuota', () => {
+ service.updateQuota('foo', { xxx: 'yyy' }).subscribe();
+ const req = httpTesting.expectOne('/api/rgw/user/foo/quota?xxx=yyy');
expect(req.request.method).toBe('PUT');
});
- it('should call post', () => {
- service.post({ foo: 'bar' }).subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?foo=bar');
+ it('should call create', () => {
+ service.create({ foo: 'bar' }).subscribe();
+ const req = httpTesting.expectOne('/api/rgw/user?foo=bar');
expect(req.request.method).toBe('POST');
});
it('should call delete', () => {
service.delete('foo').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo');
+ const req = httpTesting.expectOne('/api/rgw/user/foo');
expect(req.request.method).toBe('DELETE');
});
- it('should call addSubuser with unrecognized permission', () => {
- service.addSubuser('foo', 'bar', 'baz', null, true).subscribe();
- const req = httpTesting.expectOne(
- '/api/rgw/proxy/user?uid=foo&subuser=bar&key-type=swift&access=baz&generate-secret=true'
- );
- expect(req.request.method).toBe('PUT');
- });
-
- it('should call addSubuser with mapped permission', () => {
- service.addSubuser('foo', 'bar', 'full-control', 'baz', false).subscribe();
- const req = httpTesting.expectOne(
- '/api/rgw/proxy/user?uid=foo&subuser=bar&key-type=swift&access=full&secret-key=baz'
- );
- expect(req.request.method).toBe('PUT');
+ it('should call createSubuser', () => {
+ service.createSubuser('foo', { xxx: 'yyy' }).subscribe();
+ const req = httpTesting.expectOne('/api/rgw/user/foo/subuser?xxx=yyy');
+ expect(req.request.method).toBe('POST');
});
it('should call deleteSubuser', () => {
service.deleteSubuser('foo', 'bar').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?uid=foo&subuser=bar&purge-keys=true');
+ const req = httpTesting.expectOne('/api/rgw/user/foo/subuser/bar');
expect(req.request.method).toBe('DELETE');
});
it('should call addCapability', () => {
service.addCapability('foo', 'bar', 'baz').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?caps&uid=foo&user-caps=bar=baz');
- expect(req.request.method).toBe('PUT');
+ const req = httpTesting.expectOne('/api/rgw/user/foo/capability?type=bar&perm=baz');
+ expect(req.request.method).toBe('POST');
});
it('should call deleteCapability', () => {
service.deleteCapability('foo', 'bar', 'baz').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?caps&uid=foo&user-caps=bar=baz');
+ const req = httpTesting.expectOne('/api/rgw/user/foo/capability?type=bar&perm=baz');
expect(req.request.method).toBe('DELETE');
});
- it('should call addS3Key, with generateKey true', () => {
- service.addS3Key('1', 'foo', 'bar', 'baz', true).subscribe();
- const req = httpTesting.expectOne(
- '/api/rgw/proxy/user?key&uid=1&key-type=s3&generate-key=true&subuser=foo'
- );
- expect(req.request.method).toBe('PUT');
- });
-
- it('should call addS3Key, with generateKey false', () => {
- service.addS3Key('1', 'foo', 'bar', 'baz', false).subscribe();
- const req = httpTesting.expectOne(
- '/api/rgw/proxy/user?key' +
- '&uid=1&key-type=s3&generate-key=false&access-key=bar&secret-key=baz&subuser=foo'
- );
- expect(req.request.method).toBe('PUT');
+ it('should call addS3Key', () => {
+ service.addS3Key('foo', { xxx: 'yyy' }).subscribe();
+ const req = httpTesting.expectOne('/api/rgw/user/foo/key?key_type=s3&xxx=yyy');
+ expect(req.request.method).toBe('POST');
});
it('should call deleteS3Key', () => {
service.deleteS3Key('foo', 'bar').subscribe();
- const req = httpTesting.expectOne('/api/rgw/proxy/user?key&uid=foo&key-type=s3&access-key=bar');
+ const req = httpTesting.expectOne('/api/rgw/user/foo/key?key_type=s3&access_key=bar');
expect(req.request.method).toBe('DELETE');
});
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts
index f1b78f12baf..e93e2102d3f 100644
--- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts
+++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/rgw-user.service.ts
@@ -2,7 +2,7 @@ import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import * as _ from 'lodash';
-import {forkJoin as observableForkJoin, of as observableOf } from 'rxjs';
+import { forkJoin as observableForkJoin, of as observableOf } from 'rxjs';
import { mergeMap } from 'rxjs/operators';
import { ApiModule } from './api.module';
@@ -11,7 +11,7 @@ import { ApiModule } from './api.module';
providedIn: ApiModule
})
export class RgwUserService {
- private url = '/api/rgw/proxy/user';
+ private url = '/api/rgw/user';
constructor(private http: HttpClient) {}
@@ -26,10 +26,12 @@ export class RgwUserService {
return observableForkJoin(
uids.map((uid: string) => {
return this.get(uid);
- }));
+ })
+ );
}
return observableOf([]);
- }));
+ })
+ );
}
/**
@@ -37,38 +39,18 @@ export class RgwUserService {
* @return {Observable}
*/
enumerate() {
- return this.http.get('/api/rgw/proxy/metadata/user');
+ return this.http.get(this.url);
}
get(uid: string) {
- let params = new HttpParams();
- params = params.append('uid', uid);
- return this.http.get(this.url, { params: params });
+ return this.http.get(`${this.url}/${uid}`);
}
getQuota(uid: string) {
- let params = new HttpParams();
- params = params.append('uid', uid);
- return this.http.get(`${this.url}?quota`, { params: params });
+ return this.http.get(`${this.url}/${uid}/quota`);
}
- put(args: object) {
- let params = new HttpParams();
- _.keys(args).forEach((key) => {
- params = params.append(key, args[key]);
- });
- return this.http.put(this.url, null, { params: params });
- }
-
- putQuota(args: object) {
- let params = new HttpParams();
- _.keys(args).forEach((key) => {
- params = params.append(key, args[key]);
- });
- return this.http.put(`${this.url}?quota`, null, { params: params });
- }
-
- post(args: object) {
+ create(args: object) {
let params = new HttpParams();
_.keys(args).forEach((key) => {
params = params.append(key, args[key]);
@@ -76,86 +58,66 @@ export class RgwUserService {
return this.http.post(this.url, null, { params: params });
}
- delete(uid: string) {
+ update(uid: string, args: object) {
let params = new HttpParams();
- params = params.append('uid', uid);
- return this.http.delete(this.url, { params: params });
+ _.keys(args).forEach((key) => {
+ params = params.append(key, args[key]);
+ });
+ return this.http.put(`${this.url}/${uid}`, null, { params: params });
}
- addSubuser(
- uid: string,
- subuser: string,
- permissions: string,
- secretKey: string,
- generateSecret: boolean
- ) {
- const mapPermissions = {
- 'full-control': 'full',
- 'read-write': 'readwrite'
- };
+ updateQuota(uid: string, args: object) {
let params = new HttpParams();
- params = params.append('uid', uid);
- params = params.append('subuser', subuser);
- params = params.append('key-type', 'swift');
- params = params.append(
- 'access',
- permissions in mapPermissions ? mapPermissions[permissions] : permissions
- );
- if (generateSecret) {
- params = params.append('generate-secret', 'true');
- } else {
- params = params.append('secret-key', secretKey);
- }
- return this.http.put(this.url, null, { params: params });
+ _.keys(args).forEach((key) => {
+ params = params.append(key, args[key]);
+ });
+ return this.http.put(`${this.url}/${uid}/quota`, null, { params: params });
+ }
+
+ delete(uid: string) {
+ return this.http.delete(`${this.url}/${uid}`);
+ }
+
+ createSubuser(uid: string, args: object) {
+ let params = new HttpParams();
+ _.keys(args).forEach((key) => {
+ params = params.append(key, args[key]);
+ });
+ return this.http.post(`${this.url}/${uid}/subuser`, null, { params: params });
}
deleteSubuser(uid: string, subuser: string) {
- let params = new HttpParams();
- params = params.append('uid', uid);
- params = params.append('subuser', subuser);
- params = params.append('purge-keys', 'true');
- return this.http.delete(this.url, { params: params });
+ return this.http.delete(`${this.url}/${uid}/subuser/${subuser}`);
}
addCapability(uid: string, type: string, perm: string) {
let params = new HttpParams();
- params = params.append('uid', uid);
- params = params.append('user-caps', `${type}=${perm}`);
- return this.http.put(`${this.url}?caps`, null, { params: params });
+ params = params.append('type', type);
+ params = params.append('perm', perm);
+ return this.http.post(`${this.url}/${uid}/capability`, null, { params: params });
}
deleteCapability(uid: string, type: string, perm: string) {
let params = new HttpParams();
- params = params.append('uid', uid);
- params = params.append('user-caps', `${type}=${perm}`);
- return this.http.delete(`${this.url}?caps`, { params: params });
+ params = params.append('type', type);
+ params = params.append('perm', perm);
+ return this.http.delete(`${this.url}/${uid}/capability`, { params: params });
}
- addS3Key(
- uid: string,
- subuser: string,
- accessKey: string,
- secretKey: string,
- generateKey: boolean
- ) {
+ addS3Key(uid: string, args: object) {
let params = new HttpParams();
- params = params.append('uid', uid);
- params = params.append('key-type', 's3');
- params = params.append('generate-key', generateKey ? 'true' : 'false');
- if (!generateKey) {
- params = params.append('access-key', accessKey);
- params = params.append('secret-key', secretKey);
- }
- params = params.append('subuser', subuser);
- return this.http.put(`${this.url}?key`, null, { params: params });
+ params = params.append('key_type', 's3');
+ _.keys(args).forEach((key) => {
+ params = params.append(key, args[key]);
+ });
+ return this.http.post(`${this.url}/${uid}/key`, null, { params: params });
}
deleteS3Key(uid: string, accessKey: string) {
let params = new HttpParams();
- params = params.append('uid', uid);
- params = params.append('key-type', 's3');
- params = params.append('access-key', accessKey);
- return this.http.delete(`${this.url}?key`, { params: params });
+ params = params.append('key_type', 's3');
+ params = params.append('access_key', accessKey);
+ return this.http.delete(`${this.url}/${uid}/key`, { params: params });
}
/**
@@ -168,6 +130,7 @@ export class RgwUserService {
mergeMap((resp: string[]) => {
const index = _.indexOf(resp, uid);
return observableOf(-1 !== index);
- }));
+ })
+ );
}
}