API Services for Flavors Panel
Adding the api services needed by the flavor panel for the sub flows To test set DISABLED = False in _2081_admin_flavors_panel.py Co-Authored-By: Rajat Vig <rajatv@thoughtworks.com> Co-Authored-By: Errol Pais <epais@thoughtworks.com> Co-Authored-By: Kristine Brown <kbrown@thoughtworks.com> Co-Authored-By: Kyle Olivo <keolivo@thoughtworks.com> Co-Authored-By: Yakira Dixon <ydixon@thoughtworks.com> Co-Authored-By: Dan Siwiec <dsiwiec@thoughtworks.com> Change-Id: Iad97739203a05cce3c971cc8458cd2eb67e117d3 Partially-Implements: blueprint ng-flavors
This commit is contained in:
parent
80bbc35944
commit
0903fd20ff
@ -1,4 +1,3 @@
|
||||
|
||||
# Copyright 2014, Rackspace, US, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@ -352,6 +351,33 @@ class Flavors(generic.View):
|
||||
result['items'].append(d)
|
||||
return result
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def post(self, request):
|
||||
flavor_access = request.DATA.get('flavor_access', [])
|
||||
flavor_id = request.DATA['id']
|
||||
is_public = not flavor_access
|
||||
|
||||
flavor = api.nova.flavor_create(request,
|
||||
name=request.DATA['name'],
|
||||
memory=request.DATA['ram'],
|
||||
vcpu=request.DATA['vcpus'],
|
||||
disk=request.DATA['disk'],
|
||||
ephemeral=request
|
||||
.DATA['OS-FLV-EXT-DATA:ephemeral'],
|
||||
swap=request.DATA['swap'],
|
||||
flavorid=flavor_id,
|
||||
is_public=is_public
|
||||
)
|
||||
|
||||
for project in flavor_access:
|
||||
api.nova.add_tenant_to_flavor(
|
||||
request, flavor.id, project.get('id'))
|
||||
|
||||
return rest_utils.CreatedResponse(
|
||||
'/api/nova/flavors/%s' % flavor.id,
|
||||
flavor.to_dict()
|
||||
)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Flavor(generic.View):
|
||||
@ -368,14 +394,64 @@ class Flavor(generic.View):
|
||||
Example GET:
|
||||
http://localhost/api/nova/flavors/1
|
||||
"""
|
||||
get_extras = request.GET.get('get_extras')
|
||||
get_extras = bool(get_extras and get_extras.lower() == 'true')
|
||||
get_extras = self.extract_boolean(request, 'get_extras')
|
||||
get_access_list = self.extract_boolean(request, 'get_access_list')
|
||||
flavor = api.nova.flavor_get(request, flavor_id, get_extras=get_extras)
|
||||
|
||||
result = flavor.to_dict()
|
||||
# Bug: nova API stores and returns empty string when swap equals 0
|
||||
# https://bugs.launchpad.net/nova/+bug/1408954
|
||||
if 'swap' in result and result['swap'] == '':
|
||||
result['swap'] = 0
|
||||
if get_extras:
|
||||
result['extras'] = flavor.extras
|
||||
|
||||
if get_access_list and not flavor.is_public:
|
||||
access_list = [item.tenant_id for item in
|
||||
api.nova.flavor_access_list(request, flavor_id)]
|
||||
result['access-list'] = access_list
|
||||
return result
|
||||
|
||||
@rest_utils.ajax()
|
||||
def delete(self, request, flavor_id):
|
||||
api.nova.flavor_delete(request, flavor_id)
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def patch(self, request, flavor_id):
|
||||
flavor_access = request.DATA.get('flavor_access', [])
|
||||
is_public = not flavor_access
|
||||
|
||||
# Grab any existing extra specs, because flavor edit is currently
|
||||
# implemented as a delete followed by a create.
|
||||
extras_dict = api.nova.flavor_get_extras(request, flavor_id, raw=True)
|
||||
# Mark the existing flavor as deleted.
|
||||
api.nova.flavor_delete(request, flavor_id)
|
||||
# Then create a new flavor with the same name but a new ID.
|
||||
# This is in the same try/except block as the delete call
|
||||
# because if the delete fails the API will error out because
|
||||
# active flavors can't have the same name.
|
||||
flavor = api.nova.flavor_create(request,
|
||||
name=request.DATA['name'],
|
||||
memory=request.DATA['ram'],
|
||||
vcpu=request.DATA['vcpus'],
|
||||
disk=request.DATA['disk'],
|
||||
ephemeral=request
|
||||
.DATA['OS-FLV-EXT-DATA:ephemeral'],
|
||||
swap=request.DATA['swap'],
|
||||
flavorid=flavor_id,
|
||||
is_public=is_public
|
||||
)
|
||||
for project in flavor_access:
|
||||
api.nova.add_tenant_to_flavor(
|
||||
request, flavor.id, project.get('id'))
|
||||
|
||||
if extras_dict:
|
||||
api.nova.flavor_extra_set(request, flavor.id, extras_dict)
|
||||
|
||||
def extract_boolean(self, request, name):
|
||||
bool_string = request.GET.get(name)
|
||||
return bool(bool_string and bool_string.lower() == 'true')
|
||||
|
||||
|
||||
@urls.register
|
||||
class FlavorExtraSpecs(generic.View):
|
||||
|
@ -53,7 +53,10 @@
|
||||
getInstanceMetadata: getInstanceMetadata,
|
||||
editInstanceMetadata: editInstanceMetadata,
|
||||
getCreateKeypairUrl: getCreateKeypairUrl,
|
||||
getRegenerateKeypairUrl: getRegenerateKeypairUrl
|
||||
getRegenerateKeypairUrl: getRegenerateKeypairUrl,
|
||||
createFlavor: createFlavor,
|
||||
updateFlavor: updateFlavor,
|
||||
deleteFlavor: deleteFlavor
|
||||
};
|
||||
|
||||
return service;
|
||||
@ -314,15 +317,66 @@
|
||||
* @param {boolean} getExtras (optional)
|
||||
* Also retrieve the extra specs for the flavor.
|
||||
*/
|
||||
function getFlavor(id, getExtras) {
|
||||
function getFlavor(id, getExtras, getAccessList) {
|
||||
var config = {'params': {}};
|
||||
if (getExtras) { config.params.get_extras = 'true'; }
|
||||
return apiService.get('/api/nova/flavors/' + id, config)
|
||||
if (getAccessList) { config.params.get_access_list = 'true'; }
|
||||
return apiService.get('/api/nova/flavors/' + id + '/' , config)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.nova.createFlavor
|
||||
* @description
|
||||
* Create a single flavor.
|
||||
* @param {flavor} flavor
|
||||
* Flavor to create
|
||||
*/
|
||||
function createFlavor(flavor) {
|
||||
return apiService.post('/api/nova/flavors/', flavor)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to create the flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.nova.updateFlavor
|
||||
* @description
|
||||
* Update a single flavor.
|
||||
* @param {flavor} flavor
|
||||
* Flavor to update
|
||||
*/
|
||||
function updateFlavor(flavor) {
|
||||
return apiService.patch('/api/nova/flavors/' + flavor.id + '/', flavor)
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to update the flavor.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.nova.deleteFlavor
|
||||
* @description
|
||||
* Delete a single flavor by ID.
|
||||
*
|
||||
* @param {String} flavorId
|
||||
* Flavor to delete
|
||||
*
|
||||
* @param {boolean} suppressError
|
||||
* If passed in, this will not show the default error handling
|
||||
* (horizon alert). The glance API may not have metadata definitions
|
||||
* enabled.
|
||||
*/
|
||||
function deleteFlavor(flavorId, suppressError) {
|
||||
var promise = apiService.delete('/api/nova/flavors/' + flavorId + '/');
|
||||
|
||||
return suppressError ? promise : promise.error(function() {
|
||||
toastService.add('error', gettext('Unable to delete the flavor with id: ') + flavorId);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.nova.getFlavorExtraSpecs
|
||||
* @description
|
||||
|
@ -185,7 +185,7 @@
|
||||
{
|
||||
"func": "getFlavor",
|
||||
"method": "get",
|
||||
"path": "/api/nova/flavors/42",
|
||||
"path": "/api/nova/flavors/42/",
|
||||
"data": {
|
||||
"params": {
|
||||
"get_extras": "true"
|
||||
@ -200,7 +200,24 @@
|
||||
{
|
||||
"func": "getFlavor",
|
||||
"method": "get",
|
||||
"path": "/api/nova/flavors/42",
|
||||
"path": "/api/nova/flavors/42/",
|
||||
"data": {
|
||||
"params": {
|
||||
"get_extras": "true",
|
||||
"get_access_list": "true"
|
||||
}
|
||||
},
|
||||
"error": "Unable to retrieve the flavor.",
|
||||
"testInput": [
|
||||
42,
|
||||
true,
|
||||
true
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getFlavor",
|
||||
"method": "get",
|
||||
"path": "/api/nova/flavors/42/",
|
||||
"data": {
|
||||
"params": {}
|
||||
},
|
||||
@ -275,6 +292,37 @@
|
||||
"testInput": [
|
||||
42, {a: '1', b: '2'}, ['c', 'd']
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "createFlavor",
|
||||
"method": "post",
|
||||
"path": "/api/nova/flavors/",
|
||||
"data": 42,
|
||||
"error": "Unable to create the flavor.",
|
||||
"testInput": [
|
||||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "updateFlavor",
|
||||
"method": "patch",
|
||||
"path": "/api/nova/flavors/42/",
|
||||
"data": {
|
||||
id: 42
|
||||
},
|
||||
"error": "Unable to update the flavor.",
|
||||
"testInput": [
|
||||
{
|
||||
id: 42
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "deleteFlavor",
|
||||
"method": "delete",
|
||||
"path": "/api/nova/flavors/42/",
|
||||
"error": "Unable to delete the flavor with id: 42",
|
||||
"testInput": [42]
|
||||
}
|
||||
];
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
import mock
|
||||
|
||||
from django.conf import settings
|
||||
from json import loads as to_json
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api.rest import nova
|
||||
@ -281,6 +282,27 @@ class NovaRestTestCase(test.TestCase):
|
||||
#
|
||||
# Flavors
|
||||
#
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_get_single_with_access_list(self, nc):
|
||||
request = self.mock_rest_request(GET={'get_access_list': 'tRuE'})
|
||||
nc.flavor_get.return_value.to_dict.return_value = {'name': '1'}
|
||||
nc.flavor_get.return_value.is_public = False
|
||||
|
||||
nc.flavor_access_list.return_value = [
|
||||
mock.Mock(**{'tenant_id': '11'}),
|
||||
mock.Mock(**{'tenant_id': '22'}),
|
||||
]
|
||||
|
||||
response = nova.Flavor().get(request, "1")
|
||||
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(to_json(response.content.decode('utf-8')),
|
||||
to_json('{"access-list": ["11", "22"], "name": "1"}'))
|
||||
|
||||
nc.flavor_get.assert_called_once_with(request, "1",
|
||||
get_extras=False)
|
||||
|
||||
def test_get_extras_no(self):
|
||||
self._test_flavor_get_single(get_extras=False)
|
||||
|
||||
@ -310,6 +332,197 @@ class NovaRestTestCase(test.TestCase):
|
||||
nc.flavor_get.assert_called_once_with(request, "1",
|
||||
get_extras=get_extras)
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_get_single_with_swap_set_to_empty(self, nc):
|
||||
request = self.mock_rest_request()
|
||||
nc.flavor_get.return_value\
|
||||
.to_dict.return_value = {'name': '1', 'swap': ''}
|
||||
|
||||
response = nova.Flavor().get(request, "1")
|
||||
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(to_json(response.content.decode('utf-8')),
|
||||
to_json('{"name": "1", "swap": 0}'))
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_delete(self, nc):
|
||||
request = self.mock_rest_request()
|
||||
nova.Flavor().delete(request, "1")
|
||||
nc.flavor_delete.assert_called_once_with(request, "1")
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_create(self, nc):
|
||||
flavor_req_data = '{"name": "flavor", ' \
|
||||
'"ram": 12, ' \
|
||||
'"vcpus": 1, ' \
|
||||
'"disk": 2, ' \
|
||||
'"OS-FLV-EXT-DATA:ephemeral": 3, ' \
|
||||
'"swap": 4, ' \
|
||||
'"id": "123"' \
|
||||
'}'
|
||||
|
||||
nc.flavor_create.return_value = mock.Mock(**{
|
||||
'id': '123',
|
||||
'to_dict.return_value': {'id': '123', 'name': 'flavor'}
|
||||
})
|
||||
|
||||
flavor_data = {'name': 'flavor',
|
||||
'memory': 12,
|
||||
'vcpu': 1,
|
||||
'disk': 2,
|
||||
'ephemeral': 3,
|
||||
'swap': 4,
|
||||
'flavorid': '123',
|
||||
'is_public': True}
|
||||
|
||||
request = self.mock_rest_request(body=flavor_req_data)
|
||||
response = nova.Flavors().post(request)
|
||||
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response['location'], '/api/nova/flavors/123')
|
||||
|
||||
nc.flavor_create.assert_called_once_with(request, **flavor_data)
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_create_with_access_list(self, nc):
|
||||
flavor_req_data = '{"name": "flavor", ' \
|
||||
'"ram": 12, ' \
|
||||
'"vcpus": 1, ' \
|
||||
'"disk": 2, ' \
|
||||
'"OS-FLV-EXT-DATA:ephemeral": 3, ' \
|
||||
'"swap": 4, ' \
|
||||
'"id": "123", ' \
|
||||
'"flavor_access": [{"id":"1", "name":"test"}]' \
|
||||
'}'
|
||||
|
||||
nc.flavor_create.return_value = mock.Mock(**{
|
||||
'id': '1234',
|
||||
'to_dict.return_value': {'id': '1234', 'name': 'flavor'}
|
||||
})
|
||||
|
||||
flavor_data = {'name': 'flavor',
|
||||
'memory': 12,
|
||||
'vcpu': 1,
|
||||
'disk': 2,
|
||||
'ephemeral': 3,
|
||||
'swap': 4,
|
||||
'flavorid': '123',
|
||||
'is_public': False}
|
||||
|
||||
request = self.mock_rest_request(body=flavor_req_data)
|
||||
response = nova.Flavors().post(request)
|
||||
|
||||
self.assertStatusCode(response, 201)
|
||||
self.assertEqual(response['location'], '/api/nova/flavors/1234')
|
||||
|
||||
nc.flavor_create.assert_called_once_with(request, **flavor_data)
|
||||
nc.add_tenant_to_flavor.assert_called_once_with(request, '1234', '1')
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_update(self, nc):
|
||||
flavor_req_data = '{"name": "flavor", ' \
|
||||
'"ram": 12, ' \
|
||||
'"vcpus": 1, ' \
|
||||
'"disk": 2, ' \
|
||||
'"OS-FLV-EXT-DATA:ephemeral": 3, ' \
|
||||
'"swap": 4' \
|
||||
'}'
|
||||
|
||||
nc.flavor_create.return_value = mock.Mock(**{
|
||||
'id': '123',
|
||||
'to_dict.return_value': {'id': '123', 'name': 'flavor'}
|
||||
})
|
||||
|
||||
flavor_data = {'name': 'flavor',
|
||||
'memory': 12,
|
||||
'vcpu': 1,
|
||||
'disk': 2,
|
||||
'ephemeral': 3,
|
||||
'swap': 4,
|
||||
'flavorid': '123',
|
||||
'is_public': True}
|
||||
|
||||
request = self.mock_rest_request(body=flavor_req_data)
|
||||
response = nova.Flavor().patch(request, '123')
|
||||
|
||||
self.assertStatusCode(response, 204)
|
||||
|
||||
nc.flavor_delete.assert_called_once_with(request, '123')
|
||||
nc.flavor_create.assert_called_once_with(request, **flavor_data)
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_update_with_extras(self, nc):
|
||||
flavor_req_data = '{"name": "flavor", ' \
|
||||
'"ram": 12, ' \
|
||||
'"vcpus": 1, ' \
|
||||
'"disk": 2, ' \
|
||||
'"OS-FLV-EXT-DATA:ephemeral": 3, ' \
|
||||
'"swap": 4' \
|
||||
'}'
|
||||
|
||||
extra_dict = mock.Mock()
|
||||
|
||||
nc.flavor_get_extras.return_value = extra_dict
|
||||
|
||||
nc.flavor_create.return_value = mock.Mock(**{
|
||||
'id': '1234',
|
||||
'to_dict.return_value': {'id': '1234', 'name': 'flavor'}
|
||||
})
|
||||
|
||||
flavor_data = {'name': 'flavor',
|
||||
'memory': 12,
|
||||
'vcpu': 1,
|
||||
'disk': 2,
|
||||
'ephemeral': 3,
|
||||
'swap': 4,
|
||||
'flavorid': '123',
|
||||
'is_public': True}
|
||||
|
||||
request = self.mock_rest_request(body=flavor_req_data)
|
||||
response = nova.Flavor().patch(request, '123')
|
||||
|
||||
self.assertStatusCode(response, 204)
|
||||
|
||||
nc.flavor_delete.assert_called_once_with(request, '123')
|
||||
nc.flavor_create.assert_called_once_with(request, **flavor_data)
|
||||
nc.flavor_get_extras.assert_called_once_with(request, '123', raw=True)
|
||||
nc.flavor_extra_set.assert_called_once_with(request, '1234',
|
||||
extra_dict)
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def test_flavor_update_with_access_list(self, nc):
|
||||
flavor_req_data = '{"name": "flavor", ' \
|
||||
'"ram": 12, ' \
|
||||
'"vcpus": 1, ' \
|
||||
'"disk": 2, ' \
|
||||
'"OS-FLV-EXT-DATA:ephemeral": 3, ' \
|
||||
'"swap": 4, ' \
|
||||
'"flavor_access": [{"id":"1", "name":"test"}]' \
|
||||
'}'
|
||||
|
||||
nc.flavor_create.return_value = mock.Mock(**{
|
||||
'id': '1234',
|
||||
'to_dict.return_value': {'id': '1234', 'name': 'flavor'}
|
||||
})
|
||||
|
||||
flavor_data = {'name': 'flavor',
|
||||
'memory': 12,
|
||||
'vcpu': 1,
|
||||
'disk': 2,
|
||||
'ephemeral': 3,
|
||||
'swap': 4,
|
||||
'flavorid': '123',
|
||||
'is_public': False}
|
||||
|
||||
request = self.mock_rest_request(body=flavor_req_data)
|
||||
response = nova.Flavor().patch(request, '123')
|
||||
|
||||
self.assertStatusCode(response, 204)
|
||||
|
||||
nc.flavor_delete.assert_called_once_with(request, '123')
|
||||
nc.flavor_create.assert_called_once_with(request, **flavor_data)
|
||||
nc.add_tenant_to_flavor.assert_called_once_with(request, '1234', '1')
|
||||
|
||||
@mock.patch.object(nova.api, 'nova')
|
||||
def _test_flavor_list_public(self, nc, is_public=None):
|
||||
if is_public:
|
||||
|
Loading…
Reference in New Issue
Block a user