diff --git a/etc/nova/cells.json b/etc/nova/cells.json new file mode 100644 index 000000000000..2c8016e27cc4 --- /dev/null +++ b/etc/nova/cells.json @@ -0,0 +1,26 @@ +{ + "parent": { + "name": "parent", + "api_url": "http://api.example.com:8774", + "transport_url": "rabbit://rabbit.example.com", + "weight_offset": 0.0, + "weight_scale": 1.0, + "is_parent": true, + }, + "cell1": { + "name": "cell1", + "api_url": "http://api.example.com:8774", + "transport_url": "rabbit://rabbit1.example.com", + "weight_offset": 0.0, + "weight_scale": 1.0, + "is_parent": false, + }, + "cell2": { + "name": "cell2", + "api_url": "http://api.example.com:8774", + "transport_url": "rabbit://rabbit2.example.com", + "weight_offset": 0.0, + "weight_scale": 1.0, + "is_parent": false, + } +} diff --git a/nova/api/openstack/compute/contrib/cells.py b/nova/api/openstack/compute/contrib/cells.py index dfc1fadf2123..0d0c55749baf 100644 --- a/nova/api/openstack/compute/contrib/cells.py +++ b/nova/api/openstack/compute/contrib/cells.py @@ -27,7 +27,6 @@ from nova.api.openstack import xmlutil from nova.cells import rpc_driver 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 @@ -251,7 +250,7 @@ class Controller(object): context = req.environ['nova.context'] authorize(context) try: - cell = db.cell_get(context, id) + cell = self.cells_rpcapi.cell_get(context, id) except exception.CellNotFound: raise exc.HTTPNotFound() return dict(cell=_scrub_cell(cell)) @@ -260,7 +259,10 @@ class Controller(object): """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) + try: + num_deleted = self.cells_rpcapi.cell_delete(context, id) + except exception.CellsUpdateUnsupported as e: + raise exc.HTTPForbidden(explanation=e.format_message()) if num_deleted == 0: raise exc.HTTPNotFound() return {} @@ -342,7 +344,10 @@ class Controller(object): raise exc.HTTPBadRequest(explanation=msg) self._validate_cell_name(cell['name']) self._normalize_cell(cell) - cell = db.cell_create(context, cell) + try: + cell = self.cells_rpcapi.cell_create(context, cell) + except exception.CellsUpdateUnsupported as e: + raise exc.HTTPForbidden(explanation=e.format_message()) return dict(cell=_scrub_cell(cell)) @wsgi.serializers(xml=CellTemplate) @@ -366,14 +371,16 @@ class Controller(object): # operation is administrative in nature, and # will be going away in the future, I don't see # it as much of a problem... - existing = db.cell_get(context, id) + existing = self.cells_rpcapi.cell_get(context, id) except exception.CellNotFound: raise exc.HTTPNotFound() self._normalize_cell(cell, existing) try: - cell = db.cell_update(context, id, cell) + cell = self.cells_rpcapi.cell_update(context, id, cell) except exception.CellNotFound: raise exc.HTTPNotFound() + except exception.CellsUpdateUnsupported as e: + raise exc.HTTPForbidden(explanation=e.format_message()) return dict(cell=_scrub_cell(cell)) def sync_instances(self, req, body): diff --git a/nova/api/openstack/compute/plugins/v3/cells.py b/nova/api/openstack/compute/plugins/v3/cells.py index 282f9e1200d3..b1308a15073b 100644 --- a/nova/api/openstack/compute/plugins/v3/cells.py +++ b/nova/api/openstack/compute/plugins/v3/cells.py @@ -27,7 +27,6 @@ from nova.api.openstack import xmlutil from nova.cells import rpc_driver 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 @@ -248,7 +247,7 @@ class CellsController(object): context = req.environ['nova.context'] authorize(context) try: - cell = db.cell_get(context, id) + cell = self.cells_rpcapi.cell_get(context, id) except exception.CellNotFound: raise exc.HTTPNotFound() return dict(cell=_scrub_cell(cell)) @@ -257,7 +256,10 @@ class CellsController(object): """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) + try: + num_deleted = self.cells_rpcapi.cell_delete(context, id) + except exception.CellsUpdateUnsupported as e: + raise exc.HTTPForbidden(explanation=e.format_message()) if num_deleted == 0: raise exc.HTTPNotFound() return {} @@ -339,7 +341,10 @@ class CellsController(object): raise exc.HTTPBadRequest(explanation=msg) self._validate_cell_name(cell['name']) self._normalize_cell(cell) - cell = db.cell_create(context, cell) + try: + cell = self.cells_rpcapi.cell_create(context, cell) + except exception.CellsUpdateUnsupported as e: + raise exc.HTTPForbidden(explanation=e.format_message()) return dict(cell=_scrub_cell(cell)) @wsgi.serializers(xml=CellTemplate) @@ -363,14 +368,16 @@ class CellsController(object): # operation is administrative in nature, and # will be going away in the future, I don't see # it as much of a problem... - existing = db.cell_get(context, id) + existing = self.cells_rpcapi.cell_get(context, id) except exception.CellNotFound: raise exc.HTTPNotFound() self._normalize_cell(cell, existing) try: - cell = db.cell_update(context, id, cell) + cell = self.cells_rpcapi.cell_update(context, id, cell) except exception.CellNotFound: raise exc.HTTPNotFound() + except exception.CellsUpdateUnsupported as e: + raise exc.HTTPForbidden(explanation=e.format_message()) return dict(cell=_scrub_cell(cell)) def sync_instances(self, req, body): diff --git a/nova/cells/manager.py b/nova/cells/manager.py index 7fede058e429..6ca6bb31447e 100644 --- a/nova/cells/manager.py +++ b/nova/cells/manager.py @@ -65,7 +65,7 @@ class CellsManager(manager.Manager): Scheduling requests get passed to the scheduler class. """ - RPC_API_VERSION = '1.12' + RPC_API_VERSION = '1.13' def __init__(self, *args, **kwargs): # Mostly for tests. @@ -428,3 +428,15 @@ class CellsManager(manager.Manager): do_cast=do_cast) if not do_cast: return response.value_or_raise() + + def cell_create(self, ctxt, values): + return self.state_manager.cell_create(ctxt, values) + + def cell_update(self, ctxt, cell_name, values): + return self.state_manager.cell_update(ctxt, cell_name, values) + + def cell_delete(self, ctxt, cell_name): + return self.state_manager.cell_delete(ctxt, cell_name) + + def cell_get(self, ctxt, cell_name): + return self.state_manager.cell_get(ctxt, cell_name) diff --git a/nova/cells/rpcapi.py b/nova/cells/rpcapi.py index db28bc0ef07c..8f9e3b3c75a7 100644 --- a/nova/cells/rpcapi.py +++ b/nova/cells/rpcapi.py @@ -68,6 +68,8 @@ class CellsAPI(rpc_proxy.RpcProxy): 1.10 - Adds bdm_update_or_create_at_top(), and bdm_destroy_at_top() 1.11 - Adds get_migrations() 1.12 - Adds instance_start() and instance_stop() + 1.13 - Adds cell_create(), cell_update(), cell_delete(), and + cell_get() ''' BASE_RPC_API_VERSION = '1.0' @@ -382,3 +384,25 @@ class CellsAPI(rpc_proxy.RpcProxy): self.make_msg('stop_instance', instance=instance, do_cast=do_cast), version='1.12') + + def cell_create(self, ctxt, values): + return self.call(ctxt, + self.make_msg('cell_create', values=values), + version='1.13') + + def cell_update(self, ctxt, cell_name, values): + return self.call(ctxt, + self.make_msg('cell_update', + cell_name=cell_name, + values=values), + version='1.13') + + def cell_delete(self, ctxt, cell_name): + return self.call(ctxt, + self.make_msg('cell_delete', cell_name=cell_name), + version='1.13') + + def cell_get(self, ctxt, cell_name): + return self.call(ctxt, + self.make_msg('cell_get', cell_name=cell_name), + version='1.13') diff --git a/nova/cells/state.py b/nova/cells/state.py index 0f6562d14959..c1f2a4a02319 100644 --- a/nova/cells/state.py +++ b/nova/cells/state.py @@ -26,6 +26,8 @@ from nova.cells import rpc_driver from nova import context from nova.db import base from nova import exception +from nova.openstack.common import fileutils +from nova.openstack.common import jsonutils from nova.openstack.common import log as logging from nova.openstack.common import timeutils from nova import utils @@ -34,6 +36,11 @@ cell_state_manager_opts = [ cfg.IntOpt('db_check_interval', default=60, help='Seconds between getting fresh cell info from db.'), + cfg.StrOpt('cells_config', + default=None, + help='Configuration file from which to read cells ' + 'configuration. If given, overrides reading cells ' + 'from the database.'), ] @@ -107,19 +114,48 @@ class CellState(object): return "Cell '%s' (%s)" % (self.name, me) -def sync_from_db(f): +def sync_before(f): """Use as a decorator to wrap methods that use cell information to make sure they sync the latest information from the DB periodically. """ @functools.wraps(f) def wrapper(self, *args, **kwargs): - if self._time_to_sync(): - self._cell_db_sync() + self._cell_data_sync() return f(self, *args, **kwargs) return wrapper +def sync_after(f): + """Use as a decorator to wrap methods that update cell information + in the database to make sure the data is synchronized immediately. + """ + @functools.wraps(f) + def wrapper(self, *args, **kwargs): + result = f(self, *args, **kwargs) + self._cell_data_sync(force=True) + return result + return wrapper + + +_unset = object() + + class CellStateManager(base.Base): + def __new__(cls, cell_state_cls=None, cells_config=_unset): + if cls is not CellStateManager: + return super(CellStateManager, cls).__new__(cls) + + if cells_config is _unset: + cells_config = CONF.cells.cells_config + + if cells_config: + config_path = CONF.find_file(cells_config) + if not config_path: + raise cfg.ConfigFilesNotFoundError(path=config_path) + return CellStateManagerFile(cell_state_cls, config_path) + + return CellStateManagerDB(cell_state_cls) + def __init__(self, cell_state_cls=None): super(CellStateManager, self).__init__() if not cell_state_cls: @@ -129,7 +165,9 @@ class CellStateManager(base.Base): self.parent_cells = {} self.child_cells = {} self.last_cell_db_check = datetime.datetime.min - self._cell_db_sync() + + self._cell_data_sync(force=True) + my_cell_capabs = {} for cap in CONF.cells.capabilities: name, value = cap.split('=', 1) @@ -140,11 +178,8 @@ class CellStateManager(base.Base): my_cell_capabs[name] = values self.my_cell_state.update_capabilities(my_cell_capabs) - def _refresh_cells_from_db(self, ctxt): + def _refresh_cells_from_dict(self, db_cells_dict): """Make our cell info map match the db.""" - # Add/update existing cells ... - db_cells = self.db.cell_get_all(ctxt) - db_cells_dict = dict([(cell['name'], cell) for cell in db_cells]) # Update current cells. Delete ones that disappeared for cells_dict in (self.parent_cells, self.child_cells): @@ -171,7 +206,7 @@ class CellStateManager(base.Base): diff = timeutils.utcnow() - self.last_cell_db_check return diff.seconds >= CONF.cells.db_check_interval - def _update_our_capacity(self, context): + def _update_our_capacity(self, ctxt=None): """Update our capacity in the self.my_cell_state CellState. This will add/update 2 entries in our CellState.capacities, @@ -208,11 +243,14 @@ class CellStateManager(base.Base): available per instance_type. """ + if not ctxt: + ctxt = context.get_admin_context() + reserve_level = CONF.cells.reserve_percent / 100.0 compute_hosts = {} def _get_compute_hosts(): - compute_nodes = self.db.compute_node_get_all(context) + compute_nodes = self.db.compute_node_get_all(ctxt) for compute in compute_nodes: service = compute['service'] if not service or service['disabled']: @@ -255,7 +293,7 @@ class CellStateManager(base.Base): ram_mb_free_units[str(memory_mb)] += ram_free_units disk_mb_free_units[str(disk_mb)] += disk_free_units - instance_types = self.db.instance_type_get_all(context) + instance_types = self.db.instance_type_get_all(ctxt) for compute_values in compute_hosts.values(): total_ram_mb_free += compute_values['free_ram_mb'] @@ -269,22 +307,7 @@ class CellStateManager(base.Base): 'units_by_mb': disk_mb_free_units}} self.my_cell_state.update_capacities(capacities) - @utils.synchronized('cell-db-sync') - def _cell_db_sync(self): - """Update status for all cells if it's time. Most calls to - this are from the check_for_update() decorator that checks - the time, but it checks outside of a lock. The duplicate - check here is to prevent multiple threads from pulling the - information simultaneously. - """ - if self._time_to_sync(): - LOG.debug(_("Updating cell cache from db.")) - self.last_cell_db_check = timeutils.utcnow() - ctxt = context.get_admin_context() - self._refresh_cells_from_db(ctxt) - self._update_our_capacity(ctxt) - - @sync_from_db + @sync_before def get_cell_info_for_neighbors(self): """Return cell information for all neighbor cells.""" cell_list = [cell.get_cell_info() @@ -293,30 +316,30 @@ class CellStateManager(base.Base): for cell in self.parent_cells.itervalues()]) return cell_list - @sync_from_db + @sync_before def get_my_state(self): """Return information for my (this) cell.""" return self.my_cell_state - @sync_from_db + @sync_before def get_child_cells(self): """Return list of child cell_infos.""" return self.child_cells.values() - @sync_from_db + @sync_before def get_parent_cells(self): """Return list of parent cell_infos.""" return self.parent_cells.values() - @sync_from_db + @sync_before def get_parent_cell(self, cell_name): return self.parent_cells.get(cell_name) - @sync_from_db + @sync_before def get_child_cell(self, cell_name): return self.child_cells.get(cell_name) - @sync_from_db + @sync_before def update_cell_capabilities(self, cell_name, capabilities): """Update capabilities for a cell.""" cell = self.child_cells.get(cell_name) @@ -332,7 +355,7 @@ class CellStateManager(base.Base): capabilities[capab_name] = set(values) cell.update_capabilities(capabilities) - @sync_from_db + @sync_before def update_cell_capacities(self, cell_name, capacities): """Update capacities for a cell.""" cell = self.child_cells.get(cell_name) @@ -345,7 +368,7 @@ class CellStateManager(base.Base): return cell.update_capacities(capacities) - @sync_from_db + @sync_before def get_our_capabilities(self, include_children=True): capabs = copy.deepcopy(self.my_cell_state.capabilities) if include_children: @@ -368,7 +391,7 @@ class CellStateManager(base.Base): target.setdefault(key, 0) target[key] += value - @sync_from_db + @sync_before def get_our_capacities(self, include_children=True): capacities = copy.deepcopy(self.my_cell_state.capacities) if include_children: @@ -376,10 +399,85 @@ class CellStateManager(base.Base): self._add_to_dict(capacities, cell.capacities) return capacities - @sync_from_db + @sync_before def get_capacities(self, cell_name=None): if not cell_name or cell_name == self.my_cell_state.name: return self.get_our_capacities() if cell_name in self.child_cells: return self.child_cells[cell_name].capacities raise exception.CellNotFound(cell_name=cell_name) + + @sync_before + def cell_get(self, ctxt, cell_name): + for cells_dict in (self.parent_cells, self.child_cells): + if cell_name in cells_dict: + return cells_dict[cell_name] + + raise exception.CellNotFound(cell_name=cell_name) + + +class CellStateManagerDB(CellStateManager): + @utils.synchronized('cell-db-sync') + def _cell_data_sync(self, force=False): + """ + Update cell status for all cells from the backing data store + when necessary. + + :param force: If True, cell status will be updated regardless + of whether it's time to do so. + """ + if force or self._time_to_sync(): + LOG.debug(_("Updating cell cache from db.")) + self.last_cell_db_check = timeutils.utcnow() + ctxt = context.get_admin_context() + db_cells = self.db.cell_get_all(ctxt) + db_cells_dict = dict((cell['name'], cell) for cell in db_cells) + self._refresh_cells_from_dict(db_cells_dict) + self._update_our_capacity(ctxt) + + @sync_after + def cell_create(self, ctxt, values): + return self.db.cell_create(ctxt, values) + + @sync_after + def cell_update(self, ctxt, cell_name, values): + return self.db.cell_update(ctxt, cell_name, values) + + @sync_after + def cell_delete(self, ctxt, cell_name): + return self.db.cell_delete(ctxt, cell_name) + + +class CellStateManagerFile(CellStateManager): + def __init__(self, cell_state_cls, cells_config_path): + self.cells_config_path = cells_config_path + super(CellStateManagerFile, self).__init__(cell_state_cls) + + def _cell_data_sync(self, force=False): + """ + Update cell status for all cells from the backing data store + when necessary. + + :param force: If True, cell status will be updated regardless + of whether it's time to do so. + """ + reloaded, data = fileutils.read_cached_file(self.cells_config_path, + force_reload=force) + + if reloaded: + LOG.debug(_("Updating cell cache from config file.")) + self.cells_config_data = jsonutils.loads(data) + self._refresh_cells_from_dict(self.cells_config_data) + + if force or self._time_to_sync(): + self.last_cell_db_check = timeutils.utcnow() + self._update_our_capacity() + + def cell_create(self, ctxt, values): + raise exception.CellsUpdateProhibited() + + def cell_update(self, ctxt, cell_name, values): + raise exception.CellsUpdateProhibited() + + def cell_delete(self, ctxt, cell_name): + raise exception.CellsUpdateProhibited() diff --git a/nova/exception.py b/nova/exception.py index f0746b19217e..012fc8d0dd91 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -884,6 +884,10 @@ class CellError(NovaException): message = _("Exception received during cell processing: %(exc_name)s.") +class CellsUpdateUnsupported(NovaException): + message = _("Cannot update cells configuration file.") + + class InstanceUnknownCell(NotFound): message = _("Cell is not known for instance %(instance_uuid)s") diff --git a/nova/tests/api/openstack/compute/contrib/test_cells.py b/nova/tests/api/openstack/compute/contrib/test_cells.py index 6434ac901063..76b712d41bf1 100644 --- a/nova/tests/api/openstack/compute/contrib/test_cells.py +++ b/nova/tests/api/openstack/compute/contrib/test_cells.py @@ -24,7 +24,6 @@ from nova.api.openstack import xmlutil from nova.cells import rpc_driver 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 @@ -46,7 +45,7 @@ FAKE_CAPABILITIES = [ {'cap3': '4,5', 'cap4': '5,6'}] -def fake_db_cell_get(context, cell_name): +def fake_cell_get(self, context, cell_name): for cell in FAKE_CELLS: if cell_name == cell['name']: return cell @@ -54,14 +53,14 @@ def fake_db_cell_get(context, cell_name): raise exception.CellNotFound(cell_name=cell_name) -def fake_db_cell_create(context, values): +def fake_cell_create(self, 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) +def fake_cell_update(self, context, cell_id, values): + cell = fake_cell_get(self, context, cell_id) cell.update(values) return cell @@ -82,17 +81,12 @@ def fake_cells_api_get_all_cell_info(*args): 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, 'cell_get', fake_cell_get) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_update', fake_cell_update) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_create', fake_cell_create) self.stubs.Set(cells_rpcapi.CellsAPI, 'get_cell_info_for_neighbors', fake_cells_api_get_all_cell_info) @@ -139,17 +133,22 @@ class CellsTest(test.TestCase): def test_cell_delete(self): call_info = {'delete_called': 0} - def fake_db_cell_delete(context, cell_name): + def fake_cell_delete(inst, context, cell_name): self.assertEqual(cell_name, 'cell999') call_info['delete_called'] += 1 - self.stubs.Set(db, 'cell_delete', fake_db_cell_delete) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_delete', fake_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): + def fake_cell_delete(inst, context, cell_name): + return 0 + + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_delete', fake_cell_delete) + req = self._get_request("cells/cell999") req.environ['nova.context'] = self.context self.assertRaises(exc.HTTPNotFound, self.controller.delete, req, diff --git a/nova/tests/api/openstack/compute/plugins/v3/test_cells.py b/nova/tests/api/openstack/compute/plugins/v3/test_cells.py index 0647879e457d..aa0a88bfd888 100644 --- a/nova/tests/api/openstack/compute/plugins/v3/test_cells.py +++ b/nova/tests/api/openstack/compute/plugins/v3/test_cells.py @@ -23,7 +23,6 @@ from nova.api.openstack import xmlutil from nova.cells import rpc_driver 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 @@ -45,7 +44,7 @@ FAKE_CAPABILITIES = [ {'cap3': '4,5', 'cap4': '5,6'}] -def fake_db_cell_get(context, cell_name): +def fake_cell_get(self, context, cell_name): for cell in FAKE_CELLS: if cell_name == cell['name']: return cell @@ -53,14 +52,14 @@ def fake_db_cell_get(context, cell_name): raise exception.CellNotFound(cell_name=cell_name) -def fake_db_cell_create(context, values): +def fake_cell_create(self, 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) +def fake_cell_update(self, context, cell_id, values): + cell = fake_cell_get(self, context, cell_id) cell.update(values) return cell @@ -81,17 +80,12 @@ def fake_cells_api_get_all_cell_info(*args): 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, 'cell_get', fake_cell_get) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_update', fake_cell_update) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_create', fake_cell_create) self.stubs.Set(cells_rpcapi.CellsAPI, 'get_cell_info_for_neighbors', fake_cells_api_get_all_cell_info) @@ -137,17 +131,22 @@ class CellsTest(test.TestCase): def test_cell_delete(self): call_info = {'delete_called': 0} - def fake_db_cell_delete(context, cell_name): + def fake_cell_delete(inst, context, cell_name): self.assertEqual(cell_name, 'cell999') call_info['delete_called'] += 1 - self.stubs.Set(db, 'cell_delete', fake_db_cell_delete) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_delete', fake_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): + def fake_cell_delete(inst, context, cell_name): + return 0 + + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_delete', fake_cell_delete) + req = self._get_request("cells/cell999") req.environ['nova.context'] = self.context self.assertRaises(exc.HTTPNotFound, self.controller.delete, req, diff --git a/nova/tests/cells/fakes.py b/nova/tests/cells/fakes.py index ffe9198f1c0d..7cd33f3d762a 100644 --- a/nova/tests/cells/fakes.py +++ b/nova/tests/cells/fakes.py @@ -83,7 +83,7 @@ class FakeCellState(cells_state.CellState): message.process() -class FakeCellStateManager(cells_state.CellStateManager): +class FakeCellStateManager(cells_state.CellStateManagerDB): def __init__(self, *args, **kwargs): super(FakeCellStateManager, self).__init__(*args, cell_state_cls=FakeCellState, **kwargs) diff --git a/nova/tests/cells/test_cells_manager.py b/nova/tests/cells/test_cells_manager.py index 736caed9f333..310190d6b5ee 100644 --- a/nova/tests/cells/test_cells_manager.py +++ b/nova/tests/cells/test_cells_manager.py @@ -617,3 +617,49 @@ class CellsManagerClassTestCase(test.TestCase): self.cells_manager.stop_instance(self.ctxt, instance='fake-instance', do_cast='meow') + + def test_cell_create(self): + values = 'values' + response = 'created_cell' + self.mox.StubOutWithMock(self.state_manager, + 'cell_create') + self.state_manager.cell_create(self.ctxt, values).\ + AndReturn(response) + self.mox.ReplayAll() + self.assertEqual(response, + self.cells_manager.cell_create(self.ctxt, values)) + + def test_cell_update(self): + cell_name = 'cell_name' + values = 'values' + response = 'updated_cell' + self.mox.StubOutWithMock(self.state_manager, + 'cell_update') + self.state_manager.cell_update(self.ctxt, cell_name, values).\ + AndReturn(response) + self.mox.ReplayAll() + self.assertEqual(response, + self.cells_manager.cell_update(self.ctxt, cell_name, + values)) + + def test_cell_delete(self): + cell_name = 'cell_name' + response = 1 + self.mox.StubOutWithMock(self.state_manager, + 'cell_delete') + self.state_manager.cell_delete(self.ctxt, cell_name).\ + AndReturn(response) + self.mox.ReplayAll() + self.assertEqual(response, + self.cells_manager.cell_delete(self.ctxt, cell_name)) + + def test_cell_get(self): + cell_name = 'cell_name' + response = 'cell_info' + self.mox.StubOutWithMock(self.state_manager, + 'cell_get') + self.state_manager.cell_get(self.ctxt, cell_name).\ + AndReturn(response) + self.mox.ReplayAll() + self.assertEqual(response, + self.cells_manager.cell_get(self.ctxt, cell_name)) diff --git a/nova/tests/cells/test_cells_rpcapi.py b/nova/tests/cells/test_cells_rpcapi.py index 189003f150c2..45108f3ee26d 100644 --- a/nova/tests/cells/test_cells_rpcapi.py +++ b/nova/tests/cells/test_cells_rpcapi.py @@ -494,3 +494,58 @@ class CellsAPITestCase(test.TestCase): self._check_result(call_info, 'stop_instance', expected_args, version='1.12') self.assertEqual(result, 'fake_response') + + def test_cell_create(self): + call_info = self._stub_rpc_method('call', 'fake_response') + + result = self.cells_rpcapi.cell_create(self.fake_context, 'values') + + expected_args = {'values': 'values'} + self._check_result(call_info, 'cell_create', + expected_args, version='1.13') + self.assertEqual(result, 'fake_response') + + def test_cell_update(self): + call_info = self._stub_rpc_method('call', 'fake_response') + + result = self.cells_rpcapi.cell_update(self.fake_context, + 'cell_name', 'values') + + expected_args = {'cell_name': 'cell_name', + 'values': 'values'} + self._check_result(call_info, 'cell_update', + expected_args, version='1.13') + self.assertEqual(result, 'fake_response') + + def test_cell_delete(self): + call_info = self._stub_rpc_method('call', 'fake_response') + + result = self.cells_rpcapi.cell_delete(self.fake_context, + 'cell_name') + + expected_args = {'cell_name': 'cell_name'} + self._check_result(call_info, 'cell_delete', + expected_args, version='1.13') + self.assertEqual(result, 'fake_response') + + def test_cell_delete(self): + call_info = self._stub_rpc_method('call', 'fake_response') + + result = self.cells_rpcapi.cell_delete(self.fake_context, + 'cell_name') + + expected_args = {'cell_name': 'cell_name'} + self._check_result(call_info, 'cell_delete', + expected_args, version='1.13') + self.assertEqual(result, 'fake_response') + + def test_cell_get(self): + call_info = self._stub_rpc_method('call', 'fake_response') + + result = self.cells_rpcapi.cell_get(self.fake_context, + 'cell_name') + + expected_args = {'cell_name': 'cell_name'} + self._check_result(call_info, 'cell_get', + expected_args, version='1.13') + self.assertEqual(result, 'fake_response') diff --git a/nova/tests/cells/test_cells_state_manager.py b/nova/tests/cells/test_cells_state_manager.py index 03dc37ed409c..eb75196e048d 100644 --- a/nova/tests/cells/test_cells_state_manager.py +++ b/nova/tests/cells/test_cells_state_manager.py @@ -166,3 +166,43 @@ class TestCellsGetCapacity(TestCellsStateManager): self.assertRaises(exception.CellNotFound, self.state_manager.get_capacities, cell_name="invalid_cell_name") + + +class FakeCellStateManager(object): + def __init__(self): + self.called = [] + + def _cell_data_sync(self, force=False): + self.called.append(('_cell_data_sync', force)) + + +class TestSyncDecorators(test.TestCase): + def test_sync_before(self): + manager = FakeCellStateManager() + + def test(inst, *args, **kwargs): + self.assertEqual(inst, manager) + self.assertEqual(args, (1, 2, 3)) + self.assertEqual(kwargs, dict(a=4, b=5, c=6)) + return 'result' + wrapper = state.sync_before(test) + + result = wrapper(manager, 1, 2, 3, a=4, b=5, c=6) + + self.assertEqual(result, 'result') + self.assertEqual(manager.called, [('_cell_data_sync', False)]) + + def test_sync_after(self): + manager = FakeCellStateManager() + + def test(inst, *args, **kwargs): + self.assertEqual(inst, manager) + self.assertEqual(args, (1, 2, 3)) + self.assertEqual(kwargs, dict(a=4, b=5, c=6)) + return 'result' + wrapper = state.sync_after(test) + + result = wrapper(manager, 1, 2, 3, a=4, b=5, c=6) + + self.assertEqual(result, 'result') + self.assertEqual(manager.called, [('_cell_data_sync', True)]) diff --git a/nova/tests/integrated/test_api_samples.py b/nova/tests/integrated/test_api_samples.py index 2df83d17b793..d8ce7c9eedb3 100644 --- a/nova/tests/integrated/test_api_samples.py +++ b/nova/tests/integrated/test_api_samples.py @@ -33,6 +33,7 @@ from nova.api.openstack.compute.contrib import coverage_ext from nova.api.openstack.compute.contrib import fping from nova.api.openstack.compute.extensions import ExtensionManager as ext_mgr # Import extensions to pull in osapi_compute_extension CONF option used below. +from nova.cells import rpcapi as cells_rpcapi from nova.cells import state from nova.cloudpipe import pipelib from nova.compute import api as compute_api @@ -2878,7 +2879,7 @@ class CellsSampleJsonTest(ApiSampleTestBase): def _fake_cell_get_all(context): return self.cells - def _fake_cell_get(context, cell_name): + def _fake_cell_get(inst, context, cell_name): for cell in self.cells: if cell['name'] == cell_name: return cell @@ -2895,7 +2896,7 @@ class CellsSampleJsonTest(ApiSampleTestBase): self.cells.append(cell) self.stubs.Set(db, 'cell_get_all', _fake_cell_get_all) - self.stubs.Set(db, 'cell_get', _fake_cell_get) + self.stubs.Set(cells_rpcapi.CellsAPI, 'cell_get', _fake_cell_get) def test_cells_empty_list(self): # Override this