Add API for container create, delete, list, show

This patch adds basic API calls for Containers, as well as the testing
infrastructure necessary to test these API calls.

Also adds some basic testing for Bays and Bay Models.

Change-Id: I7cb5dfbd32695c95a414b8d41aa0aa178fe2472e
Implements: blueprint container-api
This commit is contained in:
Rob Cresswell 2015-10-09 18:19:43 +01:00
parent 1fe073af8b
commit 4527b6c7c5
8 changed files with 412 additions and 13 deletions

View File

@ -14,13 +14,11 @@
from __future__ import absolute_import
import logging
from magnumclient.v1 import client as magnum_client
from horizon import exceptions
from horizon.utils.memoized import memoized
from openstack_dashboard.api import base
LOG = logging.getLogger(__name__)
@ -36,6 +34,7 @@ BAY_CREATE_ATTRS = ['name', 'baymodel_id', 'node_count', 'discovery_url',
'bay_create_timeout', 'master_count']
@memoized
def magnumclient(request):
magnum_url = ""
try:
@ -105,3 +104,45 @@ def bay_list(request, limit=None, marker=None, sort_key=None,
def bay_show(request, id):
return magnumclient(request).bays.get(id)
def container_create(request, bay_id, **kwargs):
"""Creates a container object
:param request: Request context
:param bay_id: ID of a bay (Required)
:param kwargs: Image ID, Name, Command, Memory
:returns: Container object
"""
return magnumclient(request).containers.create(bay_id=bay_id, **kwargs)
def container_delete(request, id):
"""Deletes a container
:param request: Request context
:param id: The ID of the container to delete
"""
magnumclient(request).containers.delete(id)
def container_list(request, marker=None, limit=None, sort_key=None,
sort_dir=None, detail=False):
"""Lists all containers
:param request: Request context
:param marker: Optional, ID of last container in previous results
:param limit: '==0' return all, '> 0' specifies max, None respects max
imposed by Magnum API
:param sort_key: Optional, key to sort by
:param sort_dir: Optional, direction of sorting ('asc' or 'desc')
:param detail: Optional, boolean, return detailed info about containers
"""
return magnumclient(request).containers.list(
marker=marker, limit=limit, sort_key=sort_key, sort_dir=sort_dir,
detail=detail)
def container_show(request, id):
"""Get an individual container
:param request: Request context
:param id: ID of the container to get
"""
return magnumclient(request).containers.get(id)

View File

@ -40,11 +40,11 @@ class BayModels(generic.View):
def get(self, request):
"""Get a list of the BayModels for a project.
The returned result is an object with property 'baymodels' and each
The returned result is an object with property 'items' and each
item under this is a BayModel.
"""
result = magnum.baymodel_list(request)
return{'baymodels': [change_to_id(n.to_dict()) for n in result]}
return {'items': [change_to_id(n.to_dict()) for n in result]}
@rest_utils.ajax(data_required=True)
def delete(self, request):
@ -63,8 +63,8 @@ class BayModels(generic.View):
"""
new_baymodel = magnum.baymodel_create(request, **request.DATA)
return rest_utils.CreatedResponse(
'/api/containers/baymodel/%s' % new_baymodel.id,
new_baymodel.to_dict())
'/api/containers/baymodel/%s' % new_baymodel['uuid'],
new_baymodel)
@urls.register
@ -77,11 +77,11 @@ class Bays(generic.View):
def get(self, request):
"""Get a list of the Bays for a project.
The returned result is an object with property 'bays' and each
The returned result is an object with property 'items' and each
item under this is a Bay.
"""
result = magnum.bay_list(request)
return{'bays': [change_to_id(n.to_dict()) for n in result]}
return {'items': [change_to_id(n.to_dict()) for n in result]}
@rest_utils.ajax(data_required=True)
def delete(self, request):
@ -100,5 +100,41 @@ class Bays(generic.View):
"""
new_bay = magnum.bay_create(request, **request.DATA)
return rest_utils.CreatedResponse(
'/api/containers/bay/%s' % new_bay.uuid,
new_bay.to_dict())
'/api/containers/bay/%s' % new_bay['uuid'],
new_bay)
@urls.register
class Containers(generic.View):
"""API for Magnum Containers
"""
url_regex = r'containers/containers/$'
@rest_utils.ajax()
def get(self, request):
"""Get a list of the Containers for a project.
The returned result is an object with property 'items' and each
item under this is a Container.
"""
result = magnum.container_list(request)
return {'items': [n.to_dict() for n in result]}
@rest_utils.ajax(data_required=True)
def delete(self, request):
"""Delete one or more Containers by ID.
Returns HTTP 204 (no content) on successful deletion.
"""
for container_id in request.DATA:
magnum.container_delete(request, container_id)
@rest_utils.ajax(data_required=True)
def post(self, request):
"""Create a new Container.
Returns the new Container object on success.
"""
container = magnum.container_create(request, **request.DATA)
return rest_utils.CreatedResponse(
'/api/containers/container/%s' % container['uuid'], container)

