Nova Flavor Server Extensions Rest APIs - Angular

This provide flavor APIs for launch instance work.
This provides extension APIs for launch instance work.
This provides single server get for instance details work.
This provide single flavor get for instance details work.

Partially Implements: blueprint launch-instance-redesign
Partially Implements: blueprint instance-details-redesign

Change-Id: Ia1fd36ec31de21c60801f4d47716ef69aad7525f
Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com>
This commit is contained in:
Travis Tripp 2015-02-21 11:23:29 -07:00 committed by Richard Jones
parent 243d2a5a44
commit b915760a56
5 changed files with 362 additions and 6 deletions

View File

@ -144,6 +144,108 @@ limitations under the License.
horizon.alert('error', gettext('Unable to create the server.'));
});
};
/**
* @name hz.api.novaAPI.getServer
* @description
* Get a single server by ID
* @param {string} id
* Specifies the id of the server to request.
*/
this.getServer = function(id) {
return apiService.get('/api/nova/servers/' + id)
.error(function () {
horizon.alert('error', gettext('Unable to retrieve server.'));
});
};
/**
* @name hz.api.novaAPI.getExtensions
* @description
* Returns a list of enabled extensions.
*
* The listing result is an object with property "items". Each item is
* an extension.
* @example
* The following is an example response:
*
* {
* "items": [
* {
* "alias": "NMN",
* "description": "Multiple network support.",
* "links": [],
* "name": "Multinic",
* "namespace": "http://docs.openstack.org/compute/ext/multinic/api/v1.1",
* "updated": "2011-06-09T00:00:00Z"
* }
* ]
* }
*/
this.getExtensions = function() {
return apiService.get('/api/nova/extensions/')
.error(function () {
horizon.alert('error', gettext('Unable to retrieve extensions.'));
});
};
/**
* @name hz.api.novaAPI.getFlavors
* @description
* Returns a list of flavors.
*
* The listing result is an object with property "items". Each item is
* a flavor.
*
* @param {boolean} isPublic (optional)
* True if public flavors should be returned. If not specified, the API
* will return public flavors by default for Admins and only project
* flavors for non-admins.
* @param {boolean} getExtras (optional)
* Also retrieve the extra specs. This is expensive (one extra underlying
* call per flavor).
*/
this.getFlavors = function(isPublic, getExtras) {
var config = {'params': {}};
if (isPublic) { config.params.is_public = 'true'; }
if (getExtras) { config.params.get_extras = 'true'; }
return apiService.get('/api/nova/flavors/', config)
.error(function () {
horizon.alert('error', gettext('Unable to retrieve flavors.'));
});
};
/**
* @name hz.api.novaAPI.getFlavor
* @description
* Get a single flavor by ID.
* @param {string} id
* Specifies the id of the flavor to request.
* @param {boolean} getExtras (optional)
* Also retrieve the extra specs for the flavor.
*/
this.getFlavor = function(id, getExtras) {
var config = {'params': {}};
if (getExtras) { config.params.get_extras = 'true'; }
return apiService.get('/api/nova/flavors/' + id, config)
.error(function () {
horizon.alert('error', gettext('Unable to retrieve flavor.'));
});
};
/**
* @name hz.api.novaAPI.getFlavorExtraSpecs
* @description
* Get a single flavor's extra specs by ID.
* @param {string} id
* Specifies the id of the flavor to request the extra specs.
*/
this.getFlavorExtraSpecs = function(id) {
return apiService.get('/api/nova/flavors/' + id + '/extra-specs')
.error(function () {
horizon.alert('error', gettext('Unable to retrieve flavor extra specs.'));
});
};
}
angular.module('hz.api')

View File

