diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 066b12261518..a5724ab03451 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -66,6 +66,7 @@ from oslo_db import exception as db_exc from oslo_log import log as logging import oslo_messaging as messaging from oslo_utils import importutils +from oslo_utils import uuidutils import six from nova.api.ec2 import ec2utils @@ -95,6 +96,7 @@ CONF.import_opt('vlan_start', 'nova.network.manager') CONF.import_opt('vpn_start', 'nova.network.manager') CONF.import_opt('default_floating_pool', 'nova.network.floating_ips') CONF.import_opt('public_interface', 'nova.network.linux_net') +CONF.import_opt('connection', 'oslo_db.options', group='database') QUOTAS = quota.QUOTAS @@ -1360,6 +1362,74 @@ class CellV2Commands(object): instance = instances[-1] print('Next marker: - %s' % instance.uuid) + # TODO(melwitt): Remove this when the oslo.messaging function + # for assembling a transport url from ConfigOpts is available + @args('--transport-url', metavar='', required=True, + dest='transport_url', + help='The transport url for the cell message queue') + @args('--name', metavar='', help='The name of the cell') + @args('--verbose', action='store_true', + help='Return and output the uuid of the created cell') + def map_cell_and_hosts(self, transport_url, name=None, verbose=False): + """EXPERIMENTAL. Create a cell mapping and host mappings for a cell. + + Users not dividing their cloud into multiple cells will be a single + cell v2 deployment and should specify: + + nova-manage cell_v2 map_cell_and_hosts --config-file + + Users running multiple cells can add a cell v2 by specifying: + + nova-manage cell_v2 map_cell_and_hosts --config-file + """ + ctxt = context.RequestContext() + cell_mapping_uuid = cell_mapping = None + # First, try to detect if a CellMapping has already been created + compute_nodes = objects.ComputeNodeList.get_all(ctxt) + if not compute_nodes: + print(_('No hosts found to map to cell, exiting.')) + return(0) + missing_nodes = [] + for compute_node in compute_nodes: + try: + host_mapping = objects.HostMapping.get_by_host( + ctxt, compute_node.host) + except exception.HostMappingNotFound: + missing_nodes.append(compute_node) + else: + if verbose: + print(_( + 'Host %(host)s is already mapped to cell %(uuid)s' + ) % {'host': host_mapping.host, + 'uuid': host_mapping.cell_mapping.uuid}) + # Re-using the existing UUID in case there is already a mapping + # NOTE(sbauza): There could be possibly multiple CellMappings + # if the operator provides another configuration file and moves + # the hosts to another cell v2, but that's not really something + # we should support. + cell_mapping_uuid = host_mapping.cell_mapping.uuid + if not missing_nodes: + print(_('All hosts are already mapped to cell(s), exiting.')) + return(0) + # Create the cell mapping in the API database + if cell_mapping_uuid is not None: + cell_mapping = objects.CellMapping.get_by_uuid( + ctxt, cell_mapping_uuid) + if cell_mapping is None: + cell_mapping_uuid = uuidutils.generate_uuid() + cell_mapping = objects.CellMapping( + ctxt, uuid=cell_mapping_uuid, name=name, + transport_url=transport_url, + database_connection=CONF.database.connection) + cell_mapping.create() + # Pull the hosts from the cell database and create the host mappings + for compute_node in missing_nodes: + host_mapping = objects.HostMapping( + ctxt, host=compute_node.host, cell_mapping=cell_mapping) + host_mapping.create() + if verbose: + print(cell_mapping_uuid) + CATEGORIES = { 'account': AccountCommands, diff --git a/nova/tests/unit/test_nova_manage.py b/nova/tests/unit/test_nova_manage.py index adf0ddb39737..b360db50ed69 100644 --- a/nova/tests/unit/test_nova_manage.py +++ b/nova/tests/unit/test_nova_manage.py @@ -18,6 +18,7 @@ import sys import fixtures import mock +from oslo_utils import uuidutils from nova.cmd import manage from nova import context @@ -680,3 +681,140 @@ class CellCommandsTestCase(test.NoDBTestCase): 'weight_offset': 0.0, 'weight_scale': 0.0} mock_db_cell_create.assert_called_once_with(ctxt, exp_values) + + +class CellV2CommandsTestCase(test.TestCase): + def setUp(self): + super(CellV2CommandsTestCase, self).setUp() + self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO())) + self.commands = manage.CellV2Commands() + + def test_map_cell_and_hosts(self): + # Create some fake compute nodes and check if they get host mappings + ctxt = context.RequestContext() + values = { + 'vcpus': 4, + 'memory_mb': 4096, + 'local_gb': 1024, + 'vcpus_used': 2, + 'memory_mb_used': 2048, + 'local_gb_used': 512, + 'hypervisor_type': 'Hyper-Dan-VM-ware', + 'hypervisor_version': 1001, + 'cpu_info': 'Schmintel i786', + } + for i in range(3): + host = 'host%s' % i + compute_node = objects.ComputeNode(ctxt, host=host, **values) + compute_node.create() + cell_transport_url = "fake://guest:devstack@127.0.0.1:9999/" + self.commands.map_cell_and_hosts(cell_transport_url, name='ssd', + verbose=True) + cell_mapping_uuid = sys.stdout.getvalue().strip() + # Verify the cell mapping + cell_mapping = objects.CellMapping.get_by_uuid(ctxt, cell_mapping_uuid) + self.assertEqual('ssd', cell_mapping.name) + self.assertEqual(cell_transport_url, cell_mapping.transport_url) + # Verify the host mappings + for i in range(3): + host = 'host%s' % i + host_mapping = objects.HostMapping.get_by_host(ctxt, host) + self.assertEqual(cell_mapping.uuid, host_mapping.cell_mapping.uuid) + + def test_map_cell_and_hosts_duplicate(self): + # Create a cell mapping and hosts and check that nothing new is created + ctxt = context.RequestContext() + cell_mapping_uuid = uuidutils.generate_uuid() + cell_mapping = objects.CellMapping( + ctxt, uuid=cell_mapping_uuid, name='fake', + transport_url='fake://', database_connection='fake://') + cell_mapping.create() + # Create compute nodes that will map to the cell + values = { + 'vcpus': 4, + 'memory_mb': 4096, + 'local_gb': 1024, + 'vcpus_used': 2, + 'memory_mb_used': 2048, + 'local_gb_used': 512, + 'hypervisor_type': 'Hyper-Dan-VM-ware', + 'hypervisor_version': 1001, + 'cpu_info': 'Schmintel i786', + } + for i in range(3): + host = 'host%s' % i + compute_node = objects.ComputeNode(ctxt, host=host, **values) + compute_node.create() + host_mapping = objects.HostMapping( + ctxt, host=host, cell_mapping=cell_mapping) + host_mapping.create() + cell_transport_url = "fake://guest:devstack@127.0.0.1:9999/" + retval = self.commands.map_cell_and_hosts(cell_transport_url, + name='ssd', + verbose=True) + self.assertEqual(0, retval) + output = sys.stdout.getvalue().strip() + expected = '' + for i in range(3): + expected += ('Host host%s is already mapped to cell %s\n' % + (i, cell_mapping_uuid)) + expected += 'All hosts are already mapped to cell(s), exiting.' + self.assertEqual(expected, output) + + def test_map_cell_and_hosts_partial_update(self): + # Create a cell mapping and partial hosts and check that + # missing HostMappings are created + ctxt = context.RequestContext() + cell_mapping_uuid = uuidutils.generate_uuid() + cell_mapping = objects.CellMapping( + ctxt, uuid=cell_mapping_uuid, name='fake', + transport_url='fake://', database_connection='fake://') + cell_mapping.create() + # Create compute nodes that will map to the cell + values = { + 'vcpus': 4, + 'memory_mb': 4096, + 'local_gb': 1024, + 'vcpus_used': 2, + 'memory_mb_used': 2048, + 'local_gb_used': 512, + 'hypervisor_type': 'Hyper-Dan-VM-ware', + 'hypervisor_version': 1001, + 'cpu_info': 'Schmintel i786', + } + for i in range(3): + host = 'host%s' % i + compute_node = objects.ComputeNode(ctxt, host=host, **values) + compute_node.create() + # Only create 2 existing HostMappings out of 3 + for i in range(2): + host = 'host%s' % i + host_mapping = objects.HostMapping( + ctxt, host=host, cell_mapping=cell_mapping) + host_mapping.create() + cell_transport_url = "fake://guest:devstack@127.0.0.1:9999/" + self.commands.map_cell_and_hosts(cell_transport_url, + name='ssd', + verbose=True) + # Verify the HostMapping for the last host was created + host_mapping = objects.HostMapping.get_by_host(ctxt, 'host2') + self.assertEqual(cell_mapping.uuid, host_mapping.cell_mapping.uuid) + # Verify the output + output = sys.stdout.getvalue().strip() + expected = '' + for i in range(2): + expected += ('Host host%s is already mapped to cell %s\n' % + (i, cell_mapping_uuid)) + # The expected CellMapping UUID for the last host should be the same + expected += cell_mapping.uuid + self.assertEqual(expected, output) + + def test_map_cell_and_hosts_no_hosts_found(self): + cell_transport_url = "fake://guest:devstack@127.0.0.1:9999/" + retval = self.commands.map_cell_and_hosts(cell_transport_url, + name='ssd', + verbose=True) + self.assertEqual(0, retval) + output = sys.stdout.getvalue().strip() + expected = 'No hosts found to map to cell, exiting.' + self.assertEqual(expected, output)