Add API services for Create a Volume, get QoS and tenant limits

Adding cinder services to create and update volumes and volume snapshots,
return Quality of Service and tenant absolute limits.

This is to support dependent patches that will create volumes for Images
from the angular panel for Images.

Co-Authored-By: Errol Pais<epais@thoughtworks.com>
Co-Authored-By: Nathan Zeplowitz<nzeplowi@thoughtworks.com>

Partially-Implements: blueprint angularize-images-table
Change-Id: I4c48c09a0f20f971588b1e920a555f12e7ed4498
This commit is contained in:
Valerie Roske 2015-10-12 11:15:03 -07:00 committed by Rajat Vig
parent f97cf83475
commit 56e34bdf0c
5 changed files with 181 additions and 4 deletions

View File

@ -96,7 +96,8 @@ class VolumeSnapshot(BaseCinderAPIResourceWrapper):
class VolumeType(BaseCinderAPIResourceWrapper):
_attrs = ['id', 'name', 'extra_specs', 'created_at',
_attrs = ['id', 'name', 'extra_specs', 'created_at', 'encryption',
'associated_qos_spec', 'description',
'os-extended-snapshot-attributes:project_id']
@ -115,6 +116,11 @@ class VolumeBackup(BaseCinderAPIResourceWrapper):
self._volume = value
class QosSpecs(BaseCinderAPIResourceWrapper):
_attrs = ['id', 'name', 'consumer', 'specs']
class VolTypeExtraSpec(object):
def __init__(self, type_id, key, val):
self.type_id = type_id
@ -581,6 +587,10 @@ def qos_spec_get_associations(request, qos_spec_id):
return cinderclient(request).qos_specs.get_associations(qos_spec_id)
def qos_specs_list(request):
return [QosSpecs(s) for s in qos_spec_list(request)]
@memoized
def tenant_absolute_limits(request):
limits = cinderclient(request).limits.get().absolute

View File

@ -54,6 +54,26 @@ class Volumes(generic.View):
)
return {'items': [u.to_dict() for u in result]}
@rest_utils.ajax(data_required=True)
def post(self, request):
volume = api.cinder.volume_create(
request,
size=request.DATA['size'],
name=request.DATA['name'],
description=request.DATA['description'],
volume_type=request.DATA['volume_type'],
snapshot_id=request.DATA['snapshot_id'],
metadata=request.DATA['metadata'],
image_id=request.DATA['image_id'],
availability_zone=request.DATA['availability_zone'],
source_volid=request.DATA['source_volid']
)
return rest_utils.CreatedResponse(
'/api/cinder/volumes/%s' % volume.id,
volume.to_dict()
)
@urls.register
class Volume(generic.View):
@ -163,3 +183,22 @@ class Extensions(generic.View):
'updated': e.updated
} for e in result]}
@urls.register
class QoSSpecs(generic.View):
url_regex = r'cinder/qosspecs/$'
@rest_utils.ajax()
def get(self, request):
result = api.cinder.qos_specs_list(request)
return {'items': [u.to_dict() for u in result]}
@urls.register
class TenantAbsoluteLimits(generic.View):
url_regex = r'cinder/tenantabsolutelimits/$'
@rest_utils.ajax()
def get(self, request):
return api.cinder.tenant_absolute_limits(request)

View File