View File

@ -49,7 +49,7 @@
}
function getBaysSuccess(response) {
ctrl.bays = response.bays;
ctrl.bays = response.items;
}
function singleDelete(bay) {

View File

@ -49,7 +49,7 @@
}
function getBayModelsSuccess(response) {
ctrl.baymodels = response.baymodels;
ctrl.baymodels = response.items;
}
function singleDelete(baymodel) {

View File

@ -0,0 +1,66 @@
# Copyright 2015 Cisco Systems, Inc.
#
# 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.
from magnum_ui import api
from magnum_ui.test import helpers as test
class MagnumApiTests(test.APITestCase):
def test_container_list(self):
containers = self.magnum_containers.list()
form_data = {'marker': None,
'limit': None,
'sort_key': None,
'sort_dir': None,
'detail': False}
magnumclient = self.stub_magnumclient()
magnumclient.containers = self.mox.CreateMockAnything()
magnumclient.containers.list(**form_data).AndReturn(containers)
self.mox.ReplayAll()
api.magnum.container_list(self.request)
def test_container_get(self):
container = self.magnum_containers.first()
magnumclient = self.stub_magnumclient()
magnumclient.containers = self.mox.CreateMockAnything()
magnumclient.containers.get(container['uuid']).AndReturn(container)
self.mox.ReplayAll()
api.magnum.container_show(self.request, container['uuid'])
def test_container_delete(self):
container = self.magnum_containers.first()
magnumclient = self.stub_magnumclient()
magnumclient.containers = self.mox.CreateMockAnything()
magnumclient.containers.delete(container['uuid'])
self.mox.ReplayAll()
api.magnum.container_delete(self.request, container['uuid'])
def test_container_create(self):
container = self.magnum_containers.first()
form_data = {'bay_id': container['bay'],
'name': container['name']}
magnumclient = self.stub_magnumclient()
magnumclient.containers = self.mox.CreateMockAnything()
magnumclient.containers.create(**form_data)\
.AndReturn(container)
self.mox.ReplayAll()
api.magnum.container_create(self.request, **form_data)

View File

@ -0,0 +1,149 @@
# Copyright 2015 Cisco Systems, Inc.
#
# 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 json
import mock
from magnum_ui.api.rest import magnum
from magnum_ui.test import test_data
from openstack_dashboard.test import helpers as test
from openstack_dashboard.test.test_data.utils import TestData
TEST = TestData(test_data.data)
class MagnumRestTestCase(test.TestCase):
# BayModels
@mock.patch.object(magnum, 'magnum')
def test_baymodel_get(self, client):
request = self.mock_rest_request()
client.baymodel_list.return_value = \
mock_resource(TEST.baymodels.list())
response = magnum.BayModels().get(request)
self.assertStatusCode(response, 200)
self.assertItemsCollectionEqual(response, TEST.baymodels.list())
client.baymodel_list.assert_called_once_with(request)
@mock.patch.object(magnum, 'magnum')
def test_baymodel_create(self, client):
test_baymodel = TEST.baymodels.first()
test_body = json.dumps(test_baymodel)
request = self.mock_rest_request(body=test_body)
client.baymodel_create.return_value = test_baymodel
response = magnum.BayModels().post(request)
self.assertStatusCode(response, 201)
self.assertEqual(response['location'],
'/api/containers/baymodel/%s' % test_baymodel['uuid'])
client.baymodel_create.assert_called_once_with(request,
**test_baymodel)
@mock.patch.object(magnum, 'magnum')
def test_baymodel_delete(self, client):
test_baymodel = TEST.baymodels.first()
request = self.mock_rest_request(
body='{"baymodel_id":' + str(test_baymodel['uuid']) + '}')
response = magnum.BayModels().delete(request)
self.assertStatusCode(response, 204)
client.baymodel_delete.assert_called_once_with(
request,
u'baymodel_id')
# Bays
@mock.patch.object(magnum, 'magnum')
def test_bay_get(self, client):
request = self.mock_rest_request()
client.bay_list.return_value = \
mock_resource(TEST.bays.list())
response = magnum.Bays().get(request)
self.assertStatusCode(response, 200)
self.assertItemsCollectionEqual(response, TEST.bays.list())
client.bay_list.assert_called_once_with(request)
@mock.patch.object(magnum, 'magnum')
def test_bay_create(self, client):
test_bay = TEST.bays.first()
test_body = json.dumps(test_bay)
request = self.mock_rest_request(body=test_body)
client.bay_create.return_value = test_bay
response = magnum.Bays().post(request)
self.assertStatusCode(response, 201)
self.assertEqual(response['location'],
'/api/containers/bay/%s' % test_bay['uuid'])
client.bay_create.assert_called_once_with(request,
**test_bay)
@mock.patch.object(magnum, 'magnum')
def test_bay_delete(self, client):
test_bay = TEST.bays.first()
request = self.mock_rest_request(
body='{"bay_id":' + str(test_bay['uuid']) + '}')
response = magnum.Bays().delete(request)
self.assertStatusCode(response, 204)
client.bay_delete.assert_called_once_with(
request,
u'bay_id')
# Containers
@mock.patch.object(magnum, 'magnum')
def test_container_get(self, client):
request = self.mock_rest_request()
client.container_list.return_value = \
mock_resource(TEST.magnum_containers.list())
response = magnum.Containers().get(request)
self.assertStatusCode(response, 200)
self.assertItemsCollectionEqual(response,
TEST.magnum_containers.list())
client.container_list.assert_called_once_with(request)
@mock.patch.object(magnum, 'magnum')
def test_container_create(self, client):
test_cont = TEST.magnum_containers.first()
test_body = json.dumps(test_cont)
request = self.mock_rest_request(body=test_body)
client.container_create.return_value = test_cont
response = magnum.Containers().post(request)
self.assertStatusCode(response, 201)
self.assertEqual(response['location'],
'/api/containers/container/%s' % test_cont['uuid'])
client.container_create.assert_called_once_with(request, **test_cont)
@mock.patch.object(magnum, 'magnum')
def test_container_delete(self, client):
test_container = TEST.magnum_containers.first()
request = self.mock_rest_request(
body='{"container_id":' + str(test_container['uuid']) + '}')
response = magnum.Containers().delete(request)
self.assertStatusCode(response, 204)
client.container_delete.assert_called_once_with(
request,
u'container_id')
def mock_resource(resource):
"""Utility function to make mocking more DRY"""
mocked_data = \
[mock.Mock(**{'to_dict.return_value': item}) for item in resource]
return mocked_data

41
magnum_ui/test/helpers.py Normal file
View File

@ -0,0 +1,41 @@
# Copyright 2015 Cisco Systems, Inc.
#
# 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.
from magnum_ui import api
from magnum_ui.test import test_data
from magnumclient.v1 import client as magnum_client
from openstack_dashboard.test import helpers
class APITestCase(helpers.APITestCase):
"""Extends the base Horizon APITestCase for magnumclient"""
def setUp(self):
super(APITestCase, self).setUp()
self._original_magnumclient = api.magnum.magnumclient
api.magnum.magnumclient = lambda request: self.stub_magnumclient()
def _setup_test_data(self):
super(APITestCase, self)._setup_test_data()
test_data.data(self)
def tearDown(self):
super(APITestCase, self).tearDown()
api.magnum.magnumclient = self._original_magnumclient
def stub_magnumclient(self):
if not hasattr(self, "magnumclient"):
self.mox.StubOutWithMock(magnum_client, 'Client')
self.magnumclient = self.mox.CreateMock(magnum_client.Client)
return self.magnumclient

View File

@ -0,0 +1,66 @@
# Copyright 2015 Cisco Systems, Inc.
#
# 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.
from openstack_dashboard.test.test_data import utils
def data(TEST):
# Test Data Containers
TEST.baymodels = utils.TestDataContainer()
TEST.bays = utils.TestDataContainer()
# 'magnum_containers' to avoid Swift naming confusion
TEST.magnum_containers = utils.TestDataContainer()
# Bay Models
baymodel_dict_1 = {"uuid": 1,
"name": "kindofabigdeal",
"image-id": "",
"keypair-id": "",
"external-network-id": "",
"coe": "",
"fixed-network": "",
"ssh-authorized-key": "",
"dns-nameserver": "",
"flavor-id": "",
"master-flavor-id": "",
"docker-volume-size": "",
"http-proxy": "",
"https-proxy": "",
"no-proxy": "",
"labels": "",
"tls-disabled": "",
"public": ""}
TEST.baymodels.add(baymodel_dict_1)
# Bays
bay_dict_1 = {"uuid": 1,
"name": "peopleknowme",
"baymodel": baymodel_dict_1["uuid"],
"node-count": "",
"master-count": "",
"discovery-url": "",
"timeout": 0}
TEST.bays.add(bay_dict_1)
# Containers
container_dict_1 = {"uuid": 1,
"name": "myapartmentsmellsofrichmahogany",
"image": "",
"bay": bay_dict_1["uuid"],
"command": "",
"memory": ""}
TEST.magnum_containers.add(container_dict_1)