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):
}, 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')
'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')
# 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(
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):
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'):
export class BucketsPageHelper extends PageHelper {
pages = pages;
+ versioningStateEnabled = 'Enabled';
+ versioningStateSuspended = 'Suspended';
/**
* TODO add check to verify the existance of the bucket!
);
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
);
// 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() {
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'));
class="bold">Zonegroup</td>
<td>{{ bucket.zonegroup }}</td>
</tr>
+ <tr>
+ <td i18n
+ class="bold">Versioning</td>
+ <td>{{ bucket.versioning }}</td>
+ </tr>
</tbody>
</table>
</div>
</div>
+ <!-- Versioning -->
+ <div class="form-group row"
+ *ngIf="editing">
+ <legend class="cd-header ml-5 mr-5" i18n>Versioning</legend>
+ <div class="col-sm-9 offset-sm-3">
+ <input type="radio"
+ id="enabled"
+ name="versioning"
+ formControlName="versioning"
+ value="Enabled"
+ [checked]="bucketForm.get('versioning').value == 'Enabled'"
+ class="custom-control custom-radio custom-control-inline align-top">
+ <label class="align-text-top"
+ for="enabled">
+ <span i18n>Enabled</span>
+ <div class="text-muted" i18n>Enables versioning for the objects in the bucket.</div>
+ </label>
+ </div>
+ <div class="col-sm-9 offset-sm-3">
+ <input type="radio"
+ id="suspended"
+ name="versioning"
+ formControlName="versioning"
+ value="Suspended"
+ [checked]="bucketForm.get('versioning').value != 'Enabled'"
+ class="custom-control custom-radio custom-control-inline align-top">
+ <label class="align-text-top"
+ for="suspended">
+ <span i18n>Suspended</span>
+ <div class="text-muted" i18n>Disables versioning for the objects in the bucket.</div>
+ </label>
+ <span class="invalid-feedback"
+ *ngIf="bucketForm.showError('versioning', frm, 'required')"
+ i18n>This field is required.</span>
+ </div>
+ </div>
+
</div>
<div class="card-footer">
<div class="button-group text-right">
component.submit();
expect(notificationService.show).toHaveBeenCalledWith(
NotificationType.success,
- 'Updated Object Gateway bucket ""'
+ 'Updated Object Gateway bucket "".'
);
});
});
id: [null],
bid: [null, [Validators.required], this.editing ? [] : [this.bucketNameValidator()]],
owner: [null, [Validators.required]],
- 'placement-target': [null, this.editing ? [] : [Validators.required]]
+ 'placement-target': [null, this.editing ? [] : [Validators.required]],
+ versioning: [null, this.editing ? [Validators.required] : []]
});
}
if (this.editing) {
// Edit
const idCtl = this.bucketForm.get('id');
- this.rgwBucketService.update(bidCtl.value, idCtl.value, ownerCtl.value).subscribe(
- () => {
- this.notificationService.show(
- NotificationType.success,
- this.i18n('Updated Object Gateway bucket "{{bid}}"', { bid: bidCtl.value })
- );
- this.goToListView();
- },
- () => {
- // Reset the 'Submit' button.
- this.bucketForm.setErrors({ cdSubmitButton: true });
- }
- );
+ const versioningCtl = this.bucketForm.get('versioning');
+ this.rgwBucketService
+ .update(bidCtl.value, idCtl.value, ownerCtl.value, versioningCtl.value)
+ .subscribe(
+ () => {
+ this.notificationService.show(
+ NotificationType.success,
+ this.i18n('Updated Object Gateway bucket "{{bid}}".', { bid: bidCtl.value })
+ );
+ this.goToListView();
+ },
+ () => {
+ // Reset the 'Submit' button.
+ this.bucketForm.setErrors({ cdSubmitButton: true });
+ }
+ );
} else {
// Add
this.rgwBucketService
});
it('should call update', () => {
- service.update('foo', 'bar', 'baz').subscribe();
- const req = httpTesting.expectOne('api/rgw/bucket/foo?bucket_id=bar&uid=baz');
+ service.update('foo', 'bar', 'baz', 'Enabled').subscribe();
+ const req = httpTesting.expectOne(
+ 'api/rgw/bucket/foo?bucket_id=bar&uid=baz&versioning_state=Enabled'
+ );
expect(req.request.method).toBe('PUT');
});
return this.http.post(this.url, null, { params: params });
}
- update(bucket: string, bucketId: string, uid: string) {
+ update(bucket: string, bucketId: string, uid: string, versioningState: string) {
let params = new HttpParams();
params = params.append('bucket_id', bucketId);
params = params.append('uid', uid);
+ params = params.append('versioning_state', versioningState);
return this.http.put(`${this.url}/${bucket}`, null, { params: params });
}
/**
* Check if the specified bucket exists.
- * @param {string} uid The bucket name to check.
+ * @param {string} bucket The bucket name to check.
* @return {Observable<boolean>}
*/
exists(bucket: string) {
)
return {'zonegroup': zonegroup_name, 'placement_targets': placement_targets}
+
+ @RestClient.api_get('/{bucket_name}?versioning')
+ def get_bucket_versioning(self, bucket_name, request=None):
+ """
+ Get bucket versioning.
+ :param str bucket_name: the name of the bucket.
+ :return: versioning state
+ :rtype: str
+ """
+ # pylint: disable=unused-argument
+ result = request()
+ if 'Status' not in result:
+ result['Status'] = 'Suspended'
+ return result['Status']
+
+ @RestClient.api_put('/{bucket_name}?versioning')
+ def set_bucket_versioning(self, bucket_name, versioning_state, request=None):
+ """
+ Set bucket versioning.
+ :param str bucket_name: the name of the bucket.
+ :param str versioning_state:
+ https://docs.aws.amazon.com/AmazonS3/latest/API/RESTBucketPUTVersioningStatus.html
+ :return: None
+ """
+ # pylint: disable=unused-argument
+ versioning_configuration = ET.Element('VersioningConfiguration')
+ status = ET.SubElement(versioning_configuration, 'Status')
+ status.text = versioning_state
+ data = ET.tostring(versioning_configuration, encoding='utf-8')
+
+ return request(data=data)