From: Volker Theile Date: Tue, 10 Mar 2020 11:38:20 +0000 (+0100) Subject: mgr/dashboard: Create bucket with x-amz-bucket-object-lock-enabled X-Git-Tag: v15.1.1~13^2 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=refs%2Fpull%2F33821%2Fhead;p=ceph.git mgr/dashboard: Create bucket with x-amz-bucket-object-lock-enabled Fixes: https://tracker.ceph.com/issues/43446 Signed-off-by: Volker Theile --- diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py index 72878498ee8..9cb3504b5ae 100644 --- a/qa/tasks/mgr/dashboard/test_rgw.py +++ b/qa/tasks/mgr/dashboard/test_rgw.py @@ -265,7 +265,7 @@ class RgwBucketTest(RgwTestCase): self.assertStatus(200) self.assertEqual(len(data), 0) - def test_create_get_update_delete_w_tenant(self): + def test_crud_w_tenant(self): # Create a new bucket. The tenant of the user is used when # the bucket is created. self._post( @@ -361,6 +361,56 @@ class RgwBucketTest(RgwTestCase): self.assertStatus(200) self.assertEqual(len(data), 0) + def test_crud_w_locking(self): + # Create + self._post('/api/rgw/bucket', + params={ + 'bucket': 'teuth-test-bucket', + 'uid': 'teuth-test-user', + 'zonegroup': 'default', + 'placement_target': 'default-placement', + 'lock_enabled': 'true', + 'lock_mode': 'GOVERNANCE', + 'lock_retention_period_days': '0', + 'lock_retention_period_years': '1' + }) + self.assertStatus(201) + # Read + data = self._get('/api/rgw/bucket/teuth-test-bucket') + self.assertStatus(200) + self.assertSchema( + data, + JObj(sub_elems={ + 'lock_enabled': JLeaf(bool), + 'lock_mode': JLeaf(str), + 'lock_retention_period_days': JLeaf(int), + 'lock_retention_period_years': JLeaf(int) + }, + allow_unknown=True)) + self.assertTrue(data['lock_enabled']) + self.assertEqual(data['lock_mode'], 'GOVERNANCE') + self.assertEqual(data['lock_retention_period_days'], 0) + self.assertEqual(data['lock_retention_period_years'], 1) + # Update + self._put('/api/rgw/bucket/teuth-test-bucket', + params={ + 'bucket_id': data['id'], + 'uid': 'teuth-test-user', + 'lock_mode': 'COMPLIANCE', + 'lock_retention_period_days': '15', + 'lock_retention_period_years': '0' + }) + self.assertStatus(200) + data = self._get('/api/rgw/bucket/teuth-test-bucket') + self.assertTrue(data['lock_enabled']) + self.assertEqual(data['lock_mode'], 'COMPLIANCE') + self.assertEqual(data['lock_retention_period_days'], 15) + self.assertEqual(data['lock_retention_period_years'], 0) + self.assertStatus(200) + # Delete + self._delete('/api/rgw/bucket/teuth-test-bucket') + self.assertStatus(204) + class RgwDaemonTest(RgwTestCase): diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index 542ba396752..433113763f1 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -14,20 +14,18 @@ from ..security import Scope, Permission from ..services.auth import AuthManager, JwtManager from ..services.ceph_service import CephService from ..services.rgw_client import RgwClient -from ..tools import json_str_to_object +from ..tools import json_str_to_object, str_to_bool try: from typing import List except ImportError: pass # Just for type checking - logger = logging.getLogger('controllers.rgw') @ApiController('/rgw', Scope.RGW) class Rgw(BaseController): - @Endpoint() @ReadPermission def status(self): @@ -56,7 +54,6 @@ class Rgw(BaseController): @ApiController('/rgw/daemon', Scope.RGW) class RgwDaemon(RESTController): - def list(self): # type: () -> List[dict] daemons = [] @@ -103,7 +100,6 @@ class RgwDaemon(RESTController): class RgwRESTController(RESTController): - def proxy(self, method, path, params=None, json_response=True): try: instance = RgwClient.admin_instance() @@ -117,7 +113,6 @@ class RgwRESTController(RESTController): @ApiController('/rgw/site', Scope.RGW) class RgwSite(RgwRESTController): - def list(self, query=None): if query == 'placement-targets': instance = RgwClient.admin_instance() @@ -132,7 +127,6 @@ class RgwSite(RgwRESTController): @ApiController('/rgw/bucket', Scope.RGW) class RgwBucket(RgwRESTController): - def _append_bid(self, bucket): """ Append the bucket identifier that looks like [/]. @@ -161,6 +155,17 @@ class RgwBucket(RgwRESTController): rgw_client.set_bucket_versioning(bucket_name, versioning_state, mfa_delete, mfa_token_serial, mfa_token_pin) + def _get_locking(self, owner, bucket_name): + rgw_client = RgwClient.instance(owner) + return rgw_client.get_bucket_locking(bucket_name) + + def _set_locking(self, owner, bucket_name, mode, + retention_period_days, retention_period_years): + rgw_client = RgwClient.instance(owner) + return rgw_client.set_bucket_locking(bucket_name, mode, + int(retention_period_days), + int(retention_period_years)) + @staticmethod def strip_tenant_from_bucket_name(bucket_name): # type (str) -> str @@ -195,41 +200,69 @@ class RgwBucket(RgwRESTController): def get(self, bucket): # type: (str) -> dict result = self.proxy('GET', 'bucket', {'bucket': bucket}) + bucket_name = RgwBucket.get_s3_bucket_name(result['bucket'], + result['tenant']) + + # Append the versioning configuration. + versioning = self._get_versioning(result['owner'], bucket_name) + result['versioning'] = versioning['Status'] + result['mfa_delete'] = versioning['MfaDelete'] - bucket_versioning =\ - self._get_versioning(result['owner'], - RgwBucket.get_s3_bucket_name(result['bucket'], result['tenant'])) - result['versioning'] = bucket_versioning['Status'] - result['mfa_delete'] = bucket_versioning['MfaDelete'] + # Append the locking configuration. + locking = self._get_locking(result['owner'], bucket_name) + result.update(locking) return self._append_bid(result) - def create(self, bucket, uid, zonegroup=None, placement_target=None): + def create(self, bucket, uid, zonegroup=None, placement_target=None, + lock_enabled='false', lock_mode=None, + lock_retention_period_days=None, + lock_retention_period_years=None): + lock_enabled = str_to_bool(lock_enabled) try: rgw_client = RgwClient.instance(uid) - return rgw_client.create_bucket(bucket, zonegroup, placement_target) + result = rgw_client.create_bucket(bucket, zonegroup, + placement_target, + lock_enabled) + if lock_enabled: + self._set_locking(uid, bucket, lock_mode, + lock_retention_period_days, + lock_retention_period_years) + return result except RequestException as e: raise DashboardException(e, http_status_code=500, component='rgw') - def set(self, bucket, bucket_id, uid, versioning_state=None, mfa_delete=None, - mfa_token_serial=None, mfa_token_pin=None): + def set(self, bucket, bucket_id, uid, versioning_state=None, + mfa_delete=None, mfa_token_serial=None, mfa_token_pin=None, + lock_mode=None, lock_retention_period_days=None, + lock_retention_period_years=None): # When linking a non-tenant-user owned bucket to a tenanted user, we # need to prefix bucket name with '/'. e.g. photos -> /photos if '$' in uid and '/' not in bucket: bucket = '/{}'.format(bucket) # Link bucket to new user: - result = self.proxy('PUT', 'bucket', { - 'bucket': bucket, - 'bucket-id': bucket_id, - 'uid': uid - }, json_response=False) + result = self.proxy('PUT', + 'bucket', { + 'bucket': bucket, + 'bucket-id': bucket_id, + 'uid': uid + }, + json_response=False) + + uid_tenant = uid[:uid.find('$')] if uid.find('$') >= 0 else None + bucket_name = RgwBucket.get_s3_bucket_name(bucket, uid_tenant) if versioning_state: - uid_tenant = uid[:uid.find('$')] if uid.find('$') >= 0 else None - self._set_versioning(uid, - RgwBucket.get_s3_bucket_name(bucket, uid_tenant), - versioning_state, mfa_delete, mfa_token_serial, mfa_token_pin) + self._set_versioning(uid, bucket_name, versioning_state, + mfa_delete, mfa_token_serial, mfa_token_pin) + + # Update locking if it is enabled. + locking = self._get_locking(uid, bucket_name) + if locking['lock_enabled']: + self._set_locking(uid, bucket_name, lock_mode, + lock_retention_period_days, + lock_retention_period_years) return self._append_bid(result) @@ -242,7 +275,6 @@ class RgwBucket(RgwRESTController): @ApiController('/rgw/user', Scope.RGW) class RgwUser(RgwRESTController): - def _append_uid(self, user): """ Append the user identifier that looks like [$]. diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html index 9f0c2ddd5dd..3d0cb7b4829 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-details/rgw-bucket-details.component.html @@ -103,6 +103,35 @@ + + + Locking + + + + + + + + + + + + + + + + + + + + + +
Enabled{{ bucket.lock_enabled | booleanText }}
Mode{{ bucket.lock_mode }}
Days{{ bucket.lock_retention_period_days }}
Years{{ bucket.lock_retention_period_years }}
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html index 6d5dcca0102..a8bf4c0d957 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/rgw/rgw-bucket-form/rgw-bucket-form.component.html @@ -119,10 +119,10 @@ - Versioning +
+ Versioning -
@@ -135,14 +135,17 @@ (change)="updateVersioning()"> + i18n>Enabled Enables versioning for the objects in the bucket.
+
+ +
Multi-Factor Authentication @@ -198,8 +201,96 @@ i18n>This field is required. - +
+ + +
+ Locking + + +
+
+
+ + + + Enables locking for the objects in the bucket. Locking can only be enabled while creating a bucket. + +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ + The entered value must be a positive integer. + Retention period requires either Days or Years. +
+
+ + +
+ +
+ + The entered value must be a positive integer. + Retention period requires either Days or Years. +
+
+