@ -38,7 +38,10 @@
getVolumeType: getVolumeType,
getDefaultVolumeType: getDefaultVolumeType,
getVolumeSnapshots: getVolumeSnapshots,
getExtensions: getExtensions
getExtensions: getExtensions,
getQoSSpecs: getQoSSpecs,
createVolume: createVolume,
getAbsoluteLimits: getAbsoluteLimits
};
return service;
@ -63,7 +66,7 @@
* For example, "status": "available" will show all available volumes.
*/
function getVolumes(params) {
var config = (params) ? {'params': params} : {};
var config = params ? {'params': params} : {};
return apiService.get('/api/cinder/volumes/', config)
.error(function () {
toastService.add('error', gettext('Unable to retrieve the volumes.'));
@ -86,6 +89,18 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.cinder.createVolume
* @description
* Create a volume.
*/
function createVolume(newVolume) {
return apiService.post('/api/cinder/volumes/', newVolume)
.error(function () {
toastService.add('error', gettext('Unable to create the volume.'));
});
}
// Volume Types
/**
@ -152,7 +167,7 @@
* snapshots.
*/
function getVolumeSnapshots(params) {
var config = (params) ? {'params': params} : {};
var config = params ? {'params': params} : {};
return apiService.get('/api/cinder/volumesnapshots/', config)
.error(function () {
toastService.add('error',
@ -192,5 +207,39 @@
});
}
/**
* @name horizon.app.core.openstack-service-api.cinder.getQoSSpecs
* @description
* Get a list of Quality of Service.
*
* The listing result is an object with property "items." Each item is
* a Quality of Service Spec.
*
* @param {Object} params
* Query parameters. Optional.
*
*/
function getQoSSpecs(params) {
var config = params ? {'params': params} : {};
return apiService.get('/api/cinder/qosspecs/', config)
.error(function () {
toastService.add('error',
gettext('Unable to retrieve the QoS Specs.'));
});
}
/**
* @name horizon.app.core.openstack-service-api.cinder.getAbsoluteLimits
* @description
* Get the limits for the current tenant.
*
*/
function getAbsoluteLimits() {
return apiService.get('/api/cinder/tenantabsolutelimits/')
.error(function () {
toastService.add('error',
gettext('Unable to retrieve the Absolute Limits.'));
});
}
}
}());

View File

@ -107,6 +107,33 @@
data: { params: 'config' },
error: 'Unable to retrieve the volume snapshots.',
testInput: [ 'config' ]
},
{
func: 'createVolume',
method: 'post',
path: '/api/cinder/volumes/',
data: { params: 'config' },
error: 'Unable to create the volume.',
testInput: [
{
params: 'config'
}
]
},
{
func: 'getQoSSpecs',
method: 'get',
path: '/api/cinder/qosspecs/',
data: {},
error: 'Unable to retrieve the QoS Specs.'
},
{
func: 'getQoSSpecs',
method: 'get',
path: '/api/cinder/qosspecs/',
data: { params: 'config' },
error: 'Unable to retrieve the QoS Specs.',
testInput: [ 'config' ]
}
];

View File

@ -65,6 +65,35 @@ class CinderRestTestCase(test.TestCase):
self.assertEqual(response.json, {"id": "one"})
cc.volume_get.assert_called_once_with(request, '1')
@mock.patch.object(cinder.api, 'cinder')
def test_volume_create(self, cc):
mock_body = '''{
"size": "",
"name": "",
"description": "",
"volume_type": "",
"snapshot_id": "",
"metadata": "",
"image_id": "",
"availability_zone": "",
"source_volid": ""
}'''
mock_volume_create_response = {
"size": ""
}
mock_post_response = '{"size": ""}'
request = self.mock_rest_request(POST={}, body=mock_body)
cc.volume_create.return_value = \
mock.Mock(**{'to_dict.return_value': mock_volume_create_response})
response = cinder.Volumes().post(request)
self.assertStatusCode(response, 201)
self.assertEqual(response.content.decode("utf-8"), mock_post_response)
#
# Volume Types
#
@ -154,3 +183,26 @@ class CinderRestTestCase(test.TestCase):
self.assertEqual(response.content,
'{"items": [{"name": "foo"}, {"name": "bar"}]}')
cc.list_extensions.assert_called_once_with(request)
@mock.patch.object(cinder.api, 'cinder')
def test_qos_specs_get(self, cc):
request = self.mock_rest_request(GET={})
cc.qos_specs_list.return_value = [
mock.Mock(**{'to_dict.return_value': {'id': 'one'}}),
mock.Mock(**{'to_dict.return_value': {'id': 'two'}}),
]
response = cinder.QoSSpecs().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content.decode("utf-8"),
'{"items": [{"id": "one"}, {"id": "two"}]}')
cc.qos_specs_list.assert_called_once_with(request)
@mock.patch.object(cinder.api, 'cinder')
def test_tenant_absolute_limits_get(self, cc):
request = self.mock_rest_request(GET={})
cc.tenant_absolute_limits.return_value = \
{'id': 'one'}
response = cinder.TenantAbsoluteLimits().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content.decode("utf-8"), '{"id": "one"}')
cc.tenant_absolute_limits.assert_called_once_with(request)