From 88ab9369e3ddfd8b8684bf280330ff8d37a74b2b Mon Sep 17 00:00:00 2001 From: "Kevin L. Mitchell" Date: Mon, 15 Jul 2013 10:32:23 -0500 Subject: [PATCH] Load cell data from a configuration file Cells currently keeps all inter-cell communication data, including usernames and passwords, in the database. This is undesirable and unnecessary, since cells data isn't updated very frequently. This change allows cells data to be drawn from a JSON file specified via a new [cells]cells_config option. When specified, the database is no longer consulted when reloading cells data. Implements blueprint eliminate-clear-passwords-from-cells-table. DocImpact: Cells may now optionally be configured through a JSON- formatted file. The file will need the columns present in the Cell model (excluding common database fields and the 'id' column). The queue connection information must be specified through a 'transport_url' field, instead of 'username', 'password', etc. The transport_url has the following form: rabbit://:@:/ The scheme may be either 'rabbit' (shown above) or 'qpid'. Change-Id: I7046ce55a0a294293c1b1a5fb0f092aeb891ee01 --- etc/nova/cells.json | 26 +++ nova/api/openstack/compute/contrib/cells.py | 19 +- .../api/openstack/compute/plugins/v3/cells.py | 19 +- nova/cells/manager.py | 14 +- nova/cells/rpcapi.py | 24 +++ nova/cells/state.py | 172 ++++++++++++++---- nova/exception.py | 4 + .../openstack/compute/contrib/test_cells.py | 29 ++- .../compute/plugins/v3/test_cells.py | 29 ++- nova/tests/cells/fakes.py | 2 +- nova/tests/cells/test_cells_manager.py | 46 +++++ nova/tests/cells/test_cells_rpcapi.py | 55 ++++++ nova/tests/cells/test_cells_state_manager.py | 40 ++++ nova/tests/integrated/test_api_samples.py | 5 +- 14 files changed, 401 insertions(+), 83 deletions(-) create mode 100644 etc/nova/cells.json 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