@ -117,6 +117,12 @@ class APIResourceWrapper(object):
for attr in self._attrs
if hasattr(self, attr)))
def to_dict(self):
obj = {}
for key in self._attrs:
obj[key] = getattr(self._apiresource, key, None)
return obj
class APIDictWrapper(object):
"""Simple wrapper for api dictionaries

View File

@ -486,14 +486,21 @@ def flavor_delete(request, flavor_id):
novaclient(request).flavors.delete(flavor_id)
def flavor_get(request, flavor_id):
return novaclient(request).flavors.get(flavor_id)
def flavor_get(request, flavor_id, get_extras=False):
flavor = novaclient(request).flavors.get(flavor_id)
if get_extras:
flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
return flavor
@memoized
def flavor_list(request, is_public=True):
def flavor_list(request, is_public=True, get_extras=False):
"""Get the list of available instance sizes (flavors)."""
return novaclient(request).flavors.list(is_public=is_public)
flavors = novaclient(request).flavors.list(is_public=is_public)
if get_extras:
for flavor in flavors:
flavor.extras = flavor_get_extras(request, flavor.id, True, flavor)
return flavors
@memoized
@ -514,9 +521,10 @@ def remove_tenant_from_flavor(request, flavor, tenant):
flavor=flavor, tenant=tenant)
def flavor_get_extras(request, flavor_id, raw=False):
def flavor_get_extras(request, flavor_id, raw=False, flavor=None):
"""Get flavor extra specs."""
flavor = novaclient(request).flavors.get(flavor_id)
if flavor is None:
flavor = novaclient(request).flavors.get(flavor_id)
extras = flavor.get_keys()
if raw:
return extras

View File

@ -173,3 +173,115 @@ class Servers(generic.View):
'/api/nova/servers/%s' % urllib.quote(new.id),
new.to_dict()
)
@urls.register
class Server(generic.View):
"""API for retrieving a single server
"""
url_regex = r'nova/servers/(?P<server_id>.+|default)$'
@rest_utils.ajax()
def get(self, request, server_id):
"""Get a specific server
http://localhost/api/nova/servers/1
"""
return api.nova.server_get(request, server_id).to_dict()
@urls.register
class Extensions(generic.View):
"""API for nova extensions.
"""
url_regex = r'nova/extensions/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of extensions.
The listing result is an object with property "items". Each item is
an image.
Example GET:
http://localhost/api/nova/extensions
"""
result = api.nova.list_extensions(request)
return {'items': [e.to_dict() for e in result]}
@urls.register
class Flavors(generic.View):
"""API for nova flavors.
"""
url_regex = r'nova/flavors/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of flavors.
The listing result is an object with property "items". Each item is
an flavor. By default this will return the flavors for the user's
current project. If the user is admin, public flavors will also be
returned.
:param is_public: For a regular user, set to True to see all public
flavors. For an admin user, set to False to not see public flavors.
:param get_extras: Also retrieve the extra specs.
Example GET:
http://localhost/api/nova/flavors?is_public=true
"""
is_public = request.GET.get('is_public')
is_public = (is_public and is_public.lower() == 'true')
get_extras = request.GET.get('get_extras')
get_extras = bool(get_extras and get_extras.lower() == 'true')
flavors = api.nova.flavor_list(request, is_public=is_public,
get_extras=get_extras)
result = {'items': []}
for flavor in flavors:
d = flavor.to_dict()
if get_extras:
d['extras'] = flavor.extras
result['items'].append(d)
return result
@urls.register
class Flavor(generic.View):
"""API for retrieving a single flavor
"""
url_regex = r'nova/flavors/(?P<flavor_id>.+)/$'
@rest_utils.ajax()
def get(self, request, flavor_id):
"""Get a specific flavor
:param get_extras: Also retrieve the extra specs.
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')
flavor = api.nova.flavor_get(request, flavor_id, get_extras=get_extras)
result = flavor.to_dict()
if get_extras:
result['extras'] = flavor.extras
return result
@urls.register
class FlavorExtraSpecs(generic.View):
"""API for managing flavor extra specs
"""
url_regex = r'nova/flavors/(?P<flavor_id>.+)/extra-specs$'
@rest_utils.ajax()
def get(self, request, flavor_id):
"""Get a specific flavor's extra specs
Example GET:
http://localhost/api/nova/flavors/1/extra-specs
"""
return api.nova.flavor_get_extras(request, flavor_id, raw=True)

View File

