Enable volumes metadata update
Cinder currently expose an api to let the users update the volumes metadata so horizon should expose this functionality. The metadata is filtered to remove image metadata attributes. There is work in progress (in Cinder) that will expose the ability to write-through image metadata to underlying images where appropriate. Allowing setting of image properties in this UI would be confusing. Change-Id: If721ac1c908df7651d630f6e7d36f2cc4d69f5da Implements: blueprint ability-to-add-metadata-to-cinder-volumes-and-snapshots Co-Authored-By: Santiago Baldassin <santiago.b.baldassin@intel.com> Co-Authored-By: Pawel Skowron <pawel.skowron@intel.com> Co-Authored-By: Bartosz Fic <bartosz.fic@intel.com> Co-Authored-By: Pawel Koniszewski <pawel.koniszewski@intel.com> Co-Authored-By: Michal Dulko <michal.dulko@intel.com> Co-Authored-By: David Lyle <david.lyle@intel.com> Co-Authored-By: Paul Karikh <pkarikh@mirantis.com>
This commit is contained in:
parent
7063e2ae9e
commit
7f46e5dc23
@ -355,6 +355,14 @@ def volume_update(request, volume_id, name, description):
|
||||
**vol_data)
|
||||
|
||||
|
||||
def volume_set_metadata(request, volume_id, metadata):
|
||||
return cinderclient(request).volumes.set_metadata(volume_id, metadata)
|
||||
|
||||
|
||||
def volume_delete_metadata(request, volume_id, keys):
|
||||
return cinderclient(request).volumes.delete_metadata(volume_id, keys)
|
||||
|
||||
|
||||
def volume_reset_state(request, volume_id, state):
|
||||
return cinderclient(request).volumes.reset_state(volume_id, state)
|
||||
|
||||
@ -445,6 +453,16 @@ def volume_snapshot_update(request, snapshot_id, name, description):
|
||||
**snapshot_data)
|
||||
|
||||
|
||||
def volume_snapshot_set_metadata(request, snapshot_id, metadata):
|
||||
return cinderclient(request).volume_snapshots.set_metadata(
|
||||
snapshot_id, metadata)
|
||||
|
||||
|
||||
def volume_snapshot_delete_metadata(request, snapshot_id, keys):
|
||||
return cinderclient(request).volume_snapshots.delete_metadata(
|
||||
snapshot_id, keys)
|
||||
|
||||
|
||||
def volume_snapshot_reset_state(request, snapshot_id, state):
|
||||
return cinderclient(request).volume_snapshots.reset_state(
|
||||
snapshot_id, state)
|
||||
@ -792,7 +810,7 @@ def volume_type_extra_set(request, type_id, metadata):
|
||||
|
||||
def volume_type_extra_delete(request, type_id, keys):
|
||||
vol_type = volume_type_get(request, type_id)
|
||||
return vol_type.unset_keys([keys])
|
||||
return vol_type.unset_keys(keys)
|
||||
|
||||
|
||||
def qos_spec_list(request):
|
||||
|
@ -132,6 +132,34 @@ class VolumeTypes(generic.View):
|
||||
return {'items': [api.cinder.VolumeType(u).to_dict() for u in result]}
|
||||
|
||||
|
||||
@urls.register
|
||||
class VolumeMetadata(generic.View):
|
||||
"""API for volume metadata"""
|
||||
url_regex = r'cinder/volumes/(?P<volume_id>[^/]+)/metadata$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, volume_id):
|
||||
"""Get a specific volume's metadata
|
||||
|
||||
http://localhost/api/cinder/volumes/1/metadata
|
||||
"""
|
||||
return api.cinder.volume_get(request,
|
||||
volume_id).to_dict().get('metadata')
|
||||
|
||||
@rest_utils.ajax()
|
||||
def patch(self, request, volume_id):
|
||||
"""Update metadata items for specific volume
|
||||
|
||||
http://localhost/api/cinder/volumes/1/metadata
|
||||
"""
|
||||
updated = request.DATA['updated']
|
||||
removed = request.DATA['removed']
|
||||
if updated:
|
||||
api.cinder.volume_set_metadata(request, volume_id, updated)
|
||||
if removed:
|
||||
api.cinder.volume_delete_metadata(request, volume_id, removed)
|
||||
|
||||
|
||||
@urls.register
|
||||
class VolumeType(generic.View):
|
||||
"""API for getting a volume type.
|
||||
@ -179,6 +207,74 @@ class VolumeSnapshots(generic.View):
|
||||
return {'items': [u.to_dict() for u in result]}
|
||||
|
||||
|
||||
@urls.register
|
||||
class VolumeSnapshotMetadata(generic.View):
|
||||
"""API for getting snapshots metadata"""
|
||||
url_regex = r'cinder/volumesnapshots/' \
|
||||
r'(?P<volume_snapshot_id>[^/]+)/metadata$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, volume_snapshot_id):
|
||||
"""Get a specific volumes snapshot metadata
|
||||
|
||||
http://localhost/api/cinder/volumesnapshots/1/metadata
|
||||
"""
|
||||
result = api.cinder.volume_snapshot_get(request,
|
||||
volume_snapshot_id).\
|
||||
to_dict().get('metadata')
|
||||
return result
|
||||
|
||||
@rest_utils.ajax()
|
||||
def patch(self, request, volume_snapshot_id):
|
||||
"""Update metadata for specific volume snapshot
|
||||
|
||||
http://localhost/api/cinder/volumesnapshots/1/metadata
|
||||
"""
|
||||
updated = request.DATA['updated']
|
||||
removed = request.DATA['removed']
|
||||
if updated:
|
||||
api.cinder.volume_snapshot_set_metadata(request,
|
||||
volume_snapshot_id,
|
||||
updated)
|
||||
if removed:
|
||||
api.cinder.volume_snapshot_delete_metadata(request,
|
||||
volume_snapshot_id,
|
||||
removed)
|
||||
|
||||
|
||||
@urls.register
|
||||
class VolumeTypeMetadata(generic.View):
|
||||
"""API for getting snapshots metadata"""
|
||||
url_regex = r'cinder/volumetypes/(?P<volume_type_id>[^/]+)/metadata$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, volume_type_id):
|
||||
"""Get a specific volume's metadata
|
||||
|
||||
http://localhost/api/cinder/volumetypes/1/metadata
|
||||
"""
|
||||
metadata = api.cinder.volume_type_extra_get(request, volume_type_id)
|
||||
result = {x.key: x.value for x in metadata}
|
||||
return result
|
||||
|
||||
@rest_utils.ajax()
|
||||
def patch(self, request, volume_type_id):
|
||||
"""Update metadata for specific volume
|
||||
|
||||
http://localhost/api/cinder/volumetypes/1/metadata
|
||||
"""
|
||||
updated = request.DATA['updated']
|
||||
removed = request.DATA['removed']
|
||||
if updated:
|
||||
api.cinder.volume_type_extra_set(request,
|
||||
volume_type_id,
|
||||
updated)
|
||||
if removed:
|
||||
api.cinder.volume_type_extra_delete(request,
|
||||
volume_type_id,
|
||||
removed)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Extensions(generic.View):
|
||||
"""API for cinder extensions.
|
||||
|
@ -72,7 +72,8 @@ class VolumeSnapshotsTable(volumes_tables.VolumesTableBase):
|
||||
table_actions = (snapshots_tables.VolumeSnapshotsFilterAction,
|
||||
snapshots_tables.DeleteVolumeSnapshot,)
|
||||
row_actions = (snapshots_tables.DeleteVolumeSnapshot,
|
||||
UpdateVolumeSnapshotStatus,)
|
||||
UpdateVolumeSnapshotStatus,
|
||||
snapshots_tables.UpdateMetadata)
|
||||
row_class = UpdateRow
|
||||
status_columns = ("status",)
|
||||
columns = ('tenant', 'host', 'name', 'description', 'size', 'status',
|
||||
|
@ -19,7 +19,7 @@ from openstack_dashboard.test import helpers as test
|
||||
|
||||
from openstack_dashboard.dashboards.admin.volumes.snapshots import forms
|
||||
|
||||
INDEX_URL = reverse('horizon:admin:volumes:index')
|
||||
INDEX_URL = 'horizon:admin:volumes:index'
|
||||
|
||||
|
||||
class VolumeSnapshotsViewTests(test.BaseAdminViewTests):
|
||||
@ -79,7 +79,7 @@ class VolumeSnapshotsViewTests(test.BaseAdminViewTests):
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(error=1)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
self.assertRedirectsNoFollow(res, reverse(INDEX_URL))
|
||||
|
||||
@test.create_stubs({cinder: ('volume_snapshot_get',
|
||||
'volume_get')})
|
||||
@ -101,7 +101,7 @@ class VolumeSnapshotsViewTests(test.BaseAdminViewTests):
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertMessageCount(error=1)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
self.assertRedirectsNoFollow(res, reverse(INDEX_URL))
|
||||
|
||||
def test_get_snapshot_status_choices_without_current(self):
|
||||
current_status = {'status': 'available'}
|
||||
|
@ -195,6 +195,23 @@ class UpdateRow(tables.Row):
|
||||
return volume_type
|
||||
|
||||
|
||||
class UpdateMetadata(tables.LinkAction):
|
||||
name = "update_metadata"
|
||||
verbose_name = _("Update Metadata")
|
||||
ajax = False
|
||||
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['preempt'] = True
|
||||
super(UpdateMetadata, self).__init__(**kwargs)
|
||||
|
||||
def get_link_url(self, datum):
|
||||
obj_id = self.table.get_object_id(datum)
|
||||
self.attrs['ng-click'] = (
|
||||
"modal.openMetadataModal('volume_type', '%s', true)" % obj_id)
|
||||
return "javascript:void(0);"
|
||||
|
||||
|
||||
class VolumeTypesTable(tables.DataTable):
|
||||
name = tables.WrappingColumn("name", verbose_name=_("Name"),
|
||||
form_field=forms.CharField(max_length=64))
|
||||
@ -233,7 +250,8 @@ class VolumeTypesTable(tables.DataTable):
|
||||
EditVolumeType,
|
||||
UpdateVolumeTypeEncryption,
|
||||
DeleteVolumeTypeEncryption,
|
||||
DeleteVolumeType,)
|
||||
DeleteVolumeType,
|
||||
UpdateMetadata)
|
||||
row_class = UpdateRow
|
||||
|
||||
|
||||
|
@ -111,6 +111,7 @@ class VolumesTable(volumes_tables.VolumesTable):
|
||||
row_actions = (volumes_tables.DeleteVolume,
|
||||
UpdateVolumeStatusAction,
|
||||
UnmanageVolumeAction,
|
||||
MigrateVolume)
|
||||
MigrateVolume,
|
||||
volumes_tables.UpdateMetadata)
|
||||
columns = ('tenant', 'host', 'name', 'size', 'status', 'volume_type',
|
||||
'attachments', 'bootable', 'encryption',)
|
||||
|
@ -132,6 +132,24 @@ class CreateVolumeFromSnapshot(tables.LinkAction):
|
||||
return False
|
||||
|
||||
|
||||
class UpdateMetadata(tables.LinkAction):
|
||||
name = "update_metadata"
|
||||
verbose_name = _("Update Metadata")
|
||||
|
||||
ajax = False
|
||||
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['preempt'] = True
|
||||
super(UpdateMetadata, self).__init__(**kwargs)
|
||||
|
||||
def get_link_url(self, datum):
|
||||
obj_id = self.table.get_object_id(datum)
|
||||
self.attrs['ng-click'] = (
|
||||
"modal.openMetadataModal('volume_snapshot', '%s', true)" % obj_id)
|
||||
return "javascript:void(0);"
|
||||
|
||||
|
||||
class UpdateRow(tables.Row):
|
||||
ajax = True
|
||||
|
||||
@ -191,7 +209,8 @@ class VolumeSnapshotsTable(volume_tables.VolumesTableBase):
|
||||
launch_actions = (LaunchSnapshotNG,) + launch_actions
|
||||
|
||||
row_actions = ((CreateVolumeFromSnapshot,) + launch_actions +
|
||||
(EditVolumeSnapshot, DeleteVolumeSnapshot))
|
||||
(EditVolumeSnapshot, DeleteVolumeSnapshot,
|
||||
UpdateMetadata))
|
||||
row_class = UpdateRow
|
||||
status_columns = ("status",)
|
||||
permissions = [(
|
||||
|
@ -469,6 +469,23 @@ class VolumesFilterAction(tables.FilterAction):
|
||||
if q in volume.name.lower()]
|
||||
|
||||
|
||||
class UpdateMetadata(tables.LinkAction):
|
||||
name = "update_metadata"
|
||||
verbose_name = _("Update Metadata")
|
||||
ajax = False
|
||||
attrs = {"ng-controller": "MetadataModalHelperController as modal"}
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
kwargs['preempt'] = True
|
||||
super(UpdateMetadata, self).__init__(**kwargs)
|
||||
|
||||
def get_link_url(self, datum):
|
||||
obj_id = self.table.get_object_id(datum)
|
||||
self.attrs['ng-click'] = (
|
||||
"modal.openMetadataModal('volume', '%s', true)" % obj_id)
|
||||
return "javascript:void(0);"
|
||||
|
||||
|
||||
class VolumesTable(VolumesTableBase):
|
||||
name = tables.WrappingColumn("name",
|
||||
verbose_name=_("Name"),
|
||||
@ -504,7 +521,7 @@ class VolumesTable(VolumesTableBase):
|
||||
launch_actions +
|
||||
(EditAttachments, CreateSnapshot, CreateBackup,
|
||||
RetypeVolume, UploadToImage, CreateTransfer,
|
||||
DeleteTransfer, DeleteVolume))
|
||||
DeleteTransfer, DeleteVolume, UpdateMetadata))
|
||||
|
||||
|
||||
class DetachVolume(tables.BatchAction):
|
||||
|
@ -22,7 +22,8 @@
|
||||
|
||||
metadataService.$inject = [
|
||||
'horizon.app.core.openstack-service-api.nova',
|
||||
'horizon.app.core.openstack-service-api.glance'
|
||||
'horizon.app.core.openstack-service-api.glance',
|
||||
'horizon.app.core.openstack-service-api.cinder'
|
||||
];
|
||||
|
||||
/**
|
||||
@ -32,7 +33,7 @@
|
||||
*
|
||||
* Unified acquisition and modification of metadata.
|
||||
*/
|
||||
function metadataService(nova, glance) {
|
||||
function metadataService(nova, glance, cinder) {
|
||||
var service = {
|
||||
getMetadata: getMetadata,
|
||||
editMetadata: editMetadata,
|
||||
@ -52,7 +53,10 @@
|
||||
aggregate: nova.getAggregateExtraSpecs,
|
||||
flavor: nova.getFlavorExtraSpecs,
|
||||
image: glance.getImageProps,
|
||||
instance: nova.getInstanceMetadata
|
||||
instance: nova.getInstanceMetadata,
|
||||
volume: cinder.getVolumeMetadata,
|
||||
volume_snapshot: cinder.getVolumeSnapshotMetadata,
|
||||
volume_type: cinder.getVolumeTypeMetadata
|
||||
}[resource](id);
|
||||
}
|
||||
|
||||
@ -69,7 +73,10 @@
|
||||
aggregate: nova.editAggregateExtraSpecs,
|
||||
flavor: nova.editFlavorExtraSpecs,
|
||||
image: glance.editImageProps,
|
||||
instance: nova.editInstanceMetadata
|
||||
instance: nova.editInstanceMetadata,
|
||||
volume: cinder.editVolumeMetadata,
|
||||
volume_snapshot: cinder.editVolumeSnapshotMetadata,
|
||||
volume_type: cinder.editVolumeTypeMetadata
|
||||
}[resource](id, updated, removed);
|
||||
}
|
||||
|
||||
@ -86,7 +93,10 @@
|
||||
aggregate: 'OS::Nova::Aggregate',
|
||||
flavor: 'OS::Nova::Flavor',
|
||||
image: 'OS::Glance::Image',
|
||||
instance: 'OS::Nova::Server'
|
||||
instance: 'OS::Nova::Server',
|
||||
volume: 'OS::Cinder::Volume',
|
||||
volume_snapshot: 'OS::Cinder::Snapshot',
|
||||
volume_type: 'OS:Cinder::VolumeType'
|
||||
}[resource]
|
||||
};
|
||||
if (propertiesTarget) {
|
||||
|
@ -31,10 +31,17 @@
|
||||
editImageProps: function() {},
|
||||
getNamespaces: function() {}};
|
||||
|
||||
var cinder = {getVolumeMetadata:function() {},
|
||||
getVolumeSnapshotMetadata:function() {},
|
||||
getVolumeTypeMetadata:function() {},
|
||||
editVolumeMetadata: function() {},
|
||||
editVolumeSnapshotMetadata: function() {}};
|
||||
|
||||
beforeEach(function() {
|
||||
module(function($provide) {
|
||||
$provide.value('horizon.app.core.openstack-service-api.nova', nova);
|
||||
$provide.value('horizon.app.core.openstack-service-api.glance', glance);
|
||||
$provide.value('horizon.app.core.openstack-service-api.cinder', cinder);
|
||||
});
|
||||
});
|
||||
|
||||
@ -97,6 +104,18 @@
|
||||
expect(glance.editImageProps).toHaveBeenCalledWith('1', 'updated', ['removed']);
|
||||
});
|
||||
|
||||
it('should edit volume metadata', function() {
|
||||
spyOn(cinder, 'editVolumeMetadata');
|
||||
metadataService.editMetadata('volume', '1', 'updated', ['removed']);
|
||||
expect(cinder.editVolumeMetadata).toHaveBeenCalledWith('1', 'updated', ['removed']);
|
||||
});
|
||||
|
||||
it('should edit volume snapshot metadata', function() {
|
||||
spyOn(cinder, 'editVolumeSnapshotMetadata');
|
||||
metadataService.editMetadata('volume_snapshot', '1', 'updated', ['removed']);
|
||||
expect(cinder.editVolumeSnapshotMetadata).toHaveBeenCalledWith('1', 'updated', ['removed']);
|
||||
});
|
||||
|
||||
it('should get image namespace', function() {
|
||||
spyOn(glance, 'getNamespaces');
|
||||
metadataService.getNamespaces('image');
|
||||
@ -111,6 +130,13 @@
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('should get volume metadata', function() {
|
||||
var expected = 'volume metadata';
|
||||
spyOn(cinder, 'getVolumeMetadata').and.returnValue(expected);
|
||||
var actual = metadataService.getMetadata('volume', '1');
|
||||
expect(actual).toBe(expected);
|
||||
});
|
||||
|
||||
it('should edit instance metadata', function() {
|
||||
spyOn(nova, 'editInstanceMetadata');
|
||||
metadataService.editMetadata('instance', '1', 'updated', ['removed']);
|
||||
|
@ -7,6 +7,9 @@
|
||||
<span translate ng-if="modal.resourceType==='flavor'">Update Flavor Metadata</span>
|
||||
<span translate ng-if="modal.resourceType==='image'">Update Image Metadata</span>
|
||||
<span translate ng-if="modal.resourceType==='instance'">Update Instance Metadata</span>
|
||||
<span translate ng-if="modal.resourceType==='volume'">Update Volume Metadata</span>
|
||||
<span translate ng-if="modal.resourceType==='volume_snapshot'">Update Volume Snapshot Metadata</span>
|
||||
<span translate ng-if="modal.resourceType==='volume_type'">Update Volume Type Metadata</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
|
@ -38,6 +38,9 @@
|
||||
getVolumes: getVolumes,
|
||||
getVolume: getVolume,
|
||||
getVolumeTypes: getVolumeTypes,
|
||||
getVolumeMetadata: getVolumeMetadata,
|
||||
getVolumeSnapshotMetadata: getVolumeSnapshotMetadata,
|
||||
getVolumeTypeMetadata: getVolumeTypeMetadata,
|
||||
getVolumeType: getVolumeType,
|
||||
getDefaultVolumeType: getDefaultVolumeType,
|
||||
getVolumeSnapshots: getVolumeSnapshots,
|
||||
@ -48,7 +51,10 @@
|
||||
getServices: getServices,
|
||||
getDefaultQuotaSets: getDefaultQuotaSets,
|
||||
setDefaultQuotaSets: setDefaultQuotaSets,
|
||||
updateProjectQuota: updateProjectQuota
|
||||
updateProjectQuota: updateProjectQuota,
|
||||
editVolumeMetadata: editVolumeMetadata,
|
||||
editVolumeSnapshotMetadata: editVolumeSnapshotMetadata,
|
||||
editVolumeTypeMetadata:editVolumeTypeMetadata
|
||||
};
|
||||
|
||||
return service;
|
||||
@ -146,6 +152,63 @@
|
||||
});
|
||||
}
|
||||
|
||||
function getVolumeMetadata(id) {
|
||||
return apiService.get('/api/cinder/volumes/' + id + '/metadata')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the volume metadata.'));
|
||||
});
|
||||
}
|
||||
|
||||
function getVolumeSnapshotMetadata(id) {
|
||||
return apiService.get('/api/cinder/volumesnapshots/' + id + '/metadata')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the snapshot metadata.'));
|
||||
});
|
||||
}
|
||||
|
||||
function getVolumeTypeMetadata(id) {
|
||||
return apiService.get('/api/cinder/volumetypes/' + id + '/metadata')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the volume type metadata.'));
|
||||
});
|
||||
}
|
||||
|
||||
function editVolumeMetadata(id, updated, removed) {
|
||||
return apiService.patch(
|
||||
'/api/cinder/volumes/' + id + '/metadata',
|
||||
{
|
||||
updated: updated,
|
||||
removed: removed
|
||||
}
|
||||
).error(function () {
|
||||
toastService.add('error', gettext('Unable to edit volume metadata.'));
|
||||
});
|
||||
}
|
||||
|
||||
function editVolumeSnapshotMetadata(id, updated, removed) {
|
||||
return apiService.patch(
|
||||
'/api/cinder/volumesnapshots/' + id + '/metadata',
|
||||
{
|
||||
updated: updated,
|
||||
removed: removed
|
||||
}
|
||||
).error(function () {
|
||||
toastService.add('error', gettext('Unable to edit snapshot metadata.'));
|
||||
});
|
||||
}
|
||||
|
||||
function editVolumeTypeMetadata(id, updated, removed) {
|
||||
return apiService.patch(
|
||||
'/api/cinder/volumetypes/' + id + '/metadata',
|
||||
{
|
||||
updated: updated,
|
||||
removed: removed
|
||||
}
|
||||
).error(function () {
|
||||
toastService.add('error', gettext('Unable to edit volume type metadata.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name getVolumeType
|
||||
* @description
|
||||
|
@ -62,6 +62,13 @@
|
||||
error: 'Unable to retrieve the volume.',
|
||||
testInput: [1]
|
||||
},
|
||||
{
|
||||
func: 'getVolumeMetadata',
|
||||
method: 'get',
|
||||
path: '/api/cinder/volumes/1/metadata',
|
||||
error: 'Unable to retrieve the volume metadata.',
|
||||
testInput: [1]
|
||||
},
|
||||
{
|
||||
func: 'getVolumeTypes',
|
||||
method: 'get',
|
||||
@ -76,6 +83,13 @@
|
||||
error: 'Unable to retrieve the volume type.',
|
||||
testInput: [1]
|
||||
},
|
||||
{
|
||||
func: 'getVolumeTypeMetadata',
|
||||
method: 'get',
|
||||
path: '/api/cinder/volumetypes/1/metadata',
|
||||
error: 'Unable to retrieve the volume type metadata.',
|
||||
testInput: [1]
|
||||
},
|
||||
{
|
||||
func: 'getDefaultVolumeType',
|
||||
method: 'get',
|
||||
@ -112,6 +126,13 @@
|
||||
error: 'Unable to retrieve the volume snapshots.',
|
||||
testInput: [ 'config' ]
|
||||
},
|
||||
{
|
||||
func: 'getVolumeSnapshotMetadata',
|
||||
method: 'get',
|
||||
path: '/api/cinder/volumesnapshots/1/metadata',
|
||||
error: 'Unable to retrieve the snapshot metadata.',
|
||||
testInput: [1]
|
||||
},
|
||||
{
|
||||
func: 'createVolume',
|
||||
method: 'post',
|
||||
@ -165,6 +186,42 @@
|
||||
data: {'volumes': 42},
|
||||
error: 'Unable to update project quota data.',
|
||||
testInput: [{'volumes': 42}, 42]
|
||||
},
|
||||
{ func: 'editVolumeMetadata',
|
||||
method: 'patch',
|
||||
path: '/api/cinder/volumes/42/metadata',
|
||||
data: {
|
||||
"updated": {a: '1', b: '2'},
|
||||
"removed": ['c', 'd']
|
||||
},
|
||||
error: "Unable to edit volume metadata.",
|
||||
testInput: [
|
||||
42, {a: '1', b: '2'}, ['c', 'd']
|
||||
]
|
||||
},
|
||||
{ func: 'editVolumeSnapshotMetadata',
|
||||
method: 'patch',
|
||||
path: '/api/cinder/volumesnapshots/42/metadata',
|
||||
data: {
|
||||
"updated": {a: '1', b: '2'},
|
||||
"removed": ['c', 'd']
|
||||
},
|
||||
error: "Unable to edit snapshot metadata.",
|
||||
testInput: [
|
||||
42, {a: '1', b: '2'}, ['c', 'd']
|
||||
]
|
||||
},
|
||||
{ func: 'editVolumeTypeMetadata',
|
||||
method: 'patch',
|
||||
path: '/api/cinder/volumetypes/42/metadata',
|
||||
data: {
|
||||
"updated": {a: '1', b: '2'},
|
||||
"removed": ['c', 'd']
|
||||
},
|
||||
error: "Unable to edit volume type metadata.",
|
||||
testInput: [
|
||||
42, {a: '1', b: '2'}, ['c', 'd']
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -18,6 +18,7 @@ from django.conf import settings
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api.base import Quota
|
||||
from openstack_dashboard.api.cinder import VolTypeExtraSpec
|
||||
from openstack_dashboard.api.rest import cinder
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
@ -99,6 +100,33 @@ class CinderRestTestCase(test.TestCase):
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response.content.decode("utf-8"), mock_post_response)
|
||||
|
||||
@mock.patch.object(cinder.api, 'cinder')
|
||||
def test_volume_get_metadata(self, cc):
|
||||
request = self.mock_rest_request(**{'GET': {}})
|
||||
cc.volume_get.return_value = mock.Mock(
|
||||
**{'to_dict.return_value': {'id': 'one',
|
||||
'metadata': {'foo': 'bar'}}})
|
||||
response = cinder.VolumeMetadata().get(request, '1')
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.json, {'foo': 'bar'})
|
||||
cc.volume_get.assert_called_once_with(request, '1')
|
||||
|
||||
@mock.patch.object(cinder.api, 'cinder')
|
||||
def test_volume_update_metadata(self, cc):
|
||||
request = self.mock_rest_request(
|
||||
body='{"updated": {"a": "1", "b": "2"}, '
|
||||
'"removed": ["c", "d"]}'
|
||||
)
|
||||
response = cinder.VolumeMetadata().patch(request, '1')
|
||||
self.assertStatusCode(response, 204)
|
||||
self.assertEqual(b'', response.content)
|
||||
cc.volume_set_metadata.assert_called_once_with(
|
||||
request, '1', {'a': '1', 'b': '2'}
|
||||
)
|
||||
cc.volume_delete_metadata.assert_called_once_with(
|
||||
request, '1', ['c', 'd']
|
||||
)
|
||||
|
||||
#
|
||||
# Volume Types
|
||||
#
|
||||
@ -138,6 +166,35 @@ class CinderRestTestCase(test.TestCase):
|
||||
cc.volume_type_default.assert_called_once_with(request)
|
||||
cc.VolumeType.assert_called_once_with({'name': 'one'})
|
||||
|
||||
@mock.patch.object(cinder.api, 'cinder')
|
||||
def test_volume_type_get_metadata(self, cc):
|
||||
request = self.mock_rest_request(**{'GET': {}})
|
||||
cc.volume_type_extra_get = mock.Mock()
|
||||
|
||||
cc.volume_type_extra_get.return_value = \
|
||||
[VolTypeExtraSpec(1, 'foo', 'bar')]
|
||||
# cc.volume_type_extra_get.side_effect = [{'foo': 'bar'}]
|
||||
response = cinder.VolumeTypeMetadata().get(request, '1')
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.json, {'foo': 'bar'})
|
||||
cc.volume_type_extra_get.assert_called_once_with(request, '1')
|
||||
|
||||
@mock.patch.object(cinder.api, 'cinder')
|
||||
def test_volume_type_update_metadata(self, cc):
|
||||
request = self.mock_rest_request(
|
||||
body='{"updated": {"a": "1", "b": "2"}, '
|
||||
'"removed": ["c", "d"]}'
|
||||
)
|
||||
response = cinder.VolumeTypeMetadata().patch(request, '1')
|
||||
self.assertStatusCode(response, 204)
|
||||
self.assertEqual(b'', response.content)
|
||||
cc.volume_type_extra_set.assert_called_once_with(
|
||||
request, '1', {'a': '1', 'b': '2'}
|
||||
)
|
||||
cc.volume_type_extra_delete.assert_called_once_with(
|
||||
request, '1', ['c', 'd']
|
||||
)
|
||||
|
||||
#
|
||||
# Volume Snapshots
|
||||
#
|
||||
@ -170,6 +227,33 @@ class CinderRestTestCase(test.TestCase):
|
||||
cc.volume_snapshot_list.assert_called_once_with(request,
|
||||
search_opts=filters)
|
||||
|
||||
@mock.patch.object(cinder.api, 'cinder')
|
||||
def test_volume_snapshot_get_metadata(self, cc):
|
||||
request = self.mock_rest_request(**{'GET': {}})
|
||||
cc.volume_snapshot_get.return_value = mock.Mock(
|
||||
**{'to_dict.return_value': {'id': 'one',
|
||||
'metadata': {'foo': 'bar'}}})
|
||||
response = cinder.VolumeSnapshotMetadata().get(request, '1')
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.json, {'foo': 'bar'})
|
||||
cc.volume_snapshot_get.assert_called_once_with(request, '1')
|
||||
|
||||
@mock.patch.object(cinder.api, 'cinder')
|
||||
def test_volume_snapshot_update_metadata(self, cc):
|
||||
request = self.mock_rest_request(
|
||||
body='{"updated": {"a": "1", "b": "2"}, '
|
||||
'"removed": ["c", "d"]}'
|
||||
)
|
||||
response = cinder.VolumeSnapshotMetadata().patch(request, '1')
|
||||
self.assertStatusCode(response, 204)
|
||||
self.assertEqual(b'', response.content)
|
||||
cc.volume_snapshot_set_metadata.assert_called_once_with(
|
||||
request, '1', {'a': '1', 'b': '2'}
|
||||
)
|
||||
cc.volume_snapshot_delete_metadata.assert_called_once_with(
|
||||
request, '1', ['c', 'd']
|
||||
)
|
||||
|
||||
#
|
||||
# Extensions
|
||||
#
|
||||
|
@ -274,8 +274,8 @@ class GlanceApiTests(test.APITestCase):
|
||||
def test_metadefs_namespace_list_with_properties_target(self):
|
||||
metadata_defs = self.metadata_defs.list()
|
||||
limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
|
||||
filters = {'resource_types': ['mock name'],
|
||||
'properties_target': 'mock properties target'}
|
||||
filters = {'resource_types': ['OS::Cinder::Volume'],
|
||||
'properties_target': 'user'}
|
||||
|
||||
glanceclient = self.stub_glanceclient()
|
||||
glanceclient.metadefs_namespace = self.mox.CreateMockAnything()
|
||||
@ -283,7 +283,7 @@ class GlanceApiTests(test.APITestCase):
|
||||
limit=limit,
|
||||
filters=filters,
|
||||
sort_dir='asc',
|
||||
sort_key='namespace',) \
|
||||
sort_key='namespace', ) \
|
||||
.AndReturn(metadata_defs)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
@ -302,8 +302,9 @@ def data(TEST):
|
||||
{
|
||||
'created_at': '2014-08-21T08:39:43Z',
|
||||
'prefix': 'mock',
|
||||
'name': 'mock name',
|
||||
'properties_target': 'mock properties target'
|
||||
'name': 'OS::Cinder::Volume',
|
||||
'properties_target': 'user'
|
||||
|
||||
}
|
||||
],
|
||||
'visibility': 'public',
|
||||
|
Loading…
Reference in New Issue
Block a user