Port cells extension to v3 API Part 1
This changeset only copies the v2 files (implementation and test) into the appropriate v3 directories unchanged. The copy as-is will not be loaded by either the v2 or v3 extension loaders. The second changeset will then make the changes required for it to work as a v3 extension. This is being done in order to make reviewing of extension porting easier as gerrit will display only what is actually changed for v3 rather than entirely new files Partially implements blueprint nova-v3-api Change-Id: Ie629ee328d76a1595350b597c7874b430217c769
This commit is contained in:
347
nova/api/openstack/compute/plugins/v3/cells.py
Normal file
347
nova/api/openstack/compute/plugins/v3/cells.py
Normal file
@@ -0,0 +1,347 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2011-2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""The cells extension."""
|
||||
|
||||
from oslo.config import cfg
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack import common
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import wsgi
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova.cells import rpcapi as cells_rpcapi
|
||||
from nova.compute import api as compute
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import timeutils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
CONF.import_opt('name', 'nova.cells.opts', group='cells')
|
||||
CONF.import_opt('capabilities', 'nova.cells.opts', group='cells')
|
||||
|
||||
authorize = extensions.extension_authorizer('compute', 'cells')
|
||||
|
||||
|
||||
def make_cell(elem):
|
||||
elem.set('name')
|
||||
elem.set('username')
|
||||
elem.set('type')
|
||||
elem.set('rpc_host')
|
||||
elem.set('rpc_port')
|
||||
|
||||
caps = xmlutil.SubTemplateElement(elem, 'capabilities',
|
||||
selector='capabilities')
|
||||
cap = xmlutil.SubTemplateElement(caps, xmlutil.Selector(0),
|
||||
selector=xmlutil.get_items)
|
||||
cap.text = 1
|
||||
make_capacity(elem)
|
||||
|
||||
|
||||
def make_capacity(cell):
|
||||
|
||||
def get_units_by_mb(capacity_info):
|
||||
return capacity_info['units_by_mb'].items()
|
||||
|
||||
capacity = xmlutil.SubTemplateElement(cell, 'capacities',
|
||||
selector='capacities')
|
||||
|
||||
ram_free = xmlutil.SubTemplateElement(capacity, 'ram_free',
|
||||
selector='ram_free')
|
||||
ram_free.set('total_mb', 'total_mb')
|
||||
unit_by_mb = xmlutil.SubTemplateElement(ram_free, 'unit_by_mb',
|
||||
selector=get_units_by_mb)
|
||||
unit_by_mb.set('mb', 0)
|
||||
unit_by_mb.set('unit', 1)
|
||||
|
||||
disk_free = xmlutil.SubTemplateElement(capacity, 'disk_free',
|
||||
selector='disk_free')
|
||||
disk_free.set('total_mb', 'total_mb')
|
||||
unit_by_mb = xmlutil.SubTemplateElement(disk_free, 'unit_by_mb',
|
||||
selector=get_units_by_mb)
|
||||
unit_by_mb.set('mb', 0)
|
||||
unit_by_mb.set('unit', 1)
|
||||
|
||||
cell_nsmap = {None: wsgi.XMLNS_V10}
|
||||
|
||||
|
||||
class CellTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('cell', selector='cell')
|
||||
make_cell(root)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=cell_nsmap)
|
||||
|
||||
|
||||
class CellsTemplate(xmlutil.TemplateBuilder):
|
||||
def construct(self):
|
||||
root = xmlutil.TemplateElement('cells')
|
||||
elem = xmlutil.SubTemplateElement(root, 'cell', selector='cells')
|
||||
make_cell(elem)
|
||||
return xmlutil.MasterTemplate(root, 1, nsmap=cell_nsmap)
|
||||
|
||||
|
||||
class CellDeserializer(wsgi.XMLDeserializer):
|
||||
"""Deserializer to handle xml-formatted cell create requests."""
|
||||
|
||||
def _extract_capabilities(self, cap_node):
|
||||
caps = {}
|
||||
for cap in cap_node.childNodes:
|
||||
cap_name = cap.tagName
|
||||
caps[cap_name] = self.extract_text(cap)
|
||||
return caps
|
||||
|
||||
def _extract_cell(self, node):
|
||||
cell = {}
|
||||
cell_node = self.find_first_child_named(node, 'cell')
|
||||
|
||||
extract_fns = {'capabilities': self._extract_capabilities}
|
||||
|
||||
for child in cell_node.childNodes:
|
||||
name = child.tagName
|
||||
extract_fn = extract_fns.get(name, self.extract_text)
|
||||
cell[name] = extract_fn(child)
|
||||
return cell
|
||||
|
||||
def default(self, string):
|
||||
"""Deserialize an xml-formatted cell create request."""
|
||||
node = xmlutil.safe_minidom_parse_string(string)
|
||||
|
||||
return {'body': {'cell': self._extract_cell(node)}}
|
||||
|
||||
|
||||
def _filter_keys(item, keys):
|
||||
"""
|
||||
Filters all model attributes except for keys
|
||||
item is a dict
|
||||
|
||||
"""
|
||||
return dict((k, v) for k, v in item.iteritems() if k in keys)
|
||||
|
||||
|
||||
def _scrub_cell(cell, detail=False):
|
||||
keys = ['name', 'username', 'rpc_host', 'rpc_port']
|
||||
if detail:
|
||||
keys.append('capabilities')
|
||||
|
||||
cell_info = _filter_keys(cell, keys)
|
||||
cell_info['type'] = 'parent' if cell['is_parent'] else 'child'
|
||||
return cell_info
|
||||
|
||||
|
||||
class Controller(object):
|
||||
"""Controller for Cell resources."""
|
||||
|
||||
def __init__(self, ext_mgr):
|
||||
self.compute_api = compute.API()
|
||||
self.cells_rpcapi = cells_rpcapi.CellsAPI()
|
||||
self.ext_mgr = ext_mgr
|
||||
|
||||
def _get_cells(self, ctxt, req, detail=False):
|
||||
"""Return all cells."""
|
||||
# Ask the CellsManager for the most recent data
|
||||
items = self.cells_rpcapi.get_cell_info_for_neighbors(ctxt)
|
||||
items = common.limited(items, req)
|
||||
items = [_scrub_cell(item, detail=detail) for item in items]
|
||||
return dict(cells=items)
|
||||
|
||||
@wsgi.serializers(xml=CellsTemplate)
|
||||
def index(self, req):
|
||||
"""Return all cells in brief."""
|
||||
ctxt = req.environ['nova.context']
|
||||
authorize(ctxt)
|
||||
return self._get_cells(ctxt, req)
|
||||
|
||||
@wsgi.serializers(xml=CellsTemplate)
|
||||
def detail(self, req):
|
||||
"""Return all cells in detail."""
|
||||
ctxt = req.environ['nova.context']
|
||||
authorize(ctxt)
|
||||
return self._get_cells(ctxt, req, detail=True)
|
||||
|
||||
@wsgi.serializers(xml=CellTemplate)
|
||||
def info(self, req):
|
||||
"""Return name and capabilities for this cell."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
cell_capabs = {}
|
||||
my_caps = CONF.cells.capabilities
|
||||
for cap in my_caps:
|
||||
key, value = cap.split('=')
|
||||
cell_capabs[key] = value
|
||||
cell = {'name': CONF.cells.name,
|
||||
'type': 'self',
|
||||
'rpc_host': None,
|
||||
'rpc_port': 0,
|
||||
'username': None,
|
||||
'capabilities': cell_capabs}
|
||||
return dict(cell=cell)
|
||||
|
||||
@wsgi.serializers(xml=CellTemplate)
|
||||
def capacities(self, req, id=None):
|
||||
"""Return capacities for a given cell or all cells."""
|
||||
# TODO(kaushikc): return capacities as a part of cell info and
|
||||
# cells detail calls in v3, along with capabilities
|
||||
if not self.ext_mgr.is_loaded('os-cell-capacities'):
|
||||
raise exc.HTTPNotFound()
|
||||
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
try:
|
||||
capacities = self.cells_rpcapi.get_capacities(context,
|
||||
cell_name=id)
|
||||
except exception.CellNotFound:
|
||||
msg = (_("Cell %(id)s not found.") % {'id': id})
|
||||
raise exc.HTTPNotFound(explanation=msg)
|
||||
|
||||
return dict(cell={"capacities": capacities})
|
||||
|
||||
@wsgi.serializers(xml=CellTemplate)
|
||||
def show(self, req, id):
|
||||
"""Return data about the given cell name. 'id' is a cell name."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
try:
|
||||
cell = db.cell_get(context, id)
|
||||
except exception.CellNotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
return dict(cell=_scrub_cell(cell))
|
||||
|
||||
def delete(self, req, id):
|
||||
"""Delete a child or parent cell entry. 'id' is a cell name."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
num_deleted = db.cell_delete(context, id)
|
||||
if num_deleted == 0:
|
||||
raise exc.HTTPNotFound()
|
||||
return {}
|
||||
|
||||
def _validate_cell_name(self, cell_name):
|
||||
"""Validate cell name is not empty and doesn't contain '!' or '.'."""
|
||||
if not cell_name:
|
||||
msg = _("Cell name cannot be empty")
|
||||
LOG.error(msg)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
if '!' in cell_name or '.' in cell_name:
|
||||
msg = _("Cell name cannot contain '!' or '.'")
|
||||
LOG.error(msg)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
def _validate_cell_type(self, cell_type):
|
||||
"""Validate cell_type is 'parent' or 'child'."""
|
||||
if cell_type not in ['parent', 'child']:
|
||||
msg = _("Cell type must be 'parent' or 'child'")
|
||||
LOG.error(msg)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
|
||||
def _convert_cell_type(self, cell):
|
||||
"""Convert cell['type'] to is_parent boolean."""
|
||||
if 'type' in cell:
|
||||
self._validate_cell_type(cell['type'])
|
||||
cell['is_parent'] = cell['type'] == 'parent'
|
||||
del cell['type']
|
||||
else:
|
||||
cell['is_parent'] = False
|
||||
|
||||
@wsgi.serializers(xml=CellTemplate)
|
||||
@wsgi.deserializers(xml=CellDeserializer)
|
||||
def create(self, req, body):
|
||||
"""Create a child cell entry."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
if 'cell' not in body:
|
||||
msg = _("No cell information in request")
|
||||
LOG.error(msg)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
cell = body['cell']
|
||||
if 'name' not in cell:
|
||||
msg = _("No cell name in request")
|
||||
LOG.error(msg)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
self._validate_cell_name(cell['name'])
|
||||
self._convert_cell_type(cell)
|
||||
cell = db.cell_create(context, cell)
|
||||
return dict(cell=_scrub_cell(cell))
|
||||
|
||||
@wsgi.serializers(xml=CellTemplate)
|
||||
@wsgi.deserializers(xml=CellDeserializer)
|
||||
def update(self, req, id, body):
|
||||
"""Update a child cell entry. 'id' is the cell name to update."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
if 'cell' not in body:
|
||||
msg = _("No cell information in request")
|
||||
LOG.error(msg)
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
cell = body['cell']
|
||||
cell.pop('id', None)
|
||||
if 'name' in cell:
|
||||
self._validate_cell_name(cell['name'])
|
||||
self._convert_cell_type(cell)
|
||||
try:
|
||||
cell = db.cell_update(context, id, cell)
|
||||
except exception.CellNotFound:
|
||||
raise exc.HTTPNotFound()
|
||||
return dict(cell=_scrub_cell(cell))
|
||||
|
||||
def sync_instances(self, req, body):
|
||||
"""Tell all cells to sync instance info."""
|
||||
context = req.environ['nova.context']
|
||||
authorize(context)
|
||||
project_id = body.pop('project_id', None)
|
||||
deleted = body.pop('deleted', False)
|
||||
updated_since = body.pop('updated_since', None)
|
||||
if body:
|
||||
msg = _("Only 'updated_since' and 'project_id' are understood.")
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
if updated_since:
|
||||
try:
|
||||
timeutils.parse_isotime(updated_since)
|
||||
except ValueError:
|
||||
msg = _('Invalid changes-since value')
|
||||
raise exc.HTTPBadRequest(explanation=msg)
|
||||
self.cells_rpcapi.sync_instances(context, project_id=project_id,
|
||||
updated_since=updated_since, deleted=deleted)
|
||||
|
||||
|
||||
class Cells(extensions.ExtensionDescriptor):
|
||||
"""Enables cells-related functionality such as adding neighbor cells,
|
||||
listing neighbor cells, and getting the capabilities of the local cell.
|
||||
"""
|
||||
|
||||
name = "Cells"
|
||||
alias = "os-cells"
|
||||
namespace = "http://docs.openstack.org/compute/ext/cells/api/v1.1"
|
||||
updated = "2013-05-14T00:00:00+00:00"
|
||||
|
||||
def get_resources(self):
|
||||
coll_actions = {
|
||||
'detail': 'GET',
|
||||
'info': 'GET',
|
||||
'sync_instances': 'POST',
|
||||
'capacities': 'GET',
|
||||
}
|
||||
memb_actions = {
|
||||
'capacities': 'GET',
|
||||
}
|
||||
|
||||
res = extensions.ResourceExtension('os-cells',
|
||||
Controller(self.ext_mgr), collection_actions=coll_actions,
|
||||
member_actions=memb_actions)
|
||||
return [res]
|
||||
474
nova/tests/api/openstack/compute/plugins/v3/test_cells.py
Normal file
474
nova/tests/api/openstack/compute/plugins/v3/test_cells.py
Normal file
@@ -0,0 +1,474 @@
|
||||
# Copyright 2011-2012 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 copy
|
||||
|
||||
from lxml import etree
|
||||
from webob import exc
|
||||
|
||||
from nova.api.openstack.compute.contrib import cells as cells_ext
|
||||
from nova.api.openstack import extensions
|
||||
from nova.api.openstack import xmlutil
|
||||
from nova.cells import rpcapi as cells_rpcapi
|
||||
from nova import context
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.openstack.common import timeutils
|
||||
from nova import test
|
||||
from nova.tests.api.openstack import fakes
|
||||
from nova.tests import utils
|
||||
|
||||
|
||||
FAKE_CELLS = [
|
||||
dict(id=1, name='cell1', username='bob', is_parent=True,
|
||||
weight_scale=1.0, weight_offset=0.0,
|
||||
rpc_host='r1.example.org', password='xxxx'),
|
||||
dict(id=2, name='cell2', username='alice', is_parent=False,
|
||||
weight_scale=1.0, weight_offset=0.0,
|
||||
rpc_host='r2.example.org', password='qwerty')]
|
||||
|
||||
|
||||
FAKE_CAPABILITIES = [
|
||||
{'cap1': '0,1', 'cap2': '2,3'},
|
||||
{'cap3': '4,5', 'cap4': '5,6'}]
|
||||
|
||||
|
||||
def fake_db_cell_get(context, cell_name):
|
||||
for cell in FAKE_CELLS:
|
||||
if cell_name == cell['name']:
|
||||
return cell
|
||||
else:
|
||||
raise exception.CellNotFound(cell_name=cell_name)
|
||||
|
||||
|
||||
def fake_db_cell_create(context, values):
|
||||
cell = dict(id=1)
|
||||
cell.update(values)
|
||||
return cell
|
||||
|
||||
|
||||
def fake_db_cell_update(context, cell_id, values):
|
||||
cell = fake_db_cell_get(context, cell_id)
|
||||
cell.update(values)
|
||||
return cell
|
||||
|
||||
|
||||
def fake_cells_api_get_all_cell_info(*args):
|
||||
cells = copy.deepcopy(FAKE_CELLS)
|
||||
del cells[0]['password']
|
||||
del cells[1]['password']
|
||||
for i, cell in enumerate(cells):
|
||||
cell['capabilities'] = FAKE_CAPABILITIES[i]
|
||||
return cells
|
||||
|
||||
|
||||
def fake_db_cell_get_all(context):
|
||||
return FAKE_CELLS
|
||||
|
||||
|
||||
class CellsTest(test.TestCase):
|
||||
def setUp(self):
|
||||
super(CellsTest, self).setUp()
|
||||
self.stubs.Set(db, 'cell_get', fake_db_cell_get)
|
||||
self.stubs.Set(db, 'cell_get_all', fake_db_cell_get_all)
|
||||
self.stubs.Set(db, 'cell_update', fake_db_cell_update)
|
||||
self.stubs.Set(db, 'cell_create', fake_db_cell_create)
|
||||
self.stubs.Set(cells_rpcapi.CellsAPI, 'get_cell_info_for_neighbors',
|
||||
fake_cells_api_get_all_cell_info)
|
||||
|
||||
self.ext_mgr = self.mox.CreateMock(extensions.ExtensionManager)
|
||||
self.controller = cells_ext.Controller(self.ext_mgr)
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
def _get_request(self, resource):
|
||||
return fakes.HTTPRequest.blank('/v2/fake/' + resource)
|
||||
|
||||
def test_index(self):
|
||||
req = self._get_request("cells")
|
||||
res_dict = self.controller.index(req)
|
||||
|
||||
self.assertEqual(len(res_dict['cells']), 2)
|
||||
for i, cell in enumerate(res_dict['cells']):
|
||||
self.assertEqual(cell['name'], FAKE_CELLS[i]['name'])
|
||||
self.assertNotIn('capabilitiles', cell)
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_detail(self):
|
||||
req = self._get_request("cells/detail")
|
||||
res_dict = self.controller.detail(req)
|
||||
|
||||
self.assertEqual(len(res_dict['cells']), 2)
|
||||
for i, cell in enumerate(res_dict['cells']):
|
||||
self.assertEqual(cell['name'], FAKE_CELLS[i]['name'])
|
||||
self.assertEqual(cell['capabilities'], FAKE_CAPABILITIES[i])
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_show_bogus_cell_raises(self):
|
||||
req = self._get_request("cells/bogus")
|
||||
self.assertRaises(exc.HTTPNotFound, self.controller.show, req, 'bogus')
|
||||
|
||||
def test_get_cell_by_name(self):
|
||||
req = self._get_request("cells/cell1")
|
||||
res_dict = self.controller.show(req, 'cell1')
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'cell1')
|
||||
self.assertEqual(cell['rpc_host'], 'r1.example.org')
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_cell_delete(self):
|
||||
call_info = {'delete_called': 0}
|
||||
|
||||
def fake_db_cell_delete(context, cell_name):
|
||||
self.assertEqual(cell_name, 'cell999')
|
||||
call_info['delete_called'] += 1
|
||||
|
||||
self.stubs.Set(db, 'cell_delete', fake_db_cell_delete)
|
||||
|
||||
req = self._get_request("cells/cell999")
|
||||
self.controller.delete(req, 'cell999')
|
||||
self.assertEqual(call_info['delete_called'], 1)
|
||||
|
||||
def test_delete_bogus_cell_raises(self):
|
||||
req = self._get_request("cells/cell999")
|
||||
req.environ['nova.context'] = self.context
|
||||
self.assertRaises(exc.HTTPNotFound, self.controller.delete, req,
|
||||
'cell999')
|
||||
|
||||
def test_cell_create_parent(self):
|
||||
body = {'cell': {'name': 'meow',
|
||||
'username': 'fred',
|
||||
'password': 'fubar',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent',
|
||||
# Also test this is ignored/stripped
|
||||
'is_parent': False}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
res_dict = self.controller.create(req, body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'meow')
|
||||
self.assertEqual(cell['username'], 'fred')
|
||||
self.assertEqual(cell['rpc_host'], 'r3.example.org')
|
||||
self.assertEqual(cell['type'], 'parent')
|
||||
self.assertNotIn('password', cell)
|
||||
self.assertNotIn('is_parent', cell)
|
||||
|
||||
def test_cell_create_child(self):
|
||||
body = {'cell': {'name': 'meow',
|
||||
'username': 'fred',
|
||||
'password': 'fubar',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'child'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
res_dict = self.controller.create(req, body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'meow')
|
||||
self.assertEqual(cell['username'], 'fred')
|
||||
self.assertEqual(cell['rpc_host'], 'r3.example.org')
|
||||
self.assertEqual(cell['type'], 'child')
|
||||
self.assertNotIn('password', cell)
|
||||
self.assertNotIn('is_parent', cell)
|
||||
|
||||
def test_cell_create_no_name_raises(self):
|
||||
body = {'cell': {'username': 'moocow',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
|
||||
def test_cell_create_name_empty_string_raises(self):
|
||||
body = {'cell': {'name': '',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
|
||||
def test_cell_create_name_with_bang_raises(self):
|
||||
body = {'cell': {'name': 'moo!cow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
|
||||
def test_cell_create_name_with_dot_raises(self):
|
||||
body = {'cell': {'name': 'moo.cow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'parent'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
|
||||
def test_cell_create_name_with_invalid_type_raises(self):
|
||||
body = {'cell': {'name': 'moocow',
|
||||
'username': 'fred',
|
||||
'password': 'secret',
|
||||
'rpc_host': 'r3.example.org',
|
||||
'type': 'invalid'}}
|
||||
|
||||
req = self._get_request("cells")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.create, req, body)
|
||||
|
||||
def test_cell_update(self):
|
||||
body = {'cell': {'username': 'zeb',
|
||||
'password': 'sneaky'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
res_dict = self.controller.update(req, 'cell1', body)
|
||||
cell = res_dict['cell']
|
||||
|
||||
self.assertEqual(cell['name'], 'cell1')
|
||||
self.assertEqual(cell['rpc_host'], FAKE_CELLS[0]['rpc_host'])
|
||||
self.assertEqual(cell['username'], 'zeb')
|
||||
self.assertNotIn('password', cell)
|
||||
|
||||
def test_cell_update_empty_name_raises(self):
|
||||
body = {'cell': {'name': '',
|
||||
'username': 'zeb',
|
||||
'password': 'sneaky'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.update, req, 'cell1', body)
|
||||
|
||||
def test_cell_update_invalid_type_raises(self):
|
||||
body = {'cell': {'username': 'zeb',
|
||||
'type': 'invalid',
|
||||
'password': 'sneaky'}}
|
||||
|
||||
req = self._get_request("cells/cell1")
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.update, req, 'cell1', body)
|
||||
|
||||
def test_cell_info(self):
|
||||
caps = ['cap1=a;b', 'cap2=c;d']
|
||||
self.flags(name='darksecret', capabilities=caps, group='cells')
|
||||
|
||||
req = self._get_request("cells/info")
|
||||
res_dict = self.controller.info(req)
|
||||
cell = res_dict['cell']
|
||||
cell_caps = cell['capabilities']
|
||||
|
||||
self.assertEqual(cell['name'], 'darksecret')
|
||||
self.assertEqual(cell_caps['cap1'], 'a;b')
|
||||
self.assertEqual(cell_caps['cap2'], 'c;d')
|
||||
|
||||
def test_show_capacities(self):
|
||||
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
|
||||
self.mox.StubOutWithMock(self.controller.cells_rpcapi,
|
||||
'get_capacities')
|
||||
response = {"ram_free":
|
||||
{"units_by_mb": {"8192": 0, "512": 13,
|
||||
"4096": 1, "2048": 3, "16384": 0},
|
||||
"total_mb": 7680},
|
||||
"disk_free":
|
||||
{"units_by_mb": {"81920": 11, "20480": 46,
|
||||
"40960": 23, "163840": 5, "0": 0},
|
||||
"total_mb": 1052672}
|
||||
}
|
||||
self.controller.cells_rpcapi.\
|
||||
get_capacities(self.context, cell_name=None).AndReturn(response)
|
||||
self.mox.ReplayAll()
|
||||
req = self._get_request("cells/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
res_dict = self.controller.capacities(req)
|
||||
self.assertEqual(response, res_dict['cell']['capacities'])
|
||||
|
||||
def test_show_capacity_fails_with_non_admin_context(self):
|
||||
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
|
||||
rules = {"compute_extension:cells": "is_admin:true"}
|
||||
self.policy.set_rules(rules)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
req = self._get_request("cells/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
req.environ["nova.context"].is_admin = False
|
||||
self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.capacities, req)
|
||||
|
||||
def test_show_capacities_for_invalid_cell(self):
|
||||
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
|
||||
self.mox.StubOutWithMock(self.controller.cells_rpcapi,
|
||||
'get_capacities')
|
||||
self.controller.cells_rpcapi. \
|
||||
get_capacities(self.context, cell_name="invalid_cell").AndRaise(
|
||||
exception.CellNotFound(cell_name="invalid_cell"))
|
||||
self.mox.ReplayAll()
|
||||
req = self._get_request("cells/invalid_cell/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
self.assertRaises(exc.HTTPNotFound,
|
||||
self.controller.capacities, req, "invalid_cell")
|
||||
|
||||
def test_show_capacities_for_cell(self):
|
||||
self.ext_mgr.is_loaded('os-cell-capacities').AndReturn(True)
|
||||
self.mox.StubOutWithMock(self.controller.cells_rpcapi,
|
||||
'get_capacities')
|
||||
response = {"ram_free":
|
||||
{"units_by_mb": {"8192": 0, "512": 13,
|
||||
"4096": 1, "2048": 3, "16384": 0},
|
||||
"total_mb": 7680},
|
||||
"disk_free":
|
||||
{"units_by_mb": {"81920": 11, "20480": 46,
|
||||
"40960": 23, "163840": 5, "0": 0},
|
||||
"total_mb": 1052672}
|
||||
}
|
||||
self.controller.cells_rpcapi.\
|
||||
get_capacities(self.context, cell_name='cell_name').\
|
||||
AndReturn(response)
|
||||
self.mox.ReplayAll()
|
||||
req = self._get_request("cells/capacities")
|
||||
req.environ["nova.context"] = self.context
|
||||
res_dict = self.controller.capacities(req, 'cell_name')
|
||||
self.assertEqual(response, res_dict['cell']['capacities'])
|
||||
|
||||
def test_sync_instances(self):
|
||||
call_info = {}
|
||||
|
||||
def sync_instances(self, context, **kwargs):
|
||||
call_info['project_id'] = kwargs.get('project_id')
|
||||
call_info['updated_since'] = kwargs.get('updated_since')
|
||||
|
||||
self.stubs.Set(cells_rpcapi.CellsAPI, 'sync_instances', sync_instances)
|
||||
|
||||
req = self._get_request("cells/sync_instances")
|
||||
body = {}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertEqual(call_info['project_id'], None)
|
||||
self.assertEqual(call_info['updated_since'], None)
|
||||
|
||||
body = {'project_id': 'test-project'}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertEqual(call_info['project_id'], 'test-project')
|
||||
self.assertEqual(call_info['updated_since'], None)
|
||||
|
||||
expected = timeutils.utcnow().isoformat()
|
||||
if not expected.endswith("+00:00"):
|
||||
expected += "+00:00"
|
||||
|
||||
body = {'updated_since': expected}
|
||||
self.controller.sync_instances(req, body=body)
|
||||
self.assertEqual(call_info['project_id'], None)
|
||||
self.assertEqual(call_info['updated_since'], expected)
|
||||
|
||||
body = {'updated_since': 'skjdfkjsdkf'}
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.sync_instances, req, body=body)
|
||||
|
||||
body = {'foo': 'meow'}
|
||||
self.assertRaises(exc.HTTPBadRequest,
|
||||
self.controller.sync_instances, req, body=body)
|
||||
|
||||
|
||||
class TestCellsXMLSerializer(test.TestCase):
|
||||
def test_multiple_cells(self):
|
||||
fixture = {'cells': fake_cells_api_get_all_cell_info()}
|
||||
|
||||
serializer = cells_ext.CellsTemplate()
|
||||
output = serializer.serialize(fixture)
|
||||
res_tree = etree.XML(output)
|
||||
|
||||
self.assertEqual(res_tree.tag, '{%s}cells' % xmlutil.XMLNS_V10)
|
||||
self.assertEqual(len(res_tree), 2)
|
||||
self.assertEqual(res_tree[0].tag, '{%s}cell' % xmlutil.XMLNS_V10)
|
||||
self.assertEqual(res_tree[1].tag, '{%s}cell' % xmlutil.XMLNS_V10)
|
||||
|
||||
def test_single_cell_with_caps(self):
|
||||
cell = {'id': 1,
|
||||
'name': 'darksecret',
|
||||
'username': 'meow',
|
||||
'capabilities': {'cap1': 'a;b',
|
||||
'cap2': 'c;d'}}
|
||||
fixture = {'cell': cell}
|
||||
|
||||
serializer = cells_ext.CellTemplate()
|
||||
output = serializer.serialize(fixture)
|
||||
res_tree = etree.XML(output)
|
||||
|
||||
self.assertEqual(res_tree.tag, '{%s}cell' % xmlutil.XMLNS_V10)
|
||||
self.assertEqual(res_tree.get('name'), 'darksecret')
|
||||
self.assertEqual(res_tree.get('username'), 'meow')
|
||||
self.assertEqual(res_tree.get('password'), None)
|
||||
self.assertEqual(len(res_tree), 1)
|
||||
|
||||
child = res_tree[0]
|
||||
self.assertEqual(child.tag,
|
||||
'{%s}capabilities' % xmlutil.XMLNS_V10)
|
||||
for elem in child:
|
||||
self.assertIn(elem.tag, ('{%s}cap1' % xmlutil.XMLNS_V10,
|
||||
'{%s}cap2' % xmlutil.XMLNS_V10))
|
||||
if elem.tag == '{%s}cap1' % xmlutil.XMLNS_V10:
|
||||
self.assertEqual(elem.text, 'a;b')
|
||||
elif elem.tag == '{%s}cap2' % xmlutil.XMLNS_V10:
|
||||
self.assertEqual(elem.text, 'c;d')
|
||||
|
||||
def test_single_cell_without_caps(self):
|
||||
cell = {'id': 1,
|
||||
'username': 'woof',
|
||||
'name': 'darksecret'}
|
||||
fixture = {'cell': cell}
|
||||
|
||||
serializer = cells_ext.CellTemplate()
|
||||
output = serializer.serialize(fixture)
|
||||
res_tree = etree.XML(output)
|
||||
|
||||
self.assertEqual(res_tree.tag, '{%s}cell' % xmlutil.XMLNS_V10)
|
||||
self.assertEqual(res_tree.get('name'), 'darksecret')
|
||||
self.assertEqual(res_tree.get('username'), 'woof')
|
||||
self.assertEqual(res_tree.get('password'), None)
|
||||
self.assertEqual(len(res_tree), 0)
|
||||
|
||||
|
||||
class TestCellsXMLDeserializer(test.TestCase):
|
||||
def test_cell_deserializer(self):
|
||||
caps_dict = {'cap1': 'a;b',
|
||||
'cap2': 'c;d'}
|
||||
caps_xml = ("<capabilities><cap1>a;b</cap1>"
|
||||
"<cap2>c;d</cap2></capabilities>")
|
||||
expected = {'cell': {'name': 'testcell1',
|
||||
'type': 'child',
|
||||
'rpc_host': 'localhost',
|
||||
'capabilities': caps_dict}}
|
||||
intext = ("<?xml version='1.0' encoding='UTF-8'?>\n"
|
||||
"<cell><name>testcell1</name><type>child</type>"
|
||||
"<rpc_host>localhost</rpc_host>"
|
||||
"%s</cell>") % caps_xml
|
||||
deserializer = cells_ext.CellDeserializer()
|
||||
result = deserializer.deserialize(intext)
|
||||
self.assertEqual(dict(body=expected), result)
|
||||
|
||||
def test_with_corrupt_xml(self):
|
||||
deserializer = cells_ext.CellDeserializer()
|
||||
self.assertRaises(
|
||||
exception.MalformedRequestBody,
|
||||
deserializer.deserialize,
|
||||
utils.killer_xml_body())
|
||||
Reference in New Issue
Block a user