From 8955809716c2e75ece65423ce1eb079990c34590 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Alfonso=20Mart=C3=ADnez?= Date: Thu, 26 Sep 2019 08:41:14 +0200 Subject: [PATCH] mgr/dashboard: enable/disable versioning on RGW bucket MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Fixes: https://tracker.ceph.com/issues/40920 Signed-off-by: Alfonso Martínez --- qa/tasks/mgr/dashboard/test_rgw.py | 40 ++++++++++--- src/pybind/mgr/dashboard/controllers/rgw.py | 51 +++++++++++++++- .../dashboard/frontend/e2e/rgw/buckets.po.ts | 59 +++++++++++++++++-- .../rgw-bucket-details.component.html | 5 ++ .../rgw-bucket-form.component.html | 37 ++++++++++++ .../rgw-bucket-form.component.spec.ts | 2 +- .../rgw-bucket-form.component.ts | 32 +++++----- .../app/shared/api/rgw-bucket.service.spec.ts | 6 +- .../src/app/shared/api/rgw-bucket.service.ts | 5 +- .../mgr/dashboard/services/rgw_client.py | 31 ++++++++++ 10 files changed, 237 insertions(+), 31 deletions(-) diff --git a/qa/tasks/mgr/dashboard/test_rgw.py b/qa/tasks/mgr/dashboard/test_rgw.py index ad6b4b8cad7..a04ee7fc644 100644 --- a/qa/tasks/mgr/dashboard/test_rgw.py +++ b/qa/tasks/mgr/dashboard/test_rgw.py @@ -113,16 +113,22 @@ class RgwBucketTest(RgwTestCase): def setUpClass(cls): cls.create_test_user = True super(RgwBucketTest, cls).setUpClass() - # Create a tenanted user. + # Create tenanted users. cls._radosgw_admin_cmd([ 'user', 'create', '--tenant', 'testx', '--uid', 'teuth-test-user', '--display-name', 'tenanted teuth-test-user' ]) + cls._radosgw_admin_cmd([ + 'user', 'create', '--tenant', 'testx2', '--uid', 'teuth-test-user2', + '--display-name', 'tenanted teuth-test-user 2' + ]) @classmethod def tearDownClass(cls): cls._radosgw_admin_cmd( ['user', 'rm', '--tenant', 'testx', '--uid=teuth-test-user']) + cls._radosgw_admin_cmd( + ['user', 'rm', '--tenant', 'testx2', '--uid=teuth-test-user2']) super(RgwBucketTest, cls).tearDownClass() def test_all(self): @@ -171,13 +177,16 @@ class RgwBucketTest(RgwTestCase): }, allow_unknown=True)) self.assertEqual(data['bucket'], 'teuth-test-bucket') self.assertEqual(data['owner'], 'admin') + self.assertEqual(data['placement_rule'], 'default-placement') + self.assertEqual(data['versioning'], 'Suspended') # Update the bucket. self._put( '/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'teuth-test-user' + 'uid': 'teuth-test-user', + 'versioning_state': 'Enabled' }) self.assertStatus(200) data = self._get('/api/rgw/bucket/teuth-test-bucket') @@ -188,6 +197,7 @@ class RgwBucketTest(RgwTestCase): 'tenant': JLeaf(str) }, allow_unknown=True)) self.assertEqual(data['owner'], 'teuth-test-user') + self.assertEqual(data['versioning'], 'Enabled') # Delete the bucket. self._delete('/api/rgw/bucket/teuth-test-bucket') @@ -239,34 +249,50 @@ class RgwBucketTest(RgwTestCase): # Get the bucket. data = _verify_tenant_bucket('teuth-test-bucket', 'testx', 'teuth-test-user') + self.assertEqual(data['placement_rule'], 'default-placement') + self.assertEqual(data['versioning'], 'Suspended') - # Change owner to a non-tenanted user + # Update bucket: different user with different tenant, enable versioning. self._put( '/api/rgw/bucket/{}'.format( urllib.quote_plus('testx/teuth-test-bucket')), + params={ + 'bucket_id': data['id'], + 'uid': 'testx2$teuth-test-user2', + 'versioning_state': 'Enabled' + }) + data = _verify_tenant_bucket('teuth-test-bucket', 'testx2', 'teuth-test-user2') + self.assertEqual(data['versioning'], 'Enabled') + + # Change owner to a non-tenanted user + self._put( + '/api/rgw/bucket/{}'.format( + urllib.quote_plus('testx2/teuth-test-bucket')), params={ 'bucket_id': data['id'], 'uid': 'admin' }) self.assertStatus(200) - data = self._get('/api/rgw/bucket/{}'.format( - urllib.quote_plus('teuth-test-bucket'))) + data = self._get('/api/rgw/bucket/teuth-test-bucket') self.assertStatus(200) self.assertIn('owner', data) self.assertEqual(data['owner'], 'admin') self.assertEqual(data['tenant'], '') self.assertEqual(data['bucket'], 'teuth-test-bucket') self.assertEqual(data['bid'], 'teuth-test-bucket') + self.assertEqual(data['versioning'], 'Enabled') - # Change owner back to tenanted user + # Change owner back to tenanted user, suspend versioning. self._put( '/api/rgw/bucket/teuth-test-bucket', params={ 'bucket_id': data['id'], - 'uid': 'testx$teuth-test-user' + 'uid': 'testx$teuth-test-user', + 'versioning_state': 'Suspended' }) self.assertStatus(200) data = _verify_tenant_bucket('teuth-test-bucket', 'testx', 'teuth-test-user') + self.assertEqual(data['versioning'], 'Suspended') # Delete the bucket. self._delete('/api/rgw/bucket/{}'.format( diff --git a/src/pybind/mgr/dashboard/controllers/rgw.py b/src/pybind/mgr/dashboard/controllers/rgw.py index e355503780f..f743099c0c3 100644 --- a/src/pybind/mgr/dashboard/controllers/rgw.py +++ b/src/pybind/mgr/dashboard/controllers/rgw.py @@ -137,11 +137,51 @@ class RgwBucket(RgwRESTController): if bucket['tenant'] else bucket['bucket'] return bucket + def _get_versioning(self, owner, bucket_name): + rgw_client = RgwClient.instance(owner) + return rgw_client.get_bucket_versioning(bucket_name) + + def _set_versioning(self, owner, bucket_name, versioning_state): + rgw_client = RgwClient.instance(owner) + return rgw_client.set_bucket_versioning(bucket_name, versioning_state) + + @staticmethod + def strip_tenant_from_bucket_name(bucket_name): + # type (str) => str + """ + >>> RgwBucket.strip_tenant_from_bucket_name('tenant/bucket-name') + 'bucket-name' + >>> RgwBucket.strip_tenant_from_bucket_name('bucket-name') + 'bucket-name' + """ + return bucket_name[bucket_name.find('/') + 1:] + + @staticmethod + def get_s3_bucket_name(bucket_name, tenant=None): + # type (str, str) => str + """ + >>> RgwBucket.get_s3_bucket_name('bucket-name', 'tenant') + 'tenant:bucket-name' + >>> RgwBucket.get_s3_bucket_name('tenant/bucket-name', 'tenant') + 'tenant:bucket-name' + >>> RgwBucket.get_s3_bucket_name('bucket-name') + 'bucket-name' + """ + bucket_name = RgwBucket.strip_tenant_from_bucket_name(bucket_name) + if tenant: + bucket_name = '{}:{}'.format(tenant, bucket_name) + return bucket_name + def list(self): return self.proxy('GET', 'bucket') def get(self, bucket): result = self.proxy('GET', 'bucket', {'bucket': bucket}) + + result['versioning'] =\ + self._get_versioning(result['owner'], + RgwBucket.get_s3_bucket_name(result['bucket'], result['tenant'])) + return self._append_bid(result) def create(self, bucket, uid, zonegroup=None, placement_target=None): @@ -151,16 +191,25 @@ class RgwBucket(RgwRESTController): except RequestException as e: raise DashboardException(e, http_status_code=500, component='rgw') - def set(self, bucket, bucket_id, uid): + def set(self, bucket, bucket_id, uid, versioning_state=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) + + 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) + return self._append_bid(result) def delete(self, bucket, purge_objects='true'): diff --git a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts b/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts index 6a11c92f25a..b2cd3c45ad6 100644 --- a/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts +++ b/src/pybind/mgr/dashboard/frontend/e2e/rgw/buckets.po.ts @@ -8,6 +8,8 @@ const pages = { export class BucketsPageHelper extends PageHelper { pages = pages; + versioningStateEnabled = 'Enabled'; + versioningStateSuspended = 'Suspended'; /** * TODO add check to verify the existance of the bucket! @@ -50,6 +52,16 @@ export class BucketsPageHelper extends PageHelper { ); await element(by.id('owner')).click(); // click owner dropdown menu await element(by.cssContainingText('select[name=owner] option', new_owner)).click(); // select the new user + + // Enable versioning + await expect(element(by.css('input[name=versioning]:checked')).getAttribute('value')).toBe( + this.versioningStateSuspended + ); + await element(by.css('input[id=enabled]')).click(); + await expect(element(by.css('input[name=versioning]:checked')).getAttribute('value')).toBe( + this.versioningStateEnabled + ); + await element(by.cssContainingText('button', 'Edit Bucket')).click(); // wait to be back on buckets page with table visible and click @@ -59,10 +71,45 @@ export class BucketsPageHelper extends PageHelper { ); // check its details table for edited owner field - const element_details_table = element - .all(by.css('.table.table-striped.table-bordered')) - .first(); - return expect(element_details_table.getText()).toMatch(new_owner); + let bucketDataTable = element.all(by.css('.table.table-striped.table-bordered')).first(); + await expect(bucketDataTable.getText()).toMatch(new_owner); + + // Check versioning enabled: + const ownerValueCell = bucketDataTable + .all(by.css('tr')) + .get(2) + .all(by.css('td')) + .last(); + await expect(ownerValueCell.getText()).toEqual(new_owner); + let versioningValueCell = bucketDataTable + .all(by.css('tr')) + .get(11) + .all(by.css('td')) + .last(); + await expect(versioningValueCell.getText()).toEqual(this.versioningStateEnabled); + + // Disable versioning: + await this.getFirstTableCellWithText(name).click(); // click on the bucket you want to edit in the table + await element(by.cssContainingText('button', 'Edit')).click(); // click button to move to edit page + await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); + await element(by.css('input[id=suspended]')).click(); + await expect(element(by.css('input[name=versioning]:checked')).getAttribute('value')).toBe( + this.versioningStateSuspended + ); + await element(by.cssContainingText('button', 'Edit Bucket')).click(); + + // Check versioning suspended: + await this.waitClickableAndClick( + this.getFirstTableCellWithText(name), + 'Could not return to buckets page and load table after editing bucket' + ); + bucketDataTable = element.all(by.css('.table.table-striped.table-bordered')).first(); + versioningValueCell = bucketDataTable + .all(by.css('tr')) + .get(11) + .all(by.css('td')) + .last(); + return expect(versioningValueCell.getText()).toEqual(this.versioningStateSuspended); } async testInvalidCreate() { @@ -145,6 +192,10 @@ export class BucketsPageHelper extends PageHelper { await this.waitTextToBePresent(this.getBreadcrumb(), 'Edit'); + await expect(element(by.css('input[name=versioning]:checked')).getAttribute('value')).toBe( + this.versioningStateSuspended + ); + // Chooses 'Select a user' rather than a valid owner on Edit Bucket page // and checks if it's an invalid input const ownerDropDown = element(by.id('owner')); 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 f54277de110..d4ab903dc42 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 @@ -59,6 +59,11 @@ class="bold">Zonegroup {{ bucket.zonegroup }} + + Versioning + {{ bucket.versioning }} + 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 ce120fbf600..8182e61f63d 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 @@ -127,6 +127,43 @@ + +
+ Versioning +
+ + +
+
+ + + This field is required. +
+
+