@ -142,3 +142,131 @@ class NovaRestTestCase(test.TestCase):
request, 'Ni!', 'image123', 'flavor123', 'sekrit', 'base64 yes',
[{'name': 'root'}]
)
@mock.patch.object(nova.api, 'nova')
def test_server_get_single(self, nc):
request = self.mock_rest_request()
nc.server_get.return_value.to_dict.return_value = {'name': '1'}
response = nova.Server().get(request, "1")
self.assertStatusCode(response, 200)
nc.server_get.assert_called_once_with(request, "1")
#
# Extensions
#
@mock.patch.object(nova.api, 'nova')
def _test_extension_list(self, nc):
request = self.mock_rest_request()
nc.list_extensions.return_value = [
mock.Mock(**{'to_dict.return_value': {'name': 'foo'}}),
mock.Mock(**{'to_dict.return_value': {'name': 'bar'}}),
]
response = nova.Extensions().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content,
'{"items": [{"name": "foo"}, {"name": "bar"}]}')
nc.list_extensions.assert_called_once_with(request)
#
# Flavors
#
def test_get_extras_no(self):
self._test_flavor_get_single(get_extras=False)
def test_get_extras_yes(self):
self._test_flavor_get_single(get_extras=True)
def test_get_extras_default(self):
self._test_flavor_get_single(get_extras=None)
@mock.patch.object(nova.api, 'nova')
def _test_flavor_get_single(self, nc, get_extras):
if get_extras:
request = self.mock_rest_request(GET={'get_extras': 'tRuE'})
elif get_extras is None:
request = self.mock_rest_request()
get_extras = False
else:
request = self.mock_rest_request(GET={'get_extras': 'fAlsE'})
nc.flavor_get.return_value.to_dict.return_value = {'name': '1'}
response = nova.Flavor().get(request, "1")
self.assertStatusCode(response, 200)
if get_extras:
self.assertEqual(response.content, '{"extras": {}, "name": "1"}')
else:
self.assertEqual(response.content, '{"name": "1"}')
nc.flavor_get.assert_called_once_with(request, "1",
get_extras=get_extras)
@mock.patch.object(nova.api, 'nova')
def _test_flavor_list_public(self, nc, is_public=None):
if is_public:
request = self.mock_rest_request(GET={'is_public': 'tRuE'})
elif is_public is None:
request = self.mock_rest_request(GET={})
else:
request = self.mock_rest_request(GET={'is_public': 'fAlsE'})
nc.flavor_list.return_value = [
mock.Mock(**{'to_dict.return_value': {'id': '1'}}),
mock.Mock(**{'to_dict.return_value': {'id': '2'}}),
]
response = nova.Flavors().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content,
'{"items": [{"id": "1"}, {"id": "2"}]}')
nc.flavor_list.assert_called_once_with(request, is_public=is_public,
get_extras=False)
def test_flavor_list_private(self):
self._test_flavor_list_public(is_public=False)
def test_flavor_list_public(self):
self._test_flavor_list_public(is_public=True)
def test_flavor_list_public_none(self):
self._test_flavor_list_public(is_public=None)
@mock.patch.object(nova.api, 'nova')
def _test_flavor_list_extras(self, nc, get_extras=None):
if get_extras:
request = self.mock_rest_request(GET={'get_extras': 'tRuE'})
elif get_extras is None:
request = self.mock_rest_request(GET={})
get_extras = False
else:
request = self.mock_rest_request(GET={'get_extras': 'fAlsE'})
nc.flavor_list.return_value = [
mock.Mock(**{'extras': {}, 'to_dict.return_value': {'id': '1'}}),
mock.Mock(**{'extras': {}, 'to_dict.return_value': {'id': '2'}}),
]
response = nova.Flavors().get(request)
self.assertStatusCode(response, 200)
if get_extras:
self.assertEqual(response.content,
'{"items": [{"extras": {}, "id": "1"}, '
'{"extras": {}, "id": "2"}]}')
else:
self.assertEqual(response.content,
'{"items": [{"id": "1"}, {"id": "2"}]}')
nc.flavor_list.assert_called_once_with(request, is_public=None,
get_extras=get_extras)
def test_flavor_list_extras_no(self):
self._test_flavor_list_extras(get_extras=False)
def test_flavor_list_extras_yes(self):
self._test_flavor_list_extras(get_extras=True)
def test_flavor_list_extras_absent(self):
self._test_flavor_list_extras(get_extras=None)
@mock.patch.object(nova.api, 'nova')
def test_flavor_extra_specs(self, nc):
request = self.mock_rest_request()
nc.flavor_get_extras.return_value.to_dict.return_value = {'foo': '1'}
response = nova.FlavorExtraSpecs().get(request, "1")
self.assertStatusCode(response, 200)
nc.flavor_get_extras.assert_called_once_with(request, "1", raw=True)