Cinder REST API for angular front end

This is a very early cut at the REST API needed to
support the create instance wizard work.

Partially Implements: blueprint launch-instance-redesign
Co-Authored-By: Michael Hagedorn <mike.hagedorn@hp.com>
Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com>
Co-Authored-By: Travis Tripp <travis.tripp@hp.com>

Change-Id: I0c37474e81f5ee9893768974e47a20532c535b91
This commit is contained in:
Cindy Lu 2015-01-30 09:25:32 -08:00
parent 477faf4c0b
commit 468ce38418
6 changed files with 266 additions and 1 deletions

View File

@ -0,0 +1,83 @@
/*
Copyright 2015 IBM Corp.
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.cinderAPI
* @description Provides direct access to Cinder APIs.
*/
function CinderAPI(apiService) {
// Volumes
/**
* @name hz.api.cinderAPI.getVolumes
* @description
* Get a list of volumes.
*
* The listing result is an object with property "items." Each item is
* a volume.
*
* @param {Object} params
* Query parameters. Optional.
*
* @param {string} param.search_opts
* Filters to pass through the API.
* For example, "status": "available" will show all available volumes.
*/
this.getVolumes = function(params) {
var config = (params) ? {'params': params} : {};
return apiService.get('/api/cinder/volumes/' + config)
.error(function () {
horizon.alert('error', gettext('Unable to retrieve volumes.'));
});
};
// Volume Snapshots
/**
* @name hz.api.cinderAPI.getVolumeSnapshots
* @description
* Get a list of volume snapshots.
*
* The listing result is an object with property "items." Each item is
* a volume snapshot.
*
* @param {Object} params
* Query parameters. Optional.
*
* @param {string} param.search_opts
* Filters to pass through the API.
* For example, "status": "available" will show all available volume
* snapshots.
*/
this.getVolumeSnapshots = function(params) {
var config = (params) ? {'params': params} : {};
return apiService.get('/api/cinder/volsnaps/' + config)
.error(function () {
horizon.alert('error',
gettext('Unable to retrieve volume snapshots.'));
});
};
}
// Register it with the API module so that anybody using the
// API module will have access to the Cinder APIs.
angular.module('hz.api')
.service('cinderAPI', ['apiService', CinderAPI]);
}());

View File

@ -20,8 +20,9 @@
<script src='{{ STATIC_URL }}horizon/js/angular/controllers/metadata-widget-controller.js'></script>
<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.cinder.js'></script>
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.glance.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.nova.js'></script>
<script src='{{ STATIC_URL }}angular/widget.module.js'></script>

View File

@ -72,6 +72,12 @@ class BaseCinderAPIResourceWrapper(base.APIResourceWrapper):
return (getattr(self._apiresource, 'description', None) or
getattr(self._apiresource, 'display_description', None))
def to_dict(self):
obj = {}
for key in self._attrs:
obj[key] = getattr(self._apiresource, key, None)
return obj
class Volume(BaseCinderAPIResourceWrapper):

View File

@ -22,6 +22,7 @@ in https://wiki.openstack.org/wiki/APIChangeGuidelines.
"""
# import REST API modules here
import cinder #flake8: noqa
import glance #flake8: noqa
import keystone #flake8: noqa
import nova #flake8: noqa

View File

@ -0,0 +1,86 @@
# Copyright 2015 IBM Corp.
#
# 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 over the cinder 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
def _parse_filters(request):
"""Extract REST filter parameters from the request GET args.
We iterate like this so we avoid Django GET's odd behaviour of
handing a list-of-param through some QueryDict accesses.
"""
filters = {}
for param in request.GET:
filters[param] = request.GET[param]
return filters
@urls.register
class Volumes(generic.View):
"""API for cinder volumes.
"""
url_regex = r'cinder/volumes/$'
@rest_utils.ajax()
def get(self, request):
"""Get a detailed list of volumes associated with the current user's
project.
If invoked as an admin, you may set the GET parameter "all_projects"
to 'true'.
The following get parameters may be passed in the GET
:param search_opts includes options such as name, status, bootable
The listing result is an object with property "items".
"""
# TODO(clu_): when v2 pagination stuff in Cinder API merges
# (https://review.openstack.org/#/c/118450), handle here accordingly
if request.GET.get('all_projects') == 'true':
result = api.cinder.volume_list(request, {'all_tenants': 1})
else:
result = api.cinder.volume_list(
request,
search_opts=_parse_filters(request)
)
return {'items': [u.to_dict() for u in result]}
@urls.register
class VolumeSnapshots(generic.View):
"""API for cinder volume snapshots.
"""
url_regex = r'cinder/volumesnapshots/$'
@rest_utils.ajax()
def get(self, request):
"""Get a detailed list of volume snapshots associated with the current
user's project.
The listing result is an object with property "items".
"""
result = api.cinder.volume_snapshot_list(
request,
search_opts=_parse_filters(request)
)
return {'items': [u.to_dict() for u in result]}

View File

@ -0,0 +1,88 @@
# Copyright 2015 IBM Corp.
#
# 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
import testtools
from openstack_dashboard.api.rest import cinder
from openstack_dashboard.test.api_tests import rest_test_utils as utils
class CinderRestTestCase(testtools.TestCase):
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))
def test_volumes_get(self):
self._test_volumes_get(False, {})
def test_volumes_get_all(self):
self._test_volumes_get(True, {})
def test_volumes_get_with_filters(self):
filters = {'status': 'available'}
self._test_volumes_get(False, filters)
@mock.patch.object(cinder.api, 'cinder')
def _test_volumes_get(self, all, filters, cc):
if all:
request = utils.construct_request(GET={'all_projects': 'true'})
else:
request = utils.construct_request(**{'GET': filters})
cc.volume_list.return_value = [
mock.Mock(**{'to_dict.return_value': {'id': 'one'}}),
mock.Mock(**{'to_dict.return_value': {'id': 'two'}}),
]
response = cinder.Volumes().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content,
'{"items": [{"id": "one"}, {"id": "two"}]}')
if all:
cc.volume_list.assert_called_once_with(request,
{'all_tenants': 1})
else:
cc.volume_list.assert_called_once_with(request,
search_opts=filters)
@mock.patch.object(cinder.api, 'cinder')
def test_volume_snaps_get(self, cc):
request = utils.construct_request(**{'GET': {}})
cc.volume_snapshot_list.return_value = [
mock.Mock(**{'to_dict.return_value': {'id': 'one'}}),
mock.Mock(**{'to_dict.return_value': {'id': 'two'}}),
]
response = cinder.VolumeSnapshots().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content,
'{"items": [{"id": "one"}, {"id": "two"}]}')
cc.volume_snapshot_list.assert_called_once_with(request,
search_opts={})
@mock.patch.object(cinder.api, 'cinder')
def test_volume_snaps_get_with_filters(self, cc):
filters = {'status': 'available'}
request = utils.construct_request(**{'GET': dict(filters)})
cc.volume_snapshot_list.return_value = [
mock.Mock(**{'to_dict.return_value': {'id': 'one'}}),
mock.Mock(**{'to_dict.return_value': {'id': 'two'}}),
]
response = cinder.VolumeSnapshots().get(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content,
'{"items": [{"id": "one"}, {"id": "two"}]}')
cc.volume_snapshot_list.assert_called_once_with(request,
search_opts=filters)