Glance Rest API for Angular Front End
This is the glance direct / non-abstracted rest API for the angular front end. This initial patch will only implement the APIs needed to support the launch instance work. This was refactored out of change: 141273 All v1 / v2 work is being done separately in change: 150084 Based on the work done for Keystone here: https://review.openstack.org/#/c/150636 Partially Implements: blueprint launch-instance-redesign Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com> Change-Id: I28735e242d610235695b1070a15f739e0d50b696
This commit is contained in:
parent
26bddf3841
commit
1126054cf3
horizon
openstack_dashboard
159
horizon/static/horizon/js/angular/services/hz.api.glance.js
Normal file
159
horizon/static/horizon/js/angular/services/hz.api.glance.js
Normal file
@ -0,0 +1,159 @@
|
||||
/*
|
||||
Copyright 2015, Hewlett-Packard Development Company, L.P.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name hz.api.glanceAPI
|
||||
* @description Provides direct pass through to Glance with NO abstraction.
|
||||
*/
|
||||
function GlanceAPI(apiService) {
|
||||
|
||||
// Images
|
||||
|
||||
/**
|
||||
* @name hz.api.glanceAPI.getImage
|
||||
* @description
|
||||
* Get a single image by ID
|
||||
* @param {string} id
|
||||
* Specifies the id of the image to request.
|
||||
*/
|
||||
this.getImage = function(id) {
|
||||
return apiService.get('/api/glance/images/' + id)
|
||||
.error(function () {
|
||||
horizon.alert('error', gettext('Unable to retrieve image.'));
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @name hz.api.glanceAPI.getImages
|
||||
* @description
|
||||
* Get a list of images.
|
||||
*
|
||||
* The listing result is an object with property "items". Each item is
|
||||
* an image.
|
||||
*
|
||||
* @param {Object} params
|
||||
* Query parameters. Optional.
|
||||
*
|
||||
* @param {boolean} params.paginate
|
||||
* True to paginate automatically.
|
||||
*
|
||||
* @param {string} params.marker
|
||||
* Specifies the image of the last-seen image.
|
||||
*
|
||||
* The typical pattern of limit and marker is to make an
|
||||
* initial limited request and then to use the last
|
||||
* image from the response as the marker parameter
|
||||
* in a subsequent limited request. With paginate, limit
|
||||
* is automatically set.
|
||||
*
|
||||
* @param {string} params.sort_dir
|
||||
* The sort direction ('asc' or 'desc').
|
||||
*
|
||||
* @param {string} params.sort_key
|
||||
* The field to sort on (for example, 'created_at').
|
||||
* Default is created_at.
|
||||
*
|
||||
* @param {string} params.other
|
||||
* Any additional request parameters will be passed through the API as
|
||||
* filters. For example "name" : "fedora" would filter on the fedora name.
|
||||
*/
|
||||
this.getImages = function(params) {
|
||||
var config = (params) ? { 'params' : params} : {};
|
||||
return apiService.get('/api/glance/images/', config)
|
||||
.error(function () {
|
||||
horizon.alert('error', gettext('Unable to retrieve images.'));
|
||||
});
|
||||
};
|
||||
|
||||
// Metadata Definitions - Namespaces
|
||||
|
||||
/**
|
||||
* @name hz.api.glanceAPI.getNamespaces
|
||||
* @description
|
||||
* Get a list of metadata definition namespaces.
|
||||
*
|
||||
* http://docs.openstack.org/developer/glance/metadefs-concepts.html
|
||||
*
|
||||
* The listing result is an object with property "items". Each item is
|
||||
* an namespace.
|
||||
*
|
||||
* @description
|
||||
* Get a list of namespaces.
|
||||
*
|
||||
* The listing result is an object with property "items". Each item is
|
||||
* a namespace.
|
||||
*
|
||||
* @param {Object} params
|
||||
* Query parameters. Optional.
|
||||
*
|
||||
* @param {boolean} params.paginate
|
||||
* True to paginate automatically.
|
||||
*
|
||||
* @param {string} params.marker
|
||||
* Specifies the namespace of the last-seen namespace.
|
||||
*
|
||||
* The typical pattern of limit and marker is to make an
|
||||
* initial limited request and then to use the last
|
||||
* namespace from the response as the marker parameter
|
||||
* in a subsequent limited request. With paginate, limit
|
||||
* is automatically set.
|
||||
*
|
||||
* @param {string} params.sort_dir
|
||||
* The sort direction ('asc' or 'desc').
|
||||
*
|
||||
* @param {string} params.sort_key
|
||||
* The field to sort on (for example, 'created_at').
|
||||
* Default is namespace.
|
||||
*
|
||||
* @param {string} params.other
|
||||
* Any additional request parameters will be passed through the API as
|
||||
* filters.
|
||||
*/
|
||||
this.getNamespaces = function(params) {
|
||||
var config = (params) ? { 'params' : params} : {};
|
||||
return apiService.get('/api/glance/metadefs/namespaces/', config)
|
||||
.error(function () {
|
||||
horizon.alert('error', gettext('Unable to retrieve namespaces.'));
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @name hz.api.glanceAPI.getImages
|
||||
* @description
|
||||
* Get a specific namespace.
|
||||
*
|
||||
* http://docs.openstack.org/developer/glance/metadefs-concepts.html
|
||||
*/
|
||||
this.getNamespace = function(namespace) {
|
||||
return apiService.get('/api/glance/metadefs/namespaces/' + namespace)
|
||||
.error(function () {
|
||||
horizon.alert('error', gettext('Unable to retrieve namespace.'));
|
||||
});
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// Register it with the API module so that anybody using the
|
||||
// API module will have access to the Glance APIs.
|
||||
|
||||
angular.module('hz.api')
|
||||
.service('glanceAPI', ['apiService', GlanceAPI]);
|
||||
|
||||
}());
|
@ -21,6 +21,7 @@
|
||||
<script src='{{ STATIC_URL }}horizon/js/angular/hz.api.module.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.service.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.keystone.js'></script>
|
||||
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.glance.js'></script>
|
||||
|
||||
<script src='{{ STATIC_URL }}angular/widget.module.js'></script>
|
||||
<script src='{{ STATIC_URL }}angular/help-panel/help-panel.js'></script>
|
||||
|
@ -168,6 +168,9 @@ class BaseGlanceMetadefAPIResourceWrapper(base.APIResourceWrapper):
|
||||
result[attr] = getattr(self, attr)
|
||||
return json.dumps(result, indent=indent)
|
||||
|
||||
def to_dict(self):
|
||||
return self._apiresource
|
||||
|
||||
|
||||
class Namespace(BaseGlanceMetadefAPIResourceWrapper):
|
||||
|
||||
@ -222,7 +225,8 @@ def metadefs_namespace_list(request,
|
||||
typically at first deployment is done in a single transaction
|
||||
giving them a potentially unpredictable sort result when using
|
||||
create_at.
|
||||
:param filters: specifies addition fields to filter on such as name.
|
||||
:param filters: specifies addition fields to filter on such as
|
||||
resource_types.
|
||||
:returns A tuple of three values:
|
||||
1) Current page results
|
||||
2) A boolean of whether or not there are previous page(s).
|
||||
|
@ -22,4 +22,5 @@ in https://wiki.openstack.org/wiki/APIChangeGuidelines.
|
||||
"""
|
||||
|
||||
# import REST API modules here
|
||||
import glance #flake8: noqa
|
||||
import keystone #flake8: noqa
|
||||
|
176
openstack_dashboard/api/rest/glance.py
Normal file
176
openstack_dashboard/api/rest/glance.py
Normal file
@ -0,0 +1,176 @@
|
||||
|
||||
# Copyright 2015, Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
"""API for the glance service.
|
||||
"""
|
||||
|
||||
from django.views import generic
|
||||
|
||||
from openstack_dashboard import api
|
||||
from openstack_dashboard.api.rest import utils as rest_utils
|
||||
from openstack_dashboard.api.rest import urls
|
||||
|
||||
|
||||
CLIENT_KEYWORDS = {'marker', 'sort_dir', 'sort_key', 'paginate'}
|
||||
|
||||
|
||||
def _parse_filters_kwargs(request):
|
||||
"""REST request parameters are separated appropriately.
|
||||
|
||||
Glance client processes some keywords separately
|
||||
from filters and takes them as separate inputs.
|
||||
This potentially may not be needed when Glance
|
||||
v2 support is brought into Horizon via a separate effort.
|
||||
"""
|
||||
filters = {}
|
||||
kwargs = {}
|
||||
for param in request.GET:
|
||||
if param in CLIENT_KEYWORDS:
|
||||
kwargs[param] = request.GET[param]
|
||||
else:
|
||||
filters[param] = request.GET[param]
|
||||
return filters, kwargs
|
||||
|
||||
|
||||
@urls.register
|
||||
class Image(generic.View):
|
||||
"""API for retrieving a single image
|
||||
"""
|
||||
url_regex = r'glance/images/(?P<image_id>.+|default)$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, image_id):
|
||||
"""Get a specific image
|
||||
|
||||
http://localhost/api/glance/images/cc758c90-3d98-4ea1-af44-aab405c9c915
|
||||
"""
|
||||
return api.glance.image_get(request, image_id).to_dict()
|
||||
|
||||
|
||||
@urls.register
|
||||
class Images(generic.View):
|
||||
"""API for Glance images.
|
||||
"""
|
||||
url_regex = r'glance/images/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get a list of images.
|
||||
|
||||
The listing result is an object with property "items". Each item is
|
||||
an image.
|
||||
|
||||
Example GET:
|
||||
http://localhost/api/glance/images?sort_dir=desc&sort_key=name&name=cirros-0.3.2-x86_64-uec #flake8: noqa
|
||||
|
||||
The following get parameters may be passed in the GET
|
||||
request:
|
||||
|
||||
:param paginate: If true will perform pagination based on settings.
|
||||
:param marker: Specifies the namespace of the last-seen image.
|
||||
The typical pattern of limit and marker is to make an
|
||||
initial limited request and then to use the last
|
||||
namespace from the response as the marker parameter
|
||||
in a subsequent limited request. With paginate, limit
|
||||
is automatically set.
|
||||
:param sort_dir: The sort direction ('asc' or 'desc').
|
||||
:param sort_key: The field to sort on (for example, 'created_at').
|
||||
Default is created_at.
|
||||
|
||||
Any additional request parameters will be passed through the API as
|
||||
filters. There are v1/v2 complications which are being addressed as a
|
||||
separate work stream: https://review.openstack.org/#/c/150084/
|
||||
"""
|
||||
|
||||
filters, kwargs = _parse_filters_kwargs(request)
|
||||
|
||||
images, has_more_data, has_prev_data = api.glance.image_list_detailed(
|
||||
request, filters=filters, **kwargs)
|
||||
|
||||
return {
|
||||
'items': [i.to_dict() for i in images],
|
||||
'has_more_data': has_more_data,
|
||||
'has_prev_data': has_prev_data,
|
||||
}
|
||||
|
||||
|
||||
@urls.register
|
||||
class MetadefsNamespace(generic.View):
|
||||
"""API for Glance Metadata Definitions.
|
||||
|
||||
http://docs.openstack.org/developer/glance/metadefs-concepts.html
|
||||
"""
|
||||
url_regex = r'glance/metadefs/namespaces/(?P<namespace>.+|default)$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, namespace):
|
||||
"""Get a specific metadata definition namespaces.
|
||||
|
||||
Returns the namespace. GET params are passed through.
|
||||
|
||||
Example GET:
|
||||
http://localhost/api/glance/metadefs/namespaces/OS::Compute::Watchdog
|
||||
"""
|
||||
return api.glance.metadefs_namespace_get(request, namespace)
|
||||
|
||||
|
||||
@urls.register
|
||||
class MetadefsNamespaces(generic.View):
|
||||
"""API for Single Glance Metadata Definitions.
|
||||
|
||||
http://docs.openstack.org/developer/glance/metadefs-concepts.html
|
||||
"""
|
||||
url_regex = r'glance/metadefs/namespaces/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get a list of metadata definition namespaces.
|
||||
|
||||
The listing result is an object with property "items". Each item is
|
||||
a namespace.
|
||||
|
||||
Example GET:
|
||||
http://localhost/api/glance/metadefs/namespaces?resource_types=OS::Nova::Flavor&sort_dir=desc&marker=OS::Compute::Watchdog&paginate=False&sort_key=namespace #flake8: noqa
|
||||
|
||||
The following get parameters may be passed in the GET
|
||||
request:
|
||||
|
||||
:param paginate: If true will perform pagination based on settings.
|
||||
:param marker: Specifies the namespace of the last-seen namespace.
|
||||
The typical pattern of limit and marker is to make an
|
||||
initial limited request and then to use the last
|
||||
namespace from the response as the marker parameter
|
||||
in a subsequent limited request. With paginate, limit
|
||||
is automatically set.
|
||||
:param sort_dir: The sort direction ('asc' or 'desc').
|
||||
:param sort_key: The field to sort on (for example, 'created_at').
|
||||
Default is namespace. The way base namespaces are loaded into
|
||||
glance typically at first deployment is done in a single
|
||||
transaction giving them a potentially unpredictable sort result
|
||||
when using create_at.
|
||||
|
||||
Any additional request parameters will be passed through the API as
|
||||
filters.
|
||||
"""
|
||||
|
||||
filters, kwargs = _parse_filters_kwargs(request)
|
||||
|
||||
namespaces, has_more, has_prev = api.glance.metadefs_namespace_list(
|
||||
request, filters=filters, **kwargs)
|
||||
|
||||
return {
|
||||
'items': [n.to_dict() for n in namespaces],
|
||||
'has_more_data': has_more,
|
||||
'has_prev_data': has_prev,
|
||||
}
|
153
openstack_dashboard/test/api_tests/glance_rest_tests.py
Normal file
153
openstack_dashboard/test/api_tests/glance_rest_tests.py
Normal file
@ -0,0 +1,153 @@
|
||||
# Copyright 2015, Rackspace, US, Inc.
|
||||
# Copyright 2015, Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import mock
|
||||
|
||||
from openstack_dashboard.api.rest import glance
|
||||
from openstack_dashboard.test.api_tests import rest_test_utils # noqa
|
||||
from openstack_dashboard.test import helpers as test
|
||||
|
||||
|
||||
class ImagesRestTestCase(test.APITestCase):
|
||||
def assertStatusCode(self, response, expected_code):
|
||||
if response.status_code == expected_code:
|
||||
return
|
||||
self.fail('status code %r != %r: %s' % (response.status_code,
|
||||
expected_code,
|
||||
response.content))
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_get_single(self, gc):
|
||||
request = rest_test_utils.construct_request()
|
||||
gc.image_get.return_value.to_dict.return_value = {'name': '1'}
|
||||
|
||||
response = glance.Image().get(request, "1")
|
||||
self.assertStatusCode(response, 200)
|
||||
gc.image_get.assert_called_once_with(request, "1")
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_image_get_list_detailed(self, gc):
|
||||
kwargs = {
|
||||
'sort_dir': 'desc',
|
||||
'sort_key': 'namespace',
|
||||
'marker': 1,
|
||||
'paginate': False,
|
||||
}
|
||||
filters = {'name': 'fedora'}
|
||||
request = rest_test_utils.construct_request(
|
||||
**{'GET': dict(kwargs, **filters)})
|
||||
gc.image_list_detailed.return_value = ([
|
||||
mock.Mock(**{'to_dict.return_value': {'name': 'fedora'}}),
|
||||
mock.Mock(**{'to_dict.return_value': {'name': 'cirros'}})
|
||||
], False, False)
|
||||
|
||||
response = glance.Images().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.content,
|
||||
'{"items": [{"name": "fedora"}, {"name": "cirros"}]'
|
||||
', "has_more_data": false, "has_prev_data": false}')
|
||||
gc.image_list_detailed.assert_called_once_with(request,
|
||||
filters=filters,
|
||||
**kwargs)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_namespace_get_list(self, gc):
|
||||
request = rest_test_utils.construct_request(**{'GET': {}})
|
||||
gc.metadefs_namespace_list.return_value = ([
|
||||
mock.Mock(**{'to_dict.return_value': {'namespace': '1'}}),
|
||||
mock.Mock(**{'to_dict.return_value': {'namespace': '2'}})
|
||||
], False, False)
|
||||
|
||||
response = glance.MetadefsNamespaces().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.content,
|
||||
'{"items": [{"namespace": "1"}, {"namespace": "2"}]'
|
||||
', "has_more_data": false, "has_prev_data": false}')
|
||||
gc.metadefs_namespace_list.assert_called_once_with(request,
|
||||
filters={},
|
||||
**{})
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_namespace_get_list_kwargs_and_filters(self, gc):
|
||||
kwargs = {
|
||||
'sort_dir': 'desc',
|
||||
'sort_key': 'namespace',
|
||||
'marker': 1,
|
||||
'paginate': False,
|
||||
}
|
||||
filters = {'resource_types': 'type'}
|
||||
request = rest_test_utils.construct_request(
|
||||
**{'GET': dict(kwargs, **filters)})
|
||||
gc.metadefs_namespace_list.return_value = ([
|
||||
mock.Mock(**{'to_dict.return_value': {'namespace': '1'}}),
|
||||
mock.Mock(**{'to_dict.return_value': {'namespace': '2'}})
|
||||
], False, False)
|
||||
|
||||
response = glance.MetadefsNamespaces().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.content,
|
||||
'{"items": [{"namespace": "1"}, {"namespace": "2"}]'
|
||||
', "has_more_data": false, "has_prev_data": false}')
|
||||
gc.metadefs_namespace_list.assert_called_once_with(request,
|
||||
filters=filters,
|
||||
**kwargs)
|
||||
|
||||
@mock.patch.object(glance.api, 'glance')
|
||||
def test_namespace_get_namespace(self, gc):
|
||||
kwargs = {'resource_type': ['OS::Nova::Flavor']}
|
||||
request = rest_test_utils.construct_request(**{'GET': dict(kwargs)})
|
||||
gc.metadefs_namespace_get.return_value\
|
||||
.to_dict.return_value = {'namespace': '1'}
|
||||
|
||||
response = glance.MetadefsNamespace().get(request, "1")
|
||||
self.assertStatusCode(response, 200)
|
||||
gc.metadefs_namespace_get.assert_called_once_with(request,
|
||||
"1")
|
||||
|
||||
def test_parse_filters_keywords(self):
|
||||
kwargs = {
|
||||
'sort_dir': '1',
|
||||
'sort_key': '2',
|
||||
}
|
||||
filters = {
|
||||
'filter1': '1',
|
||||
'filter2': '2',
|
||||
}
|
||||
|
||||
# Combined
|
||||
request_params = dict(kwargs)
|
||||
request_params.update(filters)
|
||||
request = rest_test_utils.construct_request(
|
||||
**{'GET': dict(request_params)})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual(kwargs, output_kwargs)
|
||||
self.assertDictEqual(filters, output_filters)
|
||||
|
||||
# Empty Filters
|
||||
request = rest_test_utils.construct_request(**{'GET': dict(kwargs)})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual(kwargs, output_kwargs)
|
||||
self.assertDictEqual({}, output_filters)
|
||||
|
||||
# Emtpy keywords
|
||||
request = rest_test_utils.construct_request(**{'GET': dict(filters)})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual({}, output_kwargs)
|
||||
self.assertDictEqual(filters, output_filters)
|
||||
|
||||
# Empty both
|
||||
request = rest_test_utils.construct_request(**{'GET': dict()})
|
||||
output_filters, output_kwargs = glance._parse_filters_kwargs(request)
|
||||
self.assertDictEqual({}, output_kwargs)
|
||||
self.assertDictEqual({}, output_filters)
|
Loading…
x
Reference in New Issue
Block a user