#    Copyright 2011 OpenStack Foundation
#    Copyright 2011 Ilya Alekseyev
#
#    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 datetime
from io import StringIO
import sys
import textwrap
from unittest import mock
import warnings

import ddt
import fixtures
from oslo_db import exception as db_exc
from oslo_serialization import jsonutils
from oslo_utils.fixture import uuidsentinel
from oslo_utils import uuidutils

from nova.cmd import manage
from nova import conf
from nova import context
from nova.db.main import api as db
from nova.db import migration
from nova import exception
from nova import objects
from nova.objects import fields as obj_fields
from nova.scheduler.client import report
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.unit import fake_requests

CONF = conf.CONF


class UtilitiesTestCase(test.NoDBTestCase):

    def test_format_dict(self):
        x = {
            'foo': 'bar',
            'bing': 'bat',
            'test': {'a nested': 'dict'},
            'wow': 'a multiline\nstring',
        }
        self.assertEqual(
            textwrap.dedent("""\
                +----------+----------------------+
                | Property | Value                |
                +----------+----------------------+
                | bing     | bat                  |
                | foo      | bar                  |
                | test     | {'a nested': 'dict'} |
                | wow      | a multiline          |
                |          | string               |
                +----------+----------------------+"""),
            manage.format_dict(x),
        )

    def test_mask_passwd(self):
        # try to trip up the regex match with extra : and @.
        url1 = ("http://user:pass@domain.com:1234/something?"
                "email=me@somewhere.com")
        self.assertEqual(
            ("http://user:****@domain.com:1234/something?"
             "email=me@somewhere.com"),
            manage.mask_passwd_in_url(url1))

        # pretty standard kinds of urls that we expect, have different
        # schemes. This ensures none of the parts get lost.
        url2 = "mysql+pymysql://root:pass@127.0.0.1/nova_api?charset=utf8"
        self.assertEqual(
            "mysql+pymysql://root:****@127.0.0.1/nova_api?charset=utf8",
            manage.mask_passwd_in_url(url2))

        url3 = "rabbit://stackrabbit:pass@10.42.0.53:5672/"
        self.assertEqual(
            "rabbit://stackrabbit:****@10.42.0.53:5672/",
            manage.mask_passwd_in_url(url3))

        url4 = ("mysql+pymysql://nova:my_password@my_IP/nova_api?"
                "charset=utf8&ssl_ca=/etc/nova/tls/mysql/ca-cert.pem"
                "&ssl_cert=/etc/nova/tls/mysql/server-cert.pem"
                "&ssl_key=/etc/nova/tls/mysql/server-key.pem")
        url4_safe = ("mysql+pymysql://nova:****@my_IP/nova_api?"
                "charset=utf8&ssl_ca=/etc/nova/tls/mysql/ca-cert.pem"
                "&ssl_cert=/etc/nova/tls/mysql/server-cert.pem"
                "&ssl_key=/etc/nova/tls/mysql/server-key.pem")
        self.assertEqual(
            url4_safe,
            manage.mask_passwd_in_url(url4))


class DbCommandsTestCase(test.NoDBTestCase):
    USES_DB_SELF = True

    def setUp(self):
        super(DbCommandsTestCase, self).setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        self.commands = manage.DbCommands()
        self.useFixture(nova_fixtures.Database())
        self.useFixture(nova_fixtures.Database(database='api'))

    def test_online_migrations_unique(self):
        names = [m.__name__ for m in self.commands.online_migrations]
        self.assertEqual(len(set(names)), len(names),
                         'Online migrations must have a unique name')

    def test_archive_deleted_rows_negative(self):
        self.assertEqual(2, self.commands.archive_deleted_rows(-1))

    def test_archive_deleted_rows_large_number(self):
        large_number = '1' * 100
        self.assertEqual(2, self.commands.archive_deleted_rows(large_number))

    @mock.patch.object(manage.DbCommands, 'purge')
    @mock.patch.object(db, 'archive_deleted_rows',
                       # Each call to archive in each cell returns
                       # total_rows_archived=15, so passing max_rows=30 will
                       # only iterate the first two cells.
                       return_value=(dict(instances=10, consoles=5),
                                     list(), 15))
    def _test_archive_deleted_rows_all_cells(self, mock_db_archive,
                                             mock_purge, purge=False):
        cell_dbs = nova_fixtures.CellDatabases()
        cell_dbs.add_cell_database('fake:///db1')
        cell_dbs.add_cell_database('fake:///db2')
        cell_dbs.add_cell_database('fake:///db3')
        self.useFixture(cell_dbs)

        ctxt = context.RequestContext()

        cell_mapping1 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db1',
                                            transport_url='fake:///mq1',
                                            name='cell1')
        cell_mapping1.create()
        cell_mapping2 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db2',
                                            transport_url='fake:///mq2',
                                            name='cell2')
        cell_mapping2.create()
        cell_mapping3 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db3',
                                            transport_url='fake:///mq3',
                                            name='cell3')
        cell_mapping3.create()
        # Archive with max_rows=30, so we test the case that when we are out of
        # limit, we don't go to the remaining cell.
        result = self.commands.archive_deleted_rows(30, verbose=True,
                                                    all_cells=True,
                                                    purge=purge)
        mock_db_archive.assert_has_calls([
            # Called with max_rows=30 but only 15 were archived.
            mock.call(
                test.MatchType(context.RequestContext), 30, before=None,
                task_log=False),
            # So the total from the last call was 15 and the new max_rows=15
            # for the next call in the second cell.
            mock.call(
                test.MatchType(context.RequestContext), 15, before=None,
                task_log=False)
        ])
        output = self.output.getvalue()
        expected = '''\
+-----------------+-------------------------+
| Table           | Number of Rows Archived |
+-----------------+-------------------------+
| cell1.consoles  | 5                       |
| cell1.instances | 10                      |
| cell2.consoles  | 5                       |
| cell2.instances | 10                      |
+-----------------+-------------------------+
'''
        if purge:
            expected += 'Rows were archived, running purge...\n'
            mock_purge.assert_called_once_with(purge_all=True, verbose=True,
                                               all_cells=True)
        else:
            mock_purge.assert_not_called()
        self.assertEqual(expected, output)
        self.assertEqual(1, result)

    def test_archive_deleted_rows_all_cells(self):
        self._test_archive_deleted_rows_all_cells()

    def test_archive_deleted_rows_all_cells_purge(self):
        self._test_archive_deleted_rows_all_cells(purge=True)

    @mock.patch.object(db, 'archive_deleted_rows')
    def test_archive_deleted_rows_all_cells_until_complete(self,
                                                           mock_db_archive):
        # First two calls to archive in each cell return total_rows_archived=15
        # and the last call returns 0 (nothing left to archive).
        fake_return = (dict(instances=10, consoles=5), list(), 15)
        mock_db_archive.side_effect = [fake_return,
                                       (dict(), list(), 0),
                                       fake_return,
                                       (dict(), list(), 0),
                                       (dict(), list(), 0)]
        cell_dbs = nova_fixtures.CellDatabases()
        cell_dbs.add_cell_database('fake:///db1')
        cell_dbs.add_cell_database('fake:///db2')
        cell_dbs.add_cell_database('fake:///db3')
        self.useFixture(cell_dbs)

        ctxt = context.RequestContext()

        cell_mapping1 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db1',
                                            transport_url='fake:///mq1',
                                            name='cell1')
        cell_mapping1.create()
        cell_mapping2 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db2',
                                            transport_url='fake:///mq2',
                                            name='cell2')
        cell_mapping2.create()
        cell_mapping3 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db3',
                                            transport_url='fake:///mq3',
                                            name='cell3')
        cell_mapping3.create()
        # Archive with max_rows=30, so we test that subsequent max_rows are not
        # reduced when until_complete=True. There is no max total limit.
        result = self.commands.archive_deleted_rows(30, verbose=True,
                                                    all_cells=True,
                                                    until_complete=True)
        mock_db_archive.assert_has_calls([
            # Called with max_rows=30 but only 15 were archived.
            mock.call(
                test.MatchType(context.RequestContext), 30, before=None,
                task_log=False),
            # Called with max_rows=30 but 0 were archived (nothing left to
            # archive in this cell)
            mock.call(
                test.MatchType(context.RequestContext), 30, before=None,
                task_log=False),
            # So the total from the last call was 0 and the new max_rows=30
            # because until_complete=True.
            mock.call(
                test.MatchType(context.RequestContext), 30, before=None,
                task_log=False),
            # Called with max_rows=30 but 0 were archived (nothing left to
            # archive in this cell)
            mock.call(
                test.MatchType(context.RequestContext), 30, before=None,
                task_log=False),
            # Called one final time with max_rows=30
            mock.call(
                test.MatchType(context.RequestContext), 30, before=None,
                task_log=False)
        ])
        output = self.output.getvalue()
        expected = '''\
Archiving.....complete
+-----------------+-------------------------+
| Table           | Number of Rows Archived |
+-----------------+-------------------------+
| cell1.consoles  | 5                       |
| cell1.instances | 10                      |
| cell2.consoles  | 5                       |
| cell2.instances | 10                      |
+-----------------+-------------------------+
'''
        self.assertEqual(expected, output)
        self.assertEqual(1, result)

    @mock.patch.object(db, 'archive_deleted_rows',
                       return_value=(
                               dict(instances=10, consoles=5), list(), 15))
    def _test_archive_deleted_rows(self, mock_db_archive, verbose=False):
        result = self.commands.archive_deleted_rows(20, verbose=verbose)
        mock_db_archive.assert_called_once_with(
            test.MatchType(context.RequestContext), 20, before=None,
            task_log=False)
        output = self.output.getvalue()
        if verbose:
            expected = '''\
+-----------+-------------------------+
| Table     | Number of Rows Archived |
+-----------+-------------------------+
| consoles  | 5                       |
| instances | 10                      |
+-----------+-------------------------+
'''
            self.assertEqual(expected, output)
        else:
            self.assertEqual(0, len(output))
        self.assertEqual(1, result)

    def test_archive_deleted_rows(self):
        # Tests that we don't show any table output (not verbose).
        self._test_archive_deleted_rows()

    def test_archive_deleted_rows_verbose(self):
        # Tests that we get table output.
        self._test_archive_deleted_rows(verbose=True)

    @mock.patch('time.sleep')
    @mock.patch.object(db, 'archive_deleted_rows')
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_archive_deleted_rows_until_complete(self, mock_get_all,
                                                 mock_db_archive, mock_sleep,
                                                 verbose=False,
                                                 sleep=0):
        mock_db_archive.side_effect = [
            ({'instances': 10, 'instance_extra': 5}, list(), 15),
            ({'instances': 5, 'instance_faults': 1}, list(), 6),
            ({}, list(), 0)]
        result = self.commands.archive_deleted_rows(20, verbose=verbose,
                                                    until_complete=True,
                                                    sleep=sleep)
        self.assertEqual(1, result)
        if verbose:
            expected = """\
Archiving.....complete
+-----------------+-------------------------+
| Table           | Number of Rows Archived |
+-----------------+-------------------------+
| instance_extra  | 5                       |
| instance_faults | 1                       |
| instances       | 15                      |
+-----------------+-------------------------+
"""
        else:
            expected = ''

        self.assertEqual(expected, self.output.getvalue())
        mock_db_archive.assert_has_calls([
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
        ])
        self.assertEqual(2, mock_sleep.call_count)
        mock_sleep.assert_has_calls([mock.call(sleep), mock.call(sleep)])

    def test_archive_deleted_rows_until_complete_quiet(self):
        self.test_archive_deleted_rows_until_complete(verbose=False)

    def test_archive_deleted_rows_until_complete_sleep(self):
        self.test_archive_deleted_rows_until_complete(sleep=30)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    @mock.patch.object(db, 'archive_deleted_rows')
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_archive_deleted_rows_until_stopped(self, mock_get_all,
                                                mock_db_archive,
                                                mock_db_purge,
                                                verbose=True):
        mock_db_archive.side_effect = [
            ({'instances': 10, 'instance_extra': 5}, list(), 15),
            ({'instances': 5, 'instance_faults': 1}, list(), 6),
            KeyboardInterrupt]
        result = self.commands.archive_deleted_rows(20, verbose=verbose,
                                                    until_complete=True,
                                                    purge=True)
        self.assertEqual(1, result)
        if verbose:
            expected = """\
Archiving.....stopped
+-----------------+-------------------------+
| Table           | Number of Rows Archived |
+-----------------+-------------------------+
| instance_extra  | 5                       |
| instance_faults | 1                       |
| instances       | 15                      |
+-----------------+-------------------------+
Rows were archived, running purge...
"""
        else:
            expected = ''

        self.assertEqual(expected, self.output.getvalue())
        mock_db_archive.assert_has_calls([
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
        ])
        mock_db_purge.assert_called_once_with(mock.ANY, None,
                                              status_fn=mock.ANY)

    @mock.patch.object(db, 'archive_deleted_rows')
    def test_archive_deleted_rows_until_stopped_cells(self, mock_db_archive,
                                                      verbose=True):
        # Test when archive with all_cells=True and until_complete=True,
        # when hit KeyboardInterrupt, it will directly return and not
        # process remaining cells.
        mock_db_archive.side_effect = [
            ({'instances': 10, 'instance_extra': 5}, list(), 15),
            KeyboardInterrupt]
        cell_dbs = nova_fixtures.CellDatabases()
        cell_dbs.add_cell_database('fake:///db1')
        cell_dbs.add_cell_database('fake:///db2')
        cell_dbs.add_cell_database('fake:///db3')
        self.useFixture(cell_dbs)

        ctxt = context.RequestContext()

        cell_mapping1 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db1',
                                            transport_url='fake:///mq1',
                                            name='cell1')
        cell_mapping1.create()
        cell_mapping2 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db2',
                                            transport_url='fake:///mq2',
                                            name='cell2')
        cell_mapping2.create()
        cell_mapping3 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db3',
                                            transport_url='fake:///mq3',
                                            name='cell3')
        cell_mapping3.create()
        result = self.commands.archive_deleted_rows(20, verbose=verbose,
                                                    until_complete=True,
                                                    all_cells=True)
        self.assertEqual(1, result)
        if verbose:
            expected = '''\
Archiving....stopped
+----------------------+-------------------------+
| Table                | Number of Rows Archived |
+----------------------+-------------------------+
| cell1.instance_extra | 5                       |
| cell1.instances      | 10                      |
+----------------------+-------------------------+
'''
        else:
            expected = ''

        self.assertEqual(expected, self.output.getvalue())
        mock_db_archive.assert_has_calls([
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False),
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False)
        ])

    def test_archive_deleted_rows_until_stopped_quiet(self):
        self.test_archive_deleted_rows_until_stopped(verbose=False)

    @mock.patch.object(db, 'archive_deleted_rows')
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_archive_deleted_rows_before(self, mock_get_all, mock_db_archive):
        mock_db_archive.side_effect = [
            ({'instances': 10, 'instance_extra': 5}, list(), 15),
            ({'instances': 5, 'instance_faults': 1}, list(), 6),
            KeyboardInterrupt]
        result = self.commands.archive_deleted_rows(20, before='2017-01-13')
        mock_db_archive.assert_called_once_with(
                test.MatchType(context.RequestContext), 20,
                before=datetime.datetime(2017, 1, 13),
                task_log=False)
        self.assertEqual(1, result)

    @mock.patch.object(db, 'archive_deleted_rows', return_value=({}, [], 0))
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_archive_deleted_rows_verbose_no_results(self, mock_get_all,
                                                     mock_db_archive):
        result = self.commands.archive_deleted_rows(20, verbose=True,
                                                    purge=True)
        mock_db_archive.assert_called_once_with(
            test.MatchType(context.RequestContext), 20, before=None,
            task_log=False)
        output = self.output.getvalue()
        # If nothing was archived, there should be no purge messages
        self.assertIn('Nothing was archived.', output)
        self.assertEqual(0, result)

    @mock.patch.object(db, 'archive_deleted_rows')
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_archive_deleted_rows_deleted_instances_no_rows(self, mock_get_all,
                                                            mock_db_archive):
        # Simulate a scenario where we didn't archive any rows (example:
        # DBReferenceError was raised while processing the instances table) but
        # the separate query for deleted_instance_uuids found rows.
        mock_db_archive.return_value = (
            {}, [uuidsentinel.instance1, uuidsentinel.instance2], 0)

        result = self.commands.archive_deleted_rows(20, verbose=True)

        mock_db_archive.assert_called_once_with(
            test.MatchType(context.RequestContext), 20, before=None,
            task_log=False)
        output = self.output.getvalue()
        # If nothing was archived, there should be no purge messages
        self.assertIn('Nothing was archived.', output)
        self.assertEqual(0, result)

    @mock.patch.object(db, 'archive_deleted_rows')
    @mock.patch.object(objects.RequestSpec, 'destroy_bulk')
    @mock.patch.object(objects.InstanceGroup, 'destroy_members_bulk')
    def test_archive_deleted_rows_and_api_db_records(
            self, mock_members_destroy, mock_reqspec_destroy, mock_db_archive,
            verbose=True):
        self.useFixture(nova_fixtures.Database())
        self.useFixture(nova_fixtures.Database(database='api'))

        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(context=ctxt,
                                           uuid=cell_uuid,
                                           database_connection='fake:///db',
                                           transport_url='fake:///mq',
                                           name='cell1')
        cell_mapping.create()
        uuids = []
        for i in range(2):
            uuid = uuidutils.generate_uuid()
            uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id, uuid=uuid)\
                                .create()
            objects.InstanceMapping(ctxt, project_id=ctxt.project_id,
                                cell_mapping=cell_mapping, instance_uuid=uuid)\
                                .create()

        mock_db_archive.return_value = (
            dict(instances=2, consoles=5), uuids, 7)
        mock_reqspec_destroy.return_value = 2
        mock_members_destroy.return_value = 0
        result = self.commands.archive_deleted_rows(20, verbose=verbose,
                                                    all_cells=True)

        self.assertEqual(1, result)
        mock_db_archive.assert_has_calls([
            mock.call(
                test.MatchType(context.RequestContext), 20, before=None,
                task_log=False)
        ])
        self.assertEqual(1, mock_reqspec_destroy.call_count)
        mock_members_destroy.assert_called_once()

        output = self.output.getvalue()
        if verbose:
            expected = '''\
+------------------------------+-------------------------+
| Table                        | Number of Rows Archived |
+------------------------------+-------------------------+
| API_DB.instance_group_member | 0                       |
| API_DB.instance_mappings     | 2                       |
| API_DB.request_specs         | 2                       |
| cell1.consoles               | 5                       |
| cell1.instances              | 2                       |
+------------------------------+-------------------------+
'''
            self.assertEqual(expected, output)
        else:
            self.assertEqual(0, len(output))

    @mock.patch.object(objects.CellMappingList, 'get_all',
                       side_effect=db_exc.CantStartEngineError)
    def test_archive_deleted_rows_without_api_connection_configured(self,
                                                           mock_get_all):
        result = self.commands.archive_deleted_rows(20, verbose=True)
        mock_get_all.assert_called_once()
        output = self.output.getvalue()
        expected = '''\
Failed to connect to API DB so aborting this archival attempt. \
Please check your config file to make sure that [api_database]/connection \
is set and run this command again.
'''
        self.assertEqual(expected, output)
        self.assertEqual(3, result)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    def test_purge_all(self, mock_purge):
        mock_purge.return_value = 1
        ret = self.commands.purge(purge_all=True)
        self.assertEqual(0, ret)
        mock_purge.assert_called_once_with(mock.ANY, None, status_fn=mock.ANY)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    def test_purge_date(self, mock_purge):
        mock_purge.return_value = 1
        ret = self.commands.purge(before='oct 21 2015')
        self.assertEqual(0, ret)
        mock_purge.assert_called_once_with(mock.ANY,
                                           datetime.datetime(2015, 10, 21),
                                           status_fn=mock.ANY)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    def test_purge_date_fail(self, mock_purge):
        ret = self.commands.purge(before='notadate')
        self.assertEqual(2, ret)
        self.assertFalse(mock_purge.called)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    def test_purge_no_args(self, mock_purge):
        ret = self.commands.purge()
        self.assertEqual(1, ret)
        self.assertFalse(mock_purge.called)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    def test_purge_nothing_deleted(self, mock_purge):
        mock_purge.return_value = 0
        ret = self.commands.purge(purge_all=True)
        self.assertEqual(3, ret)

    @mock.patch('nova.db.main.api.purge_shadow_tables')
    @mock.patch('nova.objects.CellMappingList.get_all')
    def test_purge_all_cells(self, mock_get_cells, mock_purge):
        cell1 = objects.CellMapping(uuid=uuidsentinel.cell1, name='cell1',
                                    database_connection='foo1',
                                    transport_url='bar1')
        cell2 = objects.CellMapping(uuid=uuidsentinel.cell2, name='cell2',
                                    database_connection='foo2',
                                    transport_url='bar2')

        mock_get_cells.return_value = [cell1, cell2]

        values = [123, 456]

        def fake_purge(*args, **kwargs):
            val = values.pop(0)
            kwargs['status_fn'](val)
            return val
        mock_purge.side_effect = fake_purge

        ret = self.commands.purge(purge_all=True, all_cells=True, verbose=True)
        self.assertEqual(0, ret)
        mock_get_cells.assert_called_once_with(mock.ANY)
        output = self.output.getvalue()
        expected = """\
Cell %s: 123
Cell %s: 456
""" % (cell1.identity, cell2.identity)

        self.assertEqual(expected, output)

    @mock.patch('nova.objects.CellMappingList.get_all')
    def test_purge_all_cells_no_api_config(self, mock_get_cells):
        mock_get_cells.side_effect = db_exc.DBError
        ret = self.commands.purge(purge_all=True, all_cells=True)
        self.assertEqual(4, ret)
        self.assertIn('Unable to get cell list', self.output.getvalue())

    @mock.patch.object(migration, 'db_version', return_value=2)
    def test_version(self, mock_db_version):
        self.commands.version()
        mock_db_version.assert_called_once_with()

    @mock.patch.object(migration, 'db_sync')
    def test_sync(self, mock_db_sync):
        self.commands.sync(version=4, local_cell=True)
        mock_db_sync.assert_called_once_with(4)

    @mock.patch.object(migration, 'db_sync')
    @mock.patch.object(objects.CellMapping, 'get_by_uuid', return_value='map')
    def test_sync_cell0(self, mock_get_by_uuid, mock_db_sync):
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()
        with test.nested(
                mock.patch('nova.context.RequestContext',
                           return_value=ctxt),
                mock.patch('nova.context.target_cell')) \
                                   as (mock_get_context,
                                       mock_target_cell):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock
            self.commands.sync(version=4)
            mock_get_by_uuid.assert_called_once_with(ctxt,
                                        objects.CellMapping.CELL0_UUID)
            mock_target_cell.assert_called_once_with(ctxt, 'map')

            db_sync_calls = [
                mock.call(4, context=cell_ctxt),
                mock.call(4)
            ]
            mock_db_sync.assert_has_calls(db_sync_calls)

    @mock.patch.object(objects.CellMapping, 'get_by_uuid',
                       side_effect=test.TestingException('invalid connection'))
    def test_sync_cell0_unknown_error(self, mock_get_by_uuid):
        """Asserts that a detailed error message is given when an unknown
        error occurs trying to get the cell0 cell mapping.
        """
        result = self.commands.sync()
        self.assertEqual(1, result)
        mock_get_by_uuid.assert_called_once_with(
            test.MatchType(context.RequestContext),
            objects.CellMapping.CELL0_UUID)
        expected = """ERROR: Could not access cell0.
Has the nova_api database been created?
Has the nova_cell0 database been created?
Has "nova-manage api_db sync" been run?
Has "nova-manage cell_v2 map_cell0" been run?
Is [api_database]/connection set in nova.conf?
Is the cell0 database connection URL correct?
Error: invalid connection
"""
        self.assertEqual(expected, self.output.getvalue())

    def _fake_db_command(self, migrations=None):
        if migrations is None:
            mock_mig_1 = mock.MagicMock(__name__="mock_mig_1")
            mock_mig_2 = mock.MagicMock(__name__="mock_mig_2")
            mock_mig_1.return_value = (5, 4)
            mock_mig_2.return_value = (6, 6)
            migrations = (mock_mig_1, mock_mig_2)

        class _CommandSub(manage.DbCommands):
            online_migrations = migrations

        return _CommandSub

    @mock.patch('nova.context.get_admin_context')
    def test_online_migrations(self, mock_get_context):
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
        ctxt = mock_get_context.return_value
        command_cls = self._fake_db_command()
        command = command_cls()
        command.online_data_migrations(10)
        command_cls.online_migrations[0].assert_called_once_with(ctxt, 10)
        command_cls.online_migrations[1].assert_called_once_with(ctxt, 6)
        expected = """\
5 rows matched query mock_mig_1, 4 migrated
6 rows matched query mock_mig_2, 6 migrated
+------------+--------------+-----------+
| Migration  | Total Needed | Completed |
+------------+--------------+-----------+
| mock_mig_1 |      5       |     4     |
| mock_mig_2 |      6       |     6     |
+------------+--------------+-----------+
"""
        self.assertEqual(expected, sys.stdout.getvalue())

    @mock.patch('nova.context.get_admin_context')
    def test_online_migrations_no_max_count(self, mock_get_context):
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', StringIO()))
        total = [120]
        batches = [50, 40, 30, 0]
        runs = []

        def fake_migration(context, count):
            self.assertEqual(mock_get_context.return_value, context)
            runs.append(count)
            count = batches.pop(0)
            total[0] -= count
            return count, count

        command_cls = self._fake_db_command((fake_migration,))
        command = command_cls()
        command.online_data_migrations(None)
        expected = """\
Running batches of 50 until complete
50 rows matched query fake_migration, 50 migrated
40 rows matched query fake_migration, 40 migrated
30 rows matched query fake_migration, 30 migrated
+----------------+--------------+-----------+
|   Migration    | Total Needed | Completed |
+----------------+--------------+-----------+
| fake_migration |     120      |    120    |
+----------------+--------------+-----------+
"""
        self.assertEqual(expected, sys.stdout.getvalue())
        self.assertEqual([], batches)
        self.assertEqual(0, total[0])
        self.assertEqual([50, 50, 50, 50], runs)

    @mock.patch('nova.context.get_admin_context')
    def test_online_migrations_error(self, mock_get_context):
        good_remaining = [50]

        def good_migration(context, count):
            self.assertEqual(mock_get_context.return_value, context)
            found = good_remaining[0]
            done = min(found, count)
            good_remaining[0] -= done
            return found, done

        bad_migration = mock.MagicMock()
        bad_migration.side_effect = test.TestingException
        bad_migration.__name__ = 'bad'

        command_cls = self._fake_db_command((bad_migration, good_migration))
        command = command_cls()

        # bad_migration raises an exception, but it could be because
        # good_migration had not completed yet. We should get 1 in this case,
        # because some work was done, and the command should be reiterated.
        self.assertEqual(1, command.online_data_migrations(max_count=50))

        # When running this for the second time, there's no work left for
        # good_migration to do, but bad_migration still fails - should
        # get 2 this time.
        self.assertEqual(2, command.online_data_migrations(max_count=50))

        # When --max-count is not used, we should get 2 if all possible
        # migrations completed but some raise exceptions
        good_remaining = [125]
        self.assertEqual(2, command.online_data_migrations(None))

    def test_online_migrations_bad_max(self):
        self.assertEqual(127,
                         self.commands.online_data_migrations(max_count=-2))
        self.assertEqual(127,
                         self.commands.online_data_migrations(max_count='a'))
        self.assertEqual(127,
                         self.commands.online_data_migrations(max_count=0))

    def test_online_migrations_no_max(self):
        with mock.patch.object(self.commands, '_run_migration') as rm:
            rm.return_value = {}, False
            self.assertEqual(0,
                             self.commands.online_data_migrations())

    def test_online_migrations_finished(self):
        with mock.patch.object(self.commands, '_run_migration') as rm:
            rm.return_value = {}, False
            self.assertEqual(0,
                             self.commands.online_data_migrations(max_count=5))

    def test_online_migrations_not_finished(self):
        with mock.patch.object(self.commands, '_run_migration') as rm:
            rm.return_value = {'mig': (10, 5)}, False
            self.assertEqual(1,
                             self.commands.online_data_migrations(max_count=5))


class ApiDbCommandsTestCase(test.NoDBTestCase):
    def setUp(self):
        super(ApiDbCommandsTestCase, self).setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        self.commands = manage.ApiDbCommands()

    @mock.patch.object(migration, 'db_version', return_value=2)
    def test_version(self, mock_db_version):
        self.commands.version()
        mock_db_version.assert_called_once_with(database='api')

    @mock.patch.object(migration, 'db_sync')
    def test_sync(self, mock_db_sync):
        self.commands.sync(version=4)
        mock_db_sync.assert_called_once_with(4, database='api')


@ddt.ddt
class CellV2CommandsTestCase(test.NoDBTestCase):
    USES_DB_SELF = True

    def setUp(self):
        super(CellV2CommandsTestCase, self).setUp()
        self.useFixture(nova_fixtures.Database())
        self.useFixture(nova_fixtures.Database(database='api'))
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        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 = self.output.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 = self.output.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).'
        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()

        # NOTE(danms): Create a second node on one compute to make sure
        # we handle that case
        compute_node = objects.ComputeNode(ctxt, host='host0', **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 = self.output.getvalue().strip()
        expected = ''
        for i in [0, 1, 0]:
            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 = self.output.getvalue().strip()
        expected = 'No hosts found to map to cell, exiting.'
        self.assertEqual(expected, output)

    def test_map_cell_and_hosts_no_transport_url(self):
        self.flags(transport_url=None)
        retval = self.commands.map_cell_and_hosts()
        self.assertEqual(1, retval)
        output = self.output.getvalue().strip()
        expected = ('Must specify --transport-url if [DEFAULT]/transport_url '
                    'is not set in the configuration file.')
        self.assertEqual(expected, output)

    def test_map_cell_and_hosts_transport_url_config(self):
        self.flags(transport_url = "fake://guest:devstack@127.0.0.1:9999/")
        retval = self.commands.map_cell_and_hosts()
        self.assertEqual(0, retval)

    @mock.patch.object(context, 'target_cell')
    def test_map_instances(self, mock_target_cell):
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(
                ctxt, uuid=cell_uuid, name='fake',
                transport_url='fake://', database_connection='fake://')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        instance_uuids = []
        for i in range(3):
            uuid = uuidutils.generate_uuid()
            instance_uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id,
                             user_id=ctxt.user_id, uuid=uuid).create()

        self.commands.map_instances(cell_uuid)

        for uuid in instance_uuids:
            inst_mapping = objects.InstanceMapping.get_by_instance_uuid(ctxt,
                    uuid)
            self.assertEqual(ctxt.project_id, inst_mapping.project_id)
            # Verify that map_instances populates user_id.
            self.assertEqual(ctxt.user_id, inst_mapping.user_id)
            self.assertEqual(cell_mapping.uuid, inst_mapping.cell_mapping.uuid)
        mock_target_cell.assert_called_once_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))

    @mock.patch.object(context, 'target_cell')
    def test_map_instances_duplicates(self, mock_target_cell):
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(
                ctxt, uuid=cell_uuid, name='fake',
                transport_url='fake://', database_connection='fake://')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        instance_uuids = []
        for i in range(3):
            uuid = uuidutils.generate_uuid()
            instance_uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id,
                             user_id=ctxt.user_id, uuid=uuid).create()

        objects.InstanceMapping(ctxt, project_id=ctxt.project_id,
                user_id=ctxt.user_id, instance_uuid=instance_uuids[0],
                cell_mapping=cell_mapping).create()

        self.commands.map_instances(cell_uuid)

        for uuid in instance_uuids:
            inst_mapping = objects.InstanceMapping.get_by_instance_uuid(ctxt,
                    uuid)
            self.assertEqual(ctxt.project_id, inst_mapping.project_id)

        mappings = objects.InstanceMappingList.get_by_project_id(ctxt,
                ctxt.project_id)
        self.assertEqual(3, len(mappings))
        mock_target_cell.assert_called_once_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))

    @mock.patch.object(context, 'target_cell')
    def test_map_instances_two_batches(self, mock_target_cell):
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(
                ctxt, uuid=cell_uuid, name='fake',
                transport_url='fake://', database_connection='fake://')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        instance_uuids = []
        # Batch size is 50 in map_instances
        for i in range(60):
            uuid = uuidutils.generate_uuid()
            instance_uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id,
                             user_id=ctxt.user_id, uuid=uuid).create()

        ret = self.commands.map_instances(cell_uuid)
        self.assertEqual(0, ret)

        for uuid in instance_uuids:
            inst_mapping = objects.InstanceMapping.get_by_instance_uuid(ctxt,
                    uuid)
            self.assertEqual(ctxt.project_id, inst_mapping.project_id)
        self.assertEqual(2, mock_target_cell.call_count)
        mock_target_cell.assert_called_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))

    @mock.patch.object(context, 'target_cell')
    def test_map_instances_max_count(self, mock_target_cell):
        # NOTE(gibi): map_instances command uses non canonical UUID
        # serialization for the marker instance mapping. The db schema is not
        # violated so we suppress the warning here.
        warnings.filterwarnings('ignore', message=".*invalid UUID.*")
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(
                ctxt, uuid=cell_uuid, name='fake',
                transport_url='fake://', database_connection='fake://')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        instance_uuids = []
        for i in range(6):
            uuid = uuidutils.generate_uuid()
            instance_uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id,
                             user_id=ctxt.user_id, uuid=uuid).create()

        ret = self.commands.map_instances(cell_uuid, max_count=3)
        self.assertEqual(1, ret)

        for uuid in instance_uuids[:3]:
            # First three are mapped
            inst_mapping = objects.InstanceMapping.get_by_instance_uuid(ctxt,
                    uuid)
            self.assertEqual(ctxt.project_id, inst_mapping.project_id)
        for uuid in instance_uuids[3:]:
            # Last three are not
            self.assertRaises(exception.InstanceMappingNotFound,
                    objects.InstanceMapping.get_by_instance_uuid, ctxt,
                    uuid)
        mock_target_cell.assert_called_once_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))

    @mock.patch.object(context, 'target_cell')
    def test_map_instances_marker_deleted(self, mock_target_cell):
        # NOTE(gibi): map_instances command uses non canonical UUID
        # serialization for the marker instance mapping. The db schema is not
        # violated so we suppress the warning here.
        warnings.filterwarnings('ignore', message=".*invalid UUID.*")
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(
                ctxt, uuid=cell_uuid, name='fake',
                transport_url='fake://', database_connection='fake://')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        instance_uuids = []
        for i in range(6):
            uuid = uuidutils.generate_uuid()
            instance_uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id,
                             user_id=ctxt.user_id, uuid=uuid).create()

        ret = self.commands.map_instances(cell_uuid, max_count=3)
        self.assertEqual(1, ret)

        # Instances are mapped in the order created so we know the marker is
        # based off the third instance.
        marker = instance_uuids[2].replace('-', ' ')
        marker_mapping = objects.InstanceMapping.get_by_instance_uuid(ctxt,
                marker)
        marker_mapping.destroy()

        ret = self.commands.map_instances(cell_uuid)
        self.assertEqual(0, ret)

        for uuid in instance_uuids:
            inst_mapping = objects.InstanceMapping.get_by_instance_uuid(ctxt,
                    uuid)
            self.assertEqual(ctxt.project_id, inst_mapping.project_id)
        self.assertEqual(2, mock_target_cell.call_count)
        mock_target_cell.assert_called_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))

    @mock.patch.object(context, 'target_cell')
    def test_map_instances_marker_reset(self, mock_target_cell):
        # NOTE(gibi): map_instances command uses non canonical UUID
        # serialization for the marker instance mapping. The db schema is not
        # violated so we suppress the warning here.
        warnings.filterwarnings('ignore', message=".*invalid UUID.*")
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(
                ctxt, uuid=cell_uuid, name='fake',
                transport_url='fake://', database_connection='fake://')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        instance_uuids = []
        for i in range(5):
            uuid = uuidutils.generate_uuid()
            instance_uuids.append(uuid)
            objects.Instance(ctxt, project_id=ctxt.project_id,
                             user_id=ctxt.user_id, uuid=uuid).create()

        # Maps first three instances.
        ret = self.commands.map_instances(cell_uuid, max_count=3)
        self.assertEqual(1, ret)

        # Verifying that the marker is now based on third instance
        # i.e the position of the marker.
        inst_mappings = objects.InstanceMappingList.get_by_project_id(ctxt,
                                        'INSTANCE_MIGRATION_MARKER')
        marker = inst_mappings[0].instance_uuid.replace(' ', '-')
        self.assertEqual(instance_uuids[2], marker)

        # Now calling reset with map_instances max_count=2 would reset
        # the marker as expected and start map_instances from the beginning.
        # This implies we end up finding the marker based on second instance.
        ret = self.commands.map_instances(cell_uuid, max_count=2,
                                          reset_marker=True)
        self.assertEqual(1, ret)

        inst_mappings = objects.InstanceMappingList.get_by_project_id(ctxt,
                                        'INSTANCE_MIGRATION_MARKER')
        marker = inst_mappings[0].instance_uuid.replace(' ', '-')
        self.assertEqual(instance_uuids[1], marker)

        # Maps 4th instance using the marker (3rd is already mapped).
        ret = self.commands.map_instances(cell_uuid, max_count=2)
        self.assertEqual(1, ret)

        # Verifying that the marker is now based on fourth instance
        # i.e the position of the marker.
        inst_mappings = objects.InstanceMappingList.get_by_project_id(ctxt,
                                        'INSTANCE_MIGRATION_MARKER')
        marker = inst_mappings[0].instance_uuid.replace(' ', '-')
        self.assertEqual(instance_uuids[3], marker)

        # Maps first four instances (all four duplicate entries which
        # are already present from previous calls)
        ret = self.commands.map_instances(cell_uuid, max_count=4,
                                          reset_marker=True)
        self.assertEqual(1, ret)

        # Verifying that the marker is still based on fourth instance
        # i.e the position of the marker.
        inst_mappings = objects.InstanceMappingList.get_by_project_id(ctxt,
                                        'INSTANCE_MIGRATION_MARKER')
        marker = inst_mappings[0].instance_uuid.replace(' ', '-')
        self.assertEqual(instance_uuids[3], marker)

        # Maps the 5th instance.
        ret = self.commands.map_instances(cell_uuid)
        self.assertEqual(0, ret)

    def test_map_instances_validate_cell_uuid(self):
        # create a random cell_uuid which is invalid
        cell_uuid = uuidutils.generate_uuid()
        # check that it raises an exception
        self.assertRaises(exception.CellMappingNotFound,
            self.commands.map_instances, cell_uuid)

    def test_map_cell0(self):
        ctxt = context.RequestContext()
        database_connection = 'fake:/foobar//'
        self.commands.map_cell0(database_connection)
        cell_mapping = objects.CellMapping.get_by_uuid(ctxt,
                objects.CellMapping.CELL0_UUID)
        self.assertEqual('cell0', cell_mapping.name)
        self.assertEqual('none:///', cell_mapping.transport_url)
        self.assertEqual(database_connection, cell_mapping.database_connection)

    @mock.patch.object(manage.CellV2Commands, '_map_cell0', new=mock.Mock())
    def test_map_cell0_returns_0_on_successful_create(self):
        self.assertEqual(0, self.commands.map_cell0())

    @mock.patch.object(manage.CellV2Commands, '_map_cell0')
    def test_map_cell0_returns_0_if_cell0_already_exists(self, _map_cell0):
        _map_cell0.side_effect = db_exc.DBDuplicateEntry
        exit_code = self.commands.map_cell0()
        self.assertEqual(0, exit_code)
        output = self.output.getvalue().strip()
        self.assertEqual('Cell0 is already setup', output)

    def test_map_cell0_default_database(self):
        CONF.set_default('connection',
                         'fake://netloc/nova',
                         group='database')
        ctxt = context.RequestContext()
        self.commands.map_cell0()
        cell_mapping = objects.CellMapping.get_by_uuid(ctxt,
                objects.CellMapping.CELL0_UUID)
        self.assertEqual('cell0', cell_mapping.name)
        self.assertEqual('none:///', cell_mapping.transport_url)
        self.assertEqual('fake://netloc/nova_cell0',
                         cell_mapping.database_connection)

    @ddt.data('mysql+pymysql://nova:abcd0123:AB@controller/%s',
              'mysql+pymysql://nova:abcd0123?AB@controller/%s',
              'mysql+pymysql://nova:abcd0123@AB@controller/%s',
              'mysql+pymysql://nova:abcd0123/AB@controller/%s',
              'mysql+pymysql://test:abcd0123/AB@controller/%s?charset=utf8')
    def test_map_cell0_default_database_special_characters(self,
                                                           connection):
        """Tests that a URL with special characters, like in the credentials,
        is handled properly.
        """
        decoded_connection = connection % 'nova'
        self.flags(connection=decoded_connection, group='database')
        ctxt = context.RequestContext()
        self.commands.map_cell0()
        cell_mapping = objects.CellMapping.get_by_uuid(
            ctxt, objects.CellMapping.CELL0_UUID)
        self.assertEqual('cell0', cell_mapping.name)
        self.assertEqual('none:///', cell_mapping.transport_url)
        self.assertEqual(
            connection % 'nova_cell0',
            cell_mapping.database_connection)
        # Delete the cell mapping for the next iteration.
        cell_mapping.destroy()

    def _test_migrate_simple_command(self, cell0_sync_fail=False):
        ctxt = context.RequestContext()
        CONF.set_default('connection',
                         'fake://netloc/nova',
                         group='database')
        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()

        transport_url = "fake://guest:devstack@127.0.0.1:9999/"
        cell_uuid = uuidsentinel.cell

        @mock.patch('nova.db.migration.db_sync')
        @mock.patch.object(context, 'target_cell')
        @mock.patch.object(uuidutils, 'generate_uuid',
                return_value=cell_uuid)
        def _test(mock_gen_uuid, mock_target_cell, mock_db_sync):
            if cell0_sync_fail:
                mock_db_sync.side_effect = db_exc.DBError
            result = self.commands.simple_cell_setup(transport_url)
            mock_db_sync.assert_called()
            return result

        r = _test()
        self.assertEqual(0, r)

        # Check cell0 from default
        cell_mapping = objects.CellMapping.get_by_uuid(ctxt,
                objects.CellMapping.CELL0_UUID)
        self.assertEqual('cell0', cell_mapping.name)
        self.assertEqual('none:///', cell_mapping.transport_url)
        self.assertEqual('fake://netloc/nova_cell0',
                         cell_mapping.database_connection)

        # Verify the cell mapping
        cell_mapping = objects.CellMapping.get_by_uuid(ctxt, cell_uuid)
        self.assertEqual(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_simple_command_single(self):
        self._test_migrate_simple_command()

    def test_simple_command_cell0_fail(self):
        # Make sure that if db_sync fails, we still do all the other
        # bits
        self._test_migrate_simple_command(cell0_sync_fail=True)

    def test_simple_command_multiple(self):
        # Make sure that the command is idempotent
        self._test_migrate_simple_command()
        self._test_migrate_simple_command()

    def test_instance_verify_no_mapping(self):
        r = self.commands.verify_instance(uuidsentinel.instance)
        self.assertEqual(1, r)

    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_instance_verify_has_only_instance_mapping(self, mock_get):
        im = objects.InstanceMapping(cell_mapping=None)
        mock_get.return_value = im
        r = self.commands.verify_instance(uuidsentinel.instance)
        self.assertEqual(2, r)

    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch.object(context, 'target_cell')
    def test_instance_verify_has_all_mappings(self, mock_target_cell,
                                                mock_get2, mock_get1):
        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cel)
        im = objects.InstanceMapping(cell_mapping=cm)
        mock_get1.return_value = im
        mock_get2.return_value = None
        r = self.commands.verify_instance(uuidsentinel.instance)
        self.assertEqual(0, r)

    def test_instance_verify_quiet(self):
        # NOTE(danms): This will hit the first use of the say() wrapper
        # and reasonably verify that path
        self.assertEqual(1, self.commands.verify_instance(uuidsentinel.foo,
                                                          quiet=True))

    @mock.patch.object(context, 'target_cell')
    def test_instance_verify_has_instance_mapping_but_no_instance(self,
                                                    mock_target_cell):
        ctxt = context.RequestContext('fake-user', 'fake_project')
        cell_uuid = uuidutils.generate_uuid()
        cell_mapping = objects.CellMapping(context=ctxt,
                                            uuid=cell_uuid,
                                            database_connection='fake:///db',
                                            transport_url='fake:///mq')
        cell_mapping.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        uuid = uuidutils.generate_uuid()
        objects.Instance(ctxt, project_id=ctxt.project_id, uuid=uuid).create()
        objects.InstanceMapping(ctxt, project_id=ctxt.project_id,
                                cell_mapping=cell_mapping, instance_uuid=uuid)\
                                .create()
        # a scenario where an instance is deleted, but not archived.
        inst = objects.Instance.get_by_uuid(ctxt, uuid)
        inst.destroy()
        r = self.commands.verify_instance(uuid)
        self.assertEqual(3, r)
        self.assertIn('has been deleted', self.output.getvalue())
        # a scenario where there is only the instance mapping but no instance
        # like when an instance has been archived but the instance mapping
        # was not deleted.
        uuid = uuidutils.generate_uuid()
        objects.InstanceMapping(ctxt, project_id=ctxt.project_id,
                                cell_mapping=cell_mapping, instance_uuid=uuid)\
                                .create()
        r = self.commands.verify_instance(uuid)
        self.assertEqual(4, r)
        self.assertIn('has been archived', self.output.getvalue())

    def _return_compute_nodes(self, ctxt, num=1):
        nodes = []
        for i in range(num):
            nodes.append(objects.ComputeNode(ctxt,
                                             uuid=uuidutils.generate_uuid(),
                                             host='host%s' % i,
                                             vcpus=1,
                                             memory_mb=1,
                                             local_gb=1,
                                             vcpus_used=0,
                                             memory_mb_used=0,
                                             local_gb_used=0,
                                             hypervisor_type='',
                                             hypervisor_version=1,
                                             cpu_info=''))
        return nodes

    @mock.patch.object(context, 'target_cell')
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_discover_hosts_single_cell(self, mock_cell_mapping_get_all,
                                        mock_target_cell):
        ctxt = context.RequestContext()

        compute_nodes = self._return_compute_nodes(ctxt)
        for compute_node in compute_nodes:
            compute_node.create()

        cell_mapping = objects.CellMapping(context=ctxt,
                                           uuid=uuidutils.generate_uuid(),
                                           database_connection='fake:///db',
                                           transport_url='fake:///mq')
        cell_mapping.create()

        mock_target_cell.return_value.__enter__.return_value = ctxt

        self.commands.discover_hosts(cell_uuid=cell_mapping.uuid)

        # Check that the host mappings were created
        for i, compute_node in enumerate(compute_nodes):
            host_mapping = objects.HostMapping.get_by_host(ctxt,
                                                           compute_node.host)
            self.assertEqual('host%s' % i, host_mapping.host)

        mock_target_cell.assert_called_once_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))
        mock_cell_mapping_get_all.assert_not_called()

    @mock.patch.object(context, 'target_cell')
    @mock.patch.object(objects.CellMappingList, 'get_all')
    def test_discover_hosts_single_cell_no_new_hosts(
            self, mock_cell_mapping_get_all, mock_target_cell):
        ctxt = context.RequestContext()

        # Create some compute nodes and matching host mappings
        cell_mapping = objects.CellMapping(context=ctxt,
                                           uuid=uuidutils.generate_uuid(),
                                           database_connection='fake:///db',
                                           transport_url='fake:///mq')
        cell_mapping.create()

        compute_nodes = self._return_compute_nodes(ctxt)
        for compute_node in compute_nodes:
            compute_node.create()
            host_mapping = objects.HostMapping(context=ctxt,
                                               host=compute_node.host,
                                               cell_mapping=cell_mapping)
            host_mapping.create()

        with mock.patch('nova.objects.HostMapping.create') as mock_create:
            self.commands.discover_hosts(cell_uuid=cell_mapping.uuid)
            mock_create.assert_not_called()

        mock_target_cell.assert_called_once_with(
            test.MatchType(context.RequestContext),
            test.MatchObjPrims(cell_mapping))
        mock_cell_mapping_get_all.assert_not_called()

    @mock.patch.object(objects.CellMapping, 'get_by_uuid')
    def test_discover_hosts_multiple_cells(self,
                                           mock_cell_mapping_get_by_uuid):
        # Create in-memory databases for cell1 and cell2 to let target_cell
        # run for real. We want one compute node in cell1's db and the other
        # compute node in cell2's db.
        cell_dbs = nova_fixtures.CellDatabases()
        cell_dbs.add_cell_database('fake:///db1')
        cell_dbs.add_cell_database('fake:///db2')
        self.useFixture(cell_dbs)

        ctxt = context.RequestContext()

        cell_mapping0 = objects.CellMapping(
            context=ctxt,
            uuid=objects.CellMapping.CELL0_UUID,
            database_connection='fake:///db0',
            transport_url='none:///')
        cell_mapping0.create()
        cell_mapping1 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db1',
                                            transport_url='fake:///mq1')
        cell_mapping1.create()
        cell_mapping2 = objects.CellMapping(context=ctxt,
                                            uuid=uuidutils.generate_uuid(),
                                            database_connection='fake:///db2',
                                            transport_url='fake:///mq2')
        cell_mapping2.create()

        compute_nodes = self._return_compute_nodes(ctxt, num=2)
        # Create the first compute node in cell1's db
        with context.target_cell(ctxt, cell_mapping1) as cctxt:
            compute_nodes[0]._context = cctxt
            compute_nodes[0].create()
        # Create the first compute node in cell2's db
        with context.target_cell(ctxt, cell_mapping2) as cctxt:
            compute_nodes[1]._context = cctxt
            compute_nodes[1].create()

        self.commands.discover_hosts(verbose=True)
        output = self.output.getvalue().strip()
        self.assertNotEqual('', output)

        # Check that the host mappings were created
        for i, compute_node in enumerate(compute_nodes):
            host_mapping = objects.HostMapping.get_by_host(ctxt,
                                                           compute_node.host)
            self.assertEqual('host%s' % i, host_mapping.host)

        mock_cell_mapping_get_by_uuid.assert_not_called()

    @mock.patch('nova.objects.host_mapping.discover_hosts')
    def test_discover_hosts_strict(self, mock_discover_hosts):
        # Check for exit code 0 if unmapped hosts found
        mock_discover_hosts.return_value = ['fake']
        self.assertEqual(self.commands.discover_hosts(strict=True), 0)

        # Check for exit code 1 if no unmapped hosts are found
        mock_discover_hosts.return_value = []
        self.assertEqual(self.commands.discover_hosts(strict=True), 1)

        # Check the return when strict=False
        self.assertIsNone(self.commands.discover_hosts())

    @mock.patch('nova.objects.host_mapping.discover_hosts')
    def test_discover_hosts_by_service(self, mock_discover_hosts):
        mock_discover_hosts.return_value = ['fake']
        ret = self.commands.discover_hosts(by_service=True, strict=True)
        self.assertEqual(0, ret)
        mock_discover_hosts.assert_called_once_with(mock.ANY, None,
                                                    mock.ANY,
                                                    True)

    @mock.patch('nova.objects.host_mapping.discover_hosts')
    def test_discover_hosts_mapping_exists(self, mock_discover_hosts):
        mock_discover_hosts.side_effect = exception.HostMappingExists(
            name='fake')
        ret = self.commands.discover_hosts()
        output = self.output.getvalue().strip()
        self.assertEqual(2, ret)
        expected = ("ERROR: Duplicate host mapping was encountered. This "
                    "command should be run once after all compute hosts "
                    "have been deployed and should not be run in parallel. "
                    "When run in parallel, the commands will collide with "
                    "each other trying to map the same hosts in the database "
                    "at the same time. Error: Host 'fake' mapping already "
                    "exists")
        self.assertEqual(expected, output)

    def test_validate_transport_url_in_conf(self):
        from_conf = 'fake://user:pass@host:5672/'
        self.flags(transport_url=from_conf)
        self.assertEqual(from_conf,
                         self.commands._validate_transport_url(None))

    def test_validate_transport_url_on_command_line(self):
        from_cli = 'fake://user:pass@host:5672/'
        self.assertEqual(from_cli,
                         self.commands._validate_transport_url(from_cli))

    def test_validate_transport_url_missing(self):
        self.flags(transport_url=None)
        self.assertIsNone(self.commands._validate_transport_url(None))

    def test_validate_transport_url_favors_command_line(self):
        self.flags(transport_url='fake://user:pass@host:5672/')
        from_cli = 'fake://otheruser:otherpass@otherhost:5673'
        self.assertEqual(from_cli,
                         self.commands._validate_transport_url(from_cli))

    def test_validate_transport_url_invalid_url(self):
        self.assertIsNone(self.commands._validate_transport_url('not-a-url'))
        self.assertIn('Invalid transport URL', self.output.getvalue())

    def test_non_unique_transport_url_database_connection_checker(self):
        ctxt = context.RequestContext()
        cell1 = objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq1',
                            database_connection='fake:///db1')
        cell1.create()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell2,
                            name='cell2',
                            transport_url='fake://mq2',
                            database_connection='fake:///db2').create()
        resultf = self.commands.\
                    _non_unique_transport_url_database_connection_checker(
                                        ctxt, None,
                                        'fake://mq3', 'fake:///db3')
        resultt = self.commands.\
                    _non_unique_transport_url_database_connection_checker(
                                        ctxt, None,
                                        'fake://mq1', 'fake:///db1')
        resultd = self.commands.\
                    _non_unique_transport_url_database_connection_checker(
                                        ctxt, cell1,
                                        'fake://mq1', 'fake:///db1')

        self.assertFalse(resultf)
        self.assertTrue(resultt)
        self.assertFalse(resultd)
        self.assertIn('exists', self.output.getvalue())

    def test_create_cell_use_params(self):
        ctxt = context.get_context()
        kwargs = dict(
            name='fake-name',
            transport_url='http://fake-transport-url',
            database_connection='fake-db-connection')
        status = self.commands.create_cell(verbose=True, **kwargs)
        self.assertEqual(0, status)
        cell2_uuid = self.output.getvalue().strip()
        self.commands.create_cell(**kwargs)
        cell2 = objects.CellMapping.get_by_uuid(ctxt, cell2_uuid)
        self.assertEqual(kwargs['name'], cell2.name)
        self.assertEqual(kwargs['database_connection'],
                         cell2.database_connection)
        self.assertEqual(kwargs['transport_url'], cell2.transport_url)
        self.assertIs(cell2.disabled, False)

    def test_create_cell_use_params_with_template(self):
        ctxt = context.get_context()
        self.flags(transport_url='rabbit://host:1234')
        kwargs = dict(
            name='fake-name',
            transport_url='{scheme}://other-{hostname}:{port}',
            database_connection='fake-db-connection')
        status = self.commands.create_cell(verbose=True, **kwargs)
        self.assertEqual(0, status)
        cell2_uuid = self.output.getvalue().strip()
        self.commands.create_cell(**kwargs)

        # Make sure it ended up as a template in the database
        db_cm = objects.CellMapping._get_by_uuid_from_db(ctxt, cell2_uuid)
        self.assertEqual('{scheme}://other-{hostname}:{port}',
                         db_cm.transport_url)

        # Make sure it gets translated if we load by object
        cell2 = objects.CellMapping.get_by_uuid(ctxt, cell2_uuid)
        self.assertEqual(kwargs['name'], cell2.name)
        self.assertEqual(kwargs['database_connection'],
                         cell2.database_connection)
        self.assertEqual('rabbit://other-host:1234', cell2.transport_url)
        self.assertIs(cell2.disabled, False)

    def test_create_cell_use_config_values(self):
        settings = dict(
            transport_url='http://fake-conf-transport-url',
            database_connection='fake-conf-db-connection')
        self.flags(connection=settings['database_connection'],
                   group='database')
        self.flags(transport_url=settings['transport_url'])
        ctxt = context.get_context()

        status = self.commands.create_cell(verbose=True)
        self.assertEqual(0, status)
        cell1_uuid = self.output.getvalue().split('\n')[-2].strip()
        cell1 = objects.CellMapping.get_by_uuid(ctxt, cell1_uuid)
        self.assertIsNone(cell1.name)
        self.assertEqual(settings['database_connection'],
                         cell1.database_connection)
        self.assertEqual(settings['transport_url'], cell1.transport_url)

    def test_create_cell_failed_if_non_unique(self):
        kwargs = dict(
            name='fake-name',
            transport_url='http://fake-transport-url',
            database_connection='fake-db-connection')
        status1 = self.commands.create_cell(verbose=True, **kwargs)
        status2 = self.commands.create_cell(verbose=True, **kwargs)
        self.assertEqual(0, status1)
        self.assertEqual(2, status2)
        self.assertIn('exists', self.output.getvalue())

    def test_create_cell_failed_if_no_transport_url(self):
        self.flags(transport_url=None)
        status = self.commands.create_cell()
        self.assertEqual(1, status)
        self.assertIn('--transport-url', self.output.getvalue())

    def test_create_cell_failed_if_no_database_connection(self):
        self.flags(connection=None, group='database')
        status = self.commands.create_cell(transport_url='http://fake-url')
        self.assertEqual(1, status)
        self.assertIn('--database_connection', self.output.getvalue())

    def test_create_cell_pre_disabled(self):
        ctxt = context.get_context()
        kwargs = dict(
            name='fake-name1',
            transport_url='http://fake-transport-url1',
            database_connection='fake-db-connection1')
        status1 = self.commands.create_cell(verbose=True, disabled=True,
                                            **kwargs)
        self.assertEqual(0, status1)
        cell_uuid1 = self.output.getvalue().strip()
        cell1 = objects.CellMapping.get_by_uuid(ctxt, cell_uuid1)
        self.assertEqual(kwargs['name'], cell1.name)
        self.assertIs(cell1.disabled, True)

    def test_list_cells_verbose_false(self):
        ctxt = context.RequestContext()
        cell_mapping0 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map0,
            database_connection='fake://user1:pass1@host1/db0',
            transport_url='none://user1:pass1@host1/',
            name='cell0')
        cell_mapping0.create()
        cell_mapping1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map1,
            database_connection='fake://user1@host1/db0',
            transport_url='none://user1@host1/vhost1',
            name='cell1')
        cell_mapping1.create()
        self.assertEqual(0, self.commands.list_cells())
        output = self.output.getvalue().strip()
        self.assertEqual('''\
+-------+--------------------------------------+---------------------------+-----------------------------+----------+
|  Name |                 UUID                 |       Transport URL       |     Database Connection     | Disabled |
+-------+--------------------------------------+---------------------------+-----------------------------+----------+
| cell0 | %(uuid_map0)s |  none://user1:****@host1/ | fake://user1:****@host1/db0 |  False   |
| cell1 | %(uuid_map1)s | none://user1@host1/vhost1 |    fake://user1@host1/db0   |  False   |
+-------+--------------------------------------+---------------------------+-----------------------------+----------+''' %  # noqa
                {"uuid_map0": uuidsentinel.map0,
                 "uuid_map1": uuidsentinel.map1},
                output)

    def test_list_cells_multiple_sorted_verbose_true(self):
        ctxt = context.RequestContext()
        cell_mapping0 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map0,
            database_connection='fake:///db0', transport_url='none:///',
            name='cell0')
        cell_mapping0.create()
        cell_mapping1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map1,
            database_connection='fake:///dblon', transport_url='fake:///mqlon',
            name='london', disabled=True)
        cell_mapping1.create()
        cell_mapping2 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map2,
            database_connection='fake:///dbdal', transport_url='fake:///mqdal',
            name='dallas')
        cell_mapping2.create()
        no_name = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.none,
            database_connection='fake:///dbnone',
            transport_url='fake:///mqnone')
        no_name.create()
        self.assertEqual(0, self.commands.list_cells(verbose=True))
        output = self.output.getvalue().strip()
        self.assertEqual('''\
+--------+--------------------------------------+----------------+---------------------+----------+
|  Name  |                 UUID                 | Transport URL  | Database Connection | Disabled |
+--------+--------------------------------------+----------------+---------------------+----------+
|        | %(uuid_none)s | fake:///mqnone |    fake:///dbnone   |  False   |
| cell0  | %(uuid_map0)s |    none:///    |     fake:///db0     |  False   |
| dallas | %(uuid_map2)s | fake:///mqdal  |    fake:///dbdal    |  False   |
| london | %(uuid_map1)s | fake:///mqlon  |    fake:///dblon    |   True   |
+--------+--------------------------------------+----------------+---------------------+----------+''' %  # noqa
                {"uuid_map0": uuidsentinel.map0,
                 "uuid_map1": uuidsentinel.map1,
                 "uuid_map2": uuidsentinel.map2,
                 "uuid_none": uuidsentinel.none},
                output)

    def test_delete_cell_not_found(self):
        """Tests trying to delete a cell that is not found by uuid."""
        cell_uuid = uuidutils.generate_uuid()
        self.assertEqual(1, self.commands.delete_cell(cell_uuid))
        output = self.output.getvalue().strip()
        self.assertEqual('Cell with uuid %s was not found.' % cell_uuid,
                         output)

    @mock.patch.object(objects.ComputeNodeList, 'get_all')
    def test_delete_cell_host_mappings_exist(self, mock_get_cn):
        """Tests trying to delete a cell which has host mappings."""
        cell_uuid = uuidutils.generate_uuid()
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=cell_uuid, database_connection='fake:///db',
            transport_url='fake:///mq')
        cm.create()
        # create a host mapping in this cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm)
        hm.create()
        mock_get_cn.return_value = []
        self.assertEqual(2, self.commands.delete_cell(cell_uuid))
        output = self.output.getvalue().strip()
        self.assertIn('There are existing hosts mapped to cell', output)

    @mock.patch.object(objects.InstanceList, 'get_all')
    def test_delete_cell_instance_mappings_exist_with_instances(
        self, mock_get_all):
        """Tests trying to delete a cell which has instance mappings."""
        cell_uuid = uuidutils.generate_uuid()
        ctxt = context.get_admin_context()
        mock_get_all.return_value = [objects.Instance(
            ctxt, uuid=uuidsentinel.instance)]
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=cell_uuid, database_connection='fake:///db',
            transport_url='fake:///mq')
        cm.create()
        # create an instance mapping in this cell
        im = objects.InstanceMapping(
            context=ctxt, instance_uuid=uuidutils.generate_uuid(),
            cell_mapping=cm, project_id=uuidutils.generate_uuid())
        im.create()
        self.assertEqual(3, self.commands.delete_cell(cell_uuid))
        output = self.output.getvalue().strip()
        self.assertIn('There are existing instances mapped to cell', output)

    @mock.patch.object(objects.InstanceList, 'get_all',
                       return_value=[])
    def test_delete_cell_instance_mappings_exist_without_instances(
        self, mock_get_all):
        """Tests trying to delete a cell which has instance mappings."""
        cell_uuid = uuidutils.generate_uuid()
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=cell_uuid, database_connection='fake:///db',
            transport_url='fake:///mq')
        cm.create()
        # create an instance mapping in this cell
        im = objects.InstanceMapping(
            context=ctxt, instance_uuid=uuidutils.generate_uuid(),
            cell_mapping=cm, project_id=uuidutils.generate_uuid())
        im.create()
        self.assertEqual(4, self.commands.delete_cell(cell_uuid))
        output = self.output.getvalue().strip()
        self.assertIn('There are instance mappings to cell with uuid', output)
        self.assertIn('but all instances have been deleted in the cell.',
                      output)
        self.assertIn("So execute 'nova-manage db archive_deleted_rows' to "
                      "delete the instance mappings.", output)

    def test_delete_cell_success_without_host_mappings(self):
        """Tests trying to delete an empty cell."""
        cell_uuid = uuidutils.generate_uuid()
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=cell_uuid, database_connection='fake:///db',
            transport_url='fake:///mq')
        cm.create()
        self.assertEqual(0, self.commands.delete_cell(cell_uuid))
        output = self.output.getvalue().strip()
        self.assertEqual('', output)

    @mock.patch.object(objects.ComputeNodeList, 'get_all')
    @mock.patch.object(objects.HostMapping, 'destroy')
    @mock.patch.object(objects.CellMapping, 'destroy')
    def test_delete_cell_success_with_host_mappings(self, mock_cell_destroy,
                                            mock_hm_destroy, mock_get_cn):
        """Tests trying to delete a cell with host."""
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm.create()
        # create a host mapping in this cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm)
        hm.create()
        mock_get_cn.return_value = []
        self.assertEqual(0, self.commands.delete_cell(uuidsentinel.cell1,
                                                      force=True))
        output = self.output.getvalue().strip()
        self.assertEqual('', output)
        mock_hm_destroy.assert_called_once_with()
        mock_cell_destroy.assert_called_once_with()

    @mock.patch.object(context, 'target_cell')
    @mock.patch.object(objects.InstanceMapping, 'destroy')
    @mock.patch.object(objects.HostMapping, 'destroy')
    @mock.patch.object(objects.CellMapping, 'destroy')
    def test_delete_cell_force_with_inst_mappings_of_deleted_instances(self,
                                        mock_cell_destroy, mock_hm_destroy,
                                        mock_im_destroy, mock_target_cell):

        # Test for verifying the deletion of instance_mappings
        # of deleted instances when using the --force option
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm.create()
        mock_target_cell.return_value.__enter__.return_value = ctxt
        # create a host mapping in this cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm)
        hm.create()
        # create an instance and its mapping.
        inst_uuid = uuidutils.generate_uuid()
        proj_uuid = uuidutils.generate_uuid()
        instance = objects.Instance(ctxt, project_id=proj_uuid,
                                    uuid=inst_uuid)
        instance.create()

        im = objects.InstanceMapping(ctxt, project_id=proj_uuid,
                                     cell_mapping=cm,
                                     instance_uuid=inst_uuid)
        im.create()

        res = self.commands.delete_cell(uuidsentinel.cell1, force=True)
        self.assertEqual(3, res)
        output = self.output.getvalue().strip()
        self.assertIn('There are existing instances mapped to cell', output)

        # delete the instance such that we now have only its mapping
        instance.destroy()

        res = self.commands.delete_cell(uuidsentinel.cell1, force=True)
        self.assertEqual(0, res)
        mock_hm_destroy.assert_called_once_with()
        mock_cell_destroy.assert_called_once_with()
        mock_im_destroy.assert_called_once_with()
        self.assertEqual(4, mock_target_cell.call_count)

    def test_update_cell_not_found(self):
        self.assertEqual(1, self.commands.update_cell(
            uuidsentinel.cell1, 'foo', 'fake://new', 'fake:///new'))
        self.assertIn('not found', self.output.getvalue())

    def test_update_cell_failed_if_non_unique_transport_db_urls(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq1',
                            database_connection='fake:///db1').create()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell2,
                            name='cell2',
                            transport_url='fake://mq2',
                            database_connection='fake:///db2').create()
        cell2_update1 = self.commands.update_cell(
            uuidsentinel.cell2, 'foo', 'fake://mq1', 'fake:///db1')
        self.assertEqual(3, cell2_update1)
        self.assertIn('exists', self.output.getvalue())

        cell2_update2 = self.commands.update_cell(
            uuidsentinel.cell2, 'foo', 'fake://mq1', 'fake:///db3')
        self.assertEqual(3, cell2_update2)
        self.assertIn('exists', self.output.getvalue())

        cell2_update3 = self.commands.update_cell(
            uuidsentinel.cell2, 'foo', 'fake://mq3', 'fake:///db1')
        self.assertEqual(3, cell2_update3)
        self.assertIn('exists', self.output.getvalue())

        cell2_update4 = self.commands.update_cell(
            uuidsentinel.cell2, 'foo', 'fake://mq3', 'fake:///db3')
        self.assertEqual(0, cell2_update4)

    def test_update_cell_failed(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        with mock.patch('nova.objects.CellMapping.save') as mock_save:
            mock_save.side_effect = Exception
            self.assertEqual(2, self.commands.update_cell(
                uuidsentinel.cell1, 'foo', 'fake://new', 'fake:///new'))
        self.assertIn('Unable to update', self.output.getvalue())

    def test_update_cell_success(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        self.assertEqual(0, self.commands.update_cell(
            uuidsentinel.cell1, 'foo', 'fake://new', 'fake:///new'))
        cm = objects.CellMapping.get_by_uuid(ctxt, uuidsentinel.cell1)
        self.assertEqual('foo', cm.name)
        self.assertEqual('fake://new', cm.transport_url)
        self.assertEqual('fake:///new', cm.database_connection)
        output = self.output.getvalue().strip()
        self.assertEqual('', output)

    def test_update_cell_success_defaults(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        self.assertEqual(0, self.commands.update_cell(uuidsentinel.cell1))
        cm = objects.CellMapping.get_by_uuid(ctxt, uuidsentinel.cell1)
        self.assertEqual('cell1', cm.name)
        expected_transport_url = CONF.transport_url or 'fake://mq'
        self.assertEqual(expected_transport_url, cm.transport_url)
        expected_db_connection = CONF.database.connection or 'fake:///db'
        self.assertEqual(expected_db_connection, cm.database_connection)
        output = self.output.getvalue().strip()
        lines = output.split('\n')
        self.assertIn('using the value [DEFAULT]/transport_url', lines[0])
        self.assertIn('using the value [database]/connection', lines[1])
        self.assertEqual(2, len(lines))

    def test_update_cell_disable_and_enable(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        self.assertEqual(4, self.commands.update_cell(uuidsentinel.cell1,
                                                      disable=True,
                                                      enable=True))
        output = self.output.getvalue().strip()
        self.assertIn('Cell cannot be disabled and enabled at the same '
                      'time.', output)

    def test_update_cell_disable_cell0(self):
        ctxt = context.get_admin_context()
        uuid0 = objects.CellMapping.CELL0_UUID
        objects.CellMapping(context=ctxt, uuid=uuid0, name='cell0',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        self.assertEqual(5, self.commands.update_cell(uuid0, disable=True))
        output = self.output.getvalue().strip()
        self.assertIn('Cell0 cannot be disabled.', output)

    def test_update_cell_disable_success(self):
        ctxt = context.get_admin_context()
        uuid = uuidsentinel.cell1
        objects.CellMapping(context=ctxt, uuid=uuid,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        cm = objects.CellMapping.get_by_uuid(ctxt, uuid)
        self.assertFalse(cm.disabled)
        self.assertEqual(0, self.commands.update_cell(uuid, disable=True))
        cm = objects.CellMapping.get_by_uuid(ctxt, uuid)
        self.assertTrue(cm.disabled)
        output = self.output.getvalue().strip()
        lines = output.split('\n')
        self.assertIn('using the value [DEFAULT]/transport_url', lines[0])
        self.assertIn('using the value [database]/connection', lines[1])
        self.assertEqual(2, len(lines))

    def test_update_cell_enable_success(self):
        ctxt = context.get_admin_context()
        uuid = uuidsentinel.cell1
        objects.CellMapping(context=ctxt, uuid=uuid,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db',
                            disabled=True).create()
        cm = objects.CellMapping.get_by_uuid(ctxt, uuid)
        self.assertTrue(cm.disabled)
        self.assertEqual(0, self.commands.update_cell(uuid, enable=True))
        cm = objects.CellMapping.get_by_uuid(ctxt, uuid)
        self.assertFalse(cm.disabled)
        output = self.output.getvalue().strip()
        lines = output.split('\n')
        self.assertIn('using the value [DEFAULT]/transport_url', lines[0])
        self.assertIn('using the value [database]/connection', lines[1])
        self.assertEqual(2, len(lines))

    def test_update_cell_disable_already_disabled(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db',
                            disabled=True).create()
        cm = objects.CellMapping.get_by_uuid(ctxt, uuidsentinel.cell1)
        self.assertTrue(cm.disabled)
        self.assertEqual(0, self.commands.update_cell(uuidsentinel.cell1,
                                                      disable=True))
        self.assertTrue(cm.disabled)
        output = self.output.getvalue().strip()
        self.assertIn('is already disabled', output)

    def test_update_cell_enable_already_enabled(self):
        ctxt = context.get_admin_context()
        objects.CellMapping(context=ctxt, uuid=uuidsentinel.cell1,
                            name='cell1',
                            transport_url='fake://mq',
                            database_connection='fake:///db').create()
        cm = objects.CellMapping.get_by_uuid(ctxt, uuidsentinel.cell1)
        self.assertFalse(cm.disabled)
        self.assertEqual(0, self.commands.update_cell(uuidsentinel.cell1,
                                                      enable=True))
        self.assertFalse(cm.disabled)
        output = self.output.getvalue().strip()
        self.assertIn('is already enabled', output)

    def test_list_hosts(self):
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map0, name='london',
            database_connection='fake:///db', transport_url='fake:///mq')
        cm1.create()
        cm2 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map1, name='dallas',
            database_connection='fake:///db', transport_url='fake:///mq')
        cm2.create()
        # create a host mapping in another cell
        hm1 = objects.HostMapping(
            context=ctxt, host='fake-host-1', cell_mapping=cm1)
        hm1.create()
        hm2 = objects.HostMapping(
            context=ctxt, host='fake-host-2', cell_mapping=cm2)
        hm2.create()
        self.assertEqual(0, self.commands.list_hosts())
        output = self.output.getvalue().strip()
        self.assertEqual('''\
+-----------+--------------------------------------+-------------+
| Cell Name |              Cell UUID               |   Hostname  |
+-----------+--------------------------------------+-------------+
|   london  | %(uuid_map0)s | fake-host-1 |
|   dallas  | %(uuid_map1)s | fake-host-2 |
+-----------+--------------------------------------+-------------+''' %
                {"uuid_map0": uuidsentinel.map0,
                 "uuid_map1": uuidsentinel.map1},
                output)

    def test_list_hosts_in_cell(self):
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map0, name='london',
            database_connection='fake:///db', transport_url='fake:///mq')
        cm1.create()
        cm2 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.map1, name='dallas',
            database_connection='fake:///db', transport_url='fake:///mq')
        cm2.create()
        # create a host mapping in another cell
        hm1 = objects.HostMapping(
            context=ctxt, host='fake-host-1', cell_mapping=cm1)
        hm1.create()
        hm2 = objects.HostMapping(
            context=ctxt, host='fake-host-2', cell_mapping=cm2)
        hm2.create()
        self.assertEqual(0, self.commands.list_hosts(
            cell_uuid=uuidsentinel.map0))
        output = self.output.getvalue().strip()
        self.assertEqual('''\
+-----------+--------------------------------------+-------------+
| Cell Name |              Cell UUID               |   Hostname  |
+-----------+--------------------------------------+-------------+
|   london  | %(uuid_map0)s | fake-host-1 |
+-----------+--------------------------------------+-------------+''' %
                {"uuid_map0": uuidsentinel.map0},
                output)

    def test_list_hosts_cell_not_found(self):
        """Tests trying to delete a host but a specified cell is not found."""
        self.assertEqual(1, self.commands.list_hosts(
            cell_uuid=uuidsentinel.cell1))
        output = self.output.getvalue().strip()
        self.assertEqual(
            'Cell with uuid %s was not found.' % uuidsentinel.cell1, output)

    def test_delete_host_cell_not_found(self):
        """Tests trying to delete a host but a specified cell is not found."""
        self.assertEqual(1, self.commands.delete_host(uuidsentinel.cell1,
                                                      'fake-host'))
        output = self.output.getvalue().strip()
        self.assertEqual(
            'Cell with uuid %s was not found.' % uuidsentinel.cell1, output)

    def test_delete_host_host_not_found(self):
        """Tests trying to delete a host but the host is not found."""
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm.create()
        self.assertEqual(2, self.commands.delete_host(uuidsentinel.cell1,
                                                      'fake-host'))
        output = self.output.getvalue().strip()
        self.assertEqual('The host fake-host was not found.', output)

    def test_delete_host_host_not_in_cell(self):
        """Tests trying to delete a host
        but the host does not belongs to a specified cell.
        """
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm1.create()
        cm2 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell2,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm2.create()
        # create a host mapping in another cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm2)
        hm.create()
        self.assertEqual(3, self.commands.delete_host(uuidsentinel.cell1,
                                                      'fake-host'))
        output = self.output.getvalue().strip()
        self.assertEqual(('The host fake-host was not found in the cell %s.' %
                          uuidsentinel.cell1), output)

    @mock.patch.object(objects.InstanceList, 'get_by_host')
    @mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
    def test_delete_host_instances_exist(self, mock_get_cn, mock_get_by_host):
        """Tests trying to delete a host but the host has instances."""
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm1.create()
        # create a host mapping in the cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm1)
        hm.create()
        mock_get_by_host.return_value = [objects.Instance(
            ctxt, uuid=uuidsentinel.instance)]
        mock_get_cn.return_value = []
        self.assertEqual(4, self.commands.delete_host(uuidsentinel.cell1,
                                                      'fake-host'))
        output = self.output.getvalue().strip()
        self.assertEqual('There are instances on the host fake-host.', output)
        mock_get_by_host.assert_called_once_with(
            test.MatchType(context.RequestContext), 'fake-host')

    @mock.patch.object(objects.InstanceList, 'get_by_host',
                       return_value=[])
    @mock.patch.object(objects.HostMapping, 'destroy')
    @mock.patch.object(objects.ComputeNodeList, 'get_all_by_host')
    def test_delete_host_success(self, mock_get_cn, mock_destroy,
                                 mock_get_by_host):
        """Tests trying to delete a host that has no instances."""
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm1.create()
        # create a host mapping in the cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm1)
        hm.create()

        mock_get_cn.return_value = [mock.MagicMock(), mock.MagicMock()]

        self.assertEqual(0, self.commands.delete_host(uuidsentinel.cell1,
                                                      'fake-host'))
        output = self.output.getvalue().strip()
        self.assertEqual('', output)
        mock_get_by_host.assert_called_once_with(
            test.MatchType(context.RequestContext), 'fake-host')
        mock_destroy.assert_called_once_with()
        for node in mock_get_cn.return_value:
            self.assertEqual(0, node.mapped)
            node.save.assert_called_once_with()

    @mock.patch.object(objects.InstanceList, 'get_by_host',
                       return_value=[])
    @mock.patch.object(objects.HostMapping, 'destroy')
    @mock.patch.object(objects.ComputeNodeList, 'get_all_by_host',
        side_effect=exception.ComputeHostNotFound(host='fake-host'))
    def test_delete_host_success_compute_host_not_found(self, mock_get_cn,
                                                        mock_destroy,
                                                        mock_get_by_host):
        """Tests trying to delete a host that has no instances, but cannot
           be found by ComputeNodeList.get_all_by_host.
        """
        ctxt = context.get_admin_context()
        # create the cell mapping
        cm1 = objects.CellMapping(
            context=ctxt, uuid=uuidsentinel.cell1,
            database_connection='fake:///db', transport_url='fake:///mq')
        cm1.create()
        # create a host mapping in the cell
        hm = objects.HostMapping(
            context=ctxt, host='fake-host', cell_mapping=cm1)
        hm.create()

        self.assertEqual(0, self.commands.delete_host(uuidsentinel.cell1,
                                                      'fake-host'))
        output = self.output.getvalue().strip()
        self.assertEqual('', output)
        mock_get_by_host.assert_called_once_with(
            test.MatchType(context.RequestContext), 'fake-host')
        mock_destroy.assert_called_once_with()
        mock_get_cn.assert_called_once_with(
            test.MatchType(context.RequestContext), 'fake-host')


@ddt.ddt
class TestNovaManagePlacement(test.NoDBTestCase):
    """Unit tests for the nova-manage placement commands.

    Tests in this class should be simple and can rely on mock, so they
    are usually restricted to negative or side-effect type tests.

    For more involved functional scenarios, use
    nova.tests.functional.test_nova_manage.
    """

    def setUp(self):
        super(TestNovaManagePlacement, self).setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        self.cli = manage.PlacementCommands()
        self.useFixture(fixtures.MockPatch('nova.network.neutron.get_client'))

    def test_heal_allocations_with_cell_instance_id(self):
        """Test heal allocation with both cell id and instance id"""
        cell_uuid = uuidutils.generate_uuid()
        instance_uuid = uuidutils.generate_uuid()
        self.assertEqual(127, self.cli.heal_allocations(
                        instance_uuid=instance_uuid,
                        cell_uuid=cell_uuid))
        self.assertIn('The --cell and --instance options',
        self.output.getvalue())

    @mock.patch('nova.objects.CellMapping.get_by_uuid',
                side_effect=exception.CellMappingNotFound(uuid='fake'))
    def test_heal_allocations_with_cell_id_not_found(self, mock_get):
        """Test the case where cell_id is not found"""
        self.assertEqual(127, self.cli.heal_allocations(cell_uuid='fake'))
        output = self.output.getvalue().strip()
        self.assertEqual('Cell with uuid fake was not found.', output)

    @ddt.data(-1, 0, "one")
    def test_heal_allocations_invalid_max_count(self, max_count):
        self.assertEqual(127, self.cli.heal_allocations(max_count=max_count))

    @mock.patch('nova.objects.CellMappingList.get_all',
                return_value=objects.CellMappingList())
    def test_heal_allocations_no_cells(self, mock_get_all_cells):
        self.assertEqual(4, self.cli.heal_allocations(verbose=True))
        self.assertIn('No cells to process', self.output.getvalue())

    @mock.patch('nova.objects.CellMappingList.get_all',
                return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)]))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                return_value=objects.InstanceList())
    def test_heal_allocations_no_instances(
            self, mock_get_instances, mock_get_all_cells):
        self.assertEqual(4, self.cli.heal_allocations(verbose=True))
        self.assertIn('Processed 0 instances.', self.output.getvalue())

    @mock.patch('nova.objects.CellMappingList.get_all',
                return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)]))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                return_value=objects.InstanceList(objects=[
                    objects.Instance(
                        uuid=uuidsentinel.instance, host='fake', node='fake',
                        task_state=None,
                        flavor=objects.Flavor(extra_specs={}))]))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocs_for_consumer', return_value={})
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename',
                side_effect=exception.ComputeHostNotFound(host='fake'))
    def test_heal_allocations_compute_host_not_found(
            self, mock_get_compute_node, mock_get_allocs, mock_get_instances,
            mock_get_all_cells):
        self.assertEqual(2, self.cli.heal_allocations())
        self.assertIn('Compute host fake could not be found.',
                      self.output.getvalue())

    @mock.patch('nova.objects.CellMappingList.get_all',
                return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)]))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                return_value=objects.InstanceList(objects=[
                    objects.Instance(
                        uuid=uuidsentinel.instance, host='fake', node='fake',
                        task_state=None, flavor=objects.Flavor(extra_specs={}),
                        project_id='fake-project', user_id='fake-user')]))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocs_for_consumer', return_value={})
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename',
                return_value=objects.ComputeNode(uuid=uuidsentinel.node))
    @mock.patch('nova.scheduler.utils.resources_from_flavor',
                return_value={'VCPU': 1})
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
                return_value=fake_requests.FakeResponse(
                    500, content=jsonutils.dumps({"errors": [{"code": ""}]})))
    def test_heal_allocations_put_allocations_fails(
            self, mock_put_allocations, mock_res_from_flavor,
            mock_get_compute_node, mock_get_allocs, mock_get_instances,
            mock_get_all_cells):
        self.assertEqual(3, self.cli.heal_allocations())
        self.assertIn('Failed to update allocations for consumer',
                      self.output.getvalue())
        instance = mock_get_instances.return_value[0]
        mock_res_from_flavor.assert_called_once_with(
            instance, instance.flavor)

        expected_payload = {
            'allocations': {
                uuidsentinel.node: {
                    'resources': {'VCPU': 1}
                }
            },
            'user_id': 'fake-user',
            'project_id': 'fake-project',
            'consumer_generation': None
        }
        mock_put_allocations.assert_called_once_with(
            '/allocations/%s' % instance.uuid, expected_payload,
            global_request_id=mock.ANY, version='1.28')

    @mock.patch('nova.objects.CellMappingList.get_all',
                new=mock.Mock(return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)])))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                new=mock.Mock(return_value=objects.InstanceList(objects=[
                    objects.Instance(
                        uuid=uuidsentinel.instance, host='fake', node='fake',
                        task_state=None, flavor=objects.Flavor(extra_specs={}),
                        project_id='fake-project', user_id='fake-user')])))
    def test_heal_allocations_get_allocs_placement_fails(self):
        self.assertEqual(3, self.cli.heal_allocations())
        output = self.output.getvalue()
        self.assertIn('Allocation retrieval failed', output)
        # Having not mocked get_allocs_for_consumer, we get MissingAuthPlugin.
        self.assertIn('An auth plugin is required', output)

    @mock.patch('nova.objects.CellMappingList.get_all',
                new=mock.Mock(return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)])))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                side_effect=[
                    objects.InstanceList(objects=[objects.Instance(
                        uuid=uuidsentinel.instance, host='fake', node='fake',
                        task_state=None, flavor=objects.Flavor(extra_specs={}),
                        project_id='fake-project', user_id='fake-user')]),
                    objects.InstanceList(objects=[])])
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocs_for_consumer',
                new=mock.Mock(
                    side_effect=exception.ConsumerAllocationRetrievalFailed(
                        consumer_uuid='CONSUMER', error='ERROR')))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
                new_callable=mock.NonCallableMagicMock)
    def test_heal_allocations_get_allocs_retrieval_fails(self, mock_put,
                                                         mock_getinst):
        self.assertEqual(3, self.cli.heal_allocations())

    @mock.patch('nova.objects.CellMappingList.get_all',
                return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)]))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                # Called twice, first returns 1 instance, second returns []
                side_effect=(
                    objects.InstanceList(objects=[
                        objects.Instance(
                            uuid=uuidsentinel.instance, host='fake',
                            node='fake', task_state=None,
                            project_id='fake-project', user_id='fake-user',
                            flavor=objects.Flavor(extra_specs={}))]),
                    objects.InstanceList()))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocs_for_consumer')
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename',
                new_callable=mock.NonCallableMock)  # assert not called
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
                return_value=fake_requests.FakeResponse(204))
    def test_heal_allocations(
            self, mock_put, mock_get_compute_node, mock_get_allocs,
            mock_get_instances, mock_get_all_cells):
        """Tests the scenario that there are allocations created using
        placement API microversion < 1.8 where project/user weren't provided.
        The allocations will be re-put with the instance project_id/user_id
        values. Note that GET /allocations/{consumer_id} since commit f44965010
        will create the missing consumer record using the config option
        sentinels for project and user, so we won't get null back for the
        consumer project/user.
        """
        mock_get_allocs.return_value = {
            "allocations": {
                "92637880-2d79-43c6-afab-d860886c6391": {
                    "generation": 2,
                    "resources": {
                        "DISK_GB": 50,
                        "MEMORY_MB": 512,
                        "VCPU": 2
                    }
                }
            },
            "project_id": uuidsentinel.project_id,
            "user_id": uuidsentinel.user_id,
            "consumer_generation": 12,
        }
        self.assertEqual(0, self.cli.heal_allocations(verbose=True))
        self.assertIn('Processed 1 instances.', self.output.getvalue())
        mock_get_allocs.assert_called_once_with(
            test.MatchType(context.RequestContext), uuidsentinel.instance)
        expected_put_data = mock_get_allocs.return_value
        expected_put_data['project_id'] = 'fake-project'
        expected_put_data['user_id'] = 'fake-user'
        mock_put.assert_called_once_with(
            '/allocations/%s' % uuidsentinel.instance, expected_put_data,
            global_request_id=mock.ANY, version='1.28')

    @mock.patch('nova.objects.CellMappingList.get_all',
                return_value=objects.CellMappingList(objects=[
                    objects.CellMapping(name='cell1',
                                        uuid=uuidsentinel.cell1)]))
    @mock.patch('nova.objects.InstanceList.get_by_filters',
                return_value=objects.InstanceList(objects=[
                    objects.Instance(
                        uuid=uuidsentinel.instance, host='fake', node='fake',
                        task_state=None, project_id='fake-project',
                        user_id='fake-user',
                        flavor=objects.Flavor(extra_specs={}))]))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocs_for_consumer')
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
                return_value=fake_requests.FakeResponse(
                    409,
                    content=jsonutils.dumps(
                        {"errors": [
                            {"code": "placement.concurrent_update",
                             "detail": "consumer generation conflict"}]})))
    def test_heal_allocations_put_fails(
            self, mock_put, mock_get_allocs, mock_get_instances,
            mock_get_all_cells):
        """Tests the scenario that there are allocations created using
        placement API microversion < 1.8 where project/user weren't provided
        and there was no consumer. The allocations will be re-put with the
        instance project_id/user_id values but that fails with a 409 so a
        return code of 3 is expected from the command.
        """
        mock_get_allocs.return_value = {
            "allocations": {
                "92637880-2d79-43c6-afab-d860886c6391": {
                    "generation": 2,
                    "resources": {
                        "DISK_GB": 50,
                        "MEMORY_MB": 512,
                        "VCPU": 2
                    }
                }
            },
            "project_id": uuidsentinel.project_id,
            "user_id": uuidsentinel.user_id
        }
        self.assertEqual(3, self.cli.heal_allocations(verbose=True))
        self.assertIn(
            'consumer generation conflict', self.output.getvalue())
        mock_get_allocs.assert_called_once_with(
            test.MatchType(context.RequestContext), uuidsentinel.instance)
        expected_put_data = mock_get_allocs.return_value
        expected_put_data['project_id'] = 'fake-project'
        expected_put_data['user_id'] = 'fake-user'
        mock_put.assert_called_once_with(
            '/allocations/%s' % uuidsentinel.instance, expected_put_data,
            global_request_id=mock.ANY, version='1.28')

    @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
                return_value=objects.AggregateList(objects=[
                    objects.Aggregate(name='foo', hosts=['host1'])]))
    @mock.patch('nova.objects.HostMapping.get_by_host',
                side_effect=exception.HostMappingNotFound(name='host1'))
    def test_sync_aggregates_host_mapping_not_found(
            self, mock_get_host_mapping, mock_get_aggs):
        """Tests that we handle HostMappingNotFound."""
        result = self.cli.sync_aggregates(verbose=True)
        self.assertEqual(4, result)
        self.assertIn('The following hosts were found in nova host aggregates '
                      'but no host mappings were found in the nova API DB. '
                      'Run "nova-manage cell_v2 discover_hosts" and then '
                      'retry. Missing: host1', self.output.getvalue())

    @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
                return_value=objects.AggregateList(objects=[
                    objects.Aggregate(name='foo', hosts=['host1'])]))
    @mock.patch('nova.objects.HostMapping.get_by_host',
                return_value=objects.HostMapping(
                    host='host1', cell_mapping=objects.CellMapping()))
    @mock.patch('nova.objects.ComputeNodeList.get_all_by_host',
                return_value=objects.ComputeNodeList(objects=[
                    objects.ComputeNode(hypervisor_hostname='node1'),
                    objects.ComputeNode(hypervisor_hostname='node2')]))
    @mock.patch('nova.context.target_cell')
    def test_sync_aggregates_too_many_computes_for_host(
            self, mock_target_cell, mock_get_nodes, mock_get_host_mapping,
            mock_get_aggs):
        """Tests the scenario that a host in an aggregate has more than one
        compute node so the command does not know which compute node uuid to
        use for the placement resource provider aggregate and fails.
        """
        mock_target_cell.return_value.__enter__.return_value = (
            mock.sentinel.cell_context)
        result = self.cli.sync_aggregates(verbose=True)
        self.assertEqual(1, result)
        self.assertIn('Unexpected number of compute node records '
                      '(2) found for host host1. There should '
                      'only be a one-to-one mapping.', self.output.getvalue())
        mock_get_nodes.assert_called_once_with(
            mock.sentinel.cell_context, 'host1')

    @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
                return_value=objects.AggregateList(objects=[
                    objects.Aggregate(name='foo', hosts=['host1'])]))
    @mock.patch('nova.objects.HostMapping.get_by_host',
                return_value=objects.HostMapping(
                    host='host1', cell_mapping=objects.CellMapping()))
    @mock.patch('nova.objects.ComputeNodeList.get_all_by_host',
                side_effect=exception.ComputeHostNotFound(host='host1'))
    @mock.patch('nova.context.target_cell')
    def test_sync_aggregates_compute_not_found(
            self, mock_target_cell, mock_get_nodes, mock_get_host_mapping,
            mock_get_aggs):
        """Tests the scenario that no compute node record is found for a given
        host in an aggregate.
        """
        mock_target_cell.return_value.__enter__.return_value = (
            mock.sentinel.cell_context)
        result = self.cli.sync_aggregates(verbose=True)
        self.assertEqual(5, result)
        self.assertIn('Unable to find matching compute_nodes record entries '
                      'in the cell database for the following hosts; does the '
                      'nova-compute service on each host need to be '
                      'restarted? Missing: host1', self.output.getvalue())
        mock_get_nodes.assert_called_once_with(
            mock.sentinel.cell_context, 'host1')

    @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
                new=mock.Mock(return_value=objects.AggregateList(objects=[
                    objects.Aggregate(name='foo', hosts=['host1'],
                                      uuid=uuidsentinel.aggregate)])))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'aggregate_add_host')
    def test_sync_aggregates_get_provider_aggs_placement_server_error(
            self, mock_agg_add):
        """Tests the scenario that placement returns an unexpected server
        error when getting aggregates for a given resource provider.
        """
        mock_agg_add.side_effect = (
            exception.ResourceProviderAggregateRetrievalFailed(
                uuid=uuidsentinel.rp_uuid))
        with mock.patch.object(self.cli, '_get_rp_uuid_for_host',
                               return_value=uuidsentinel.rp_uuid):
            result = self.cli.sync_aggregates(verbose=True)
        self.assertEqual(2, result)
        self.assertIn('Failed to get aggregates for resource provider with '
                      'UUID %s' % uuidsentinel.rp_uuid,
                      self.output.getvalue())

    @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
                new=mock.Mock(return_value=objects.AggregateList(objects=[
                    objects.Aggregate(name='foo', hosts=['host1'],
                                      uuid=uuidsentinel.aggregate)])))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'aggregate_add_host')
    def test_sync_aggregates_put_aggregates_fails_provider_not_found(
            self, mock_agg_add):
        """Tests the scenario that we are trying to add a provider to an
        aggregate in placement but the
        PUT /resource_providers/{rp_uuid}/aggregates call fails with a 404
        because the provider is not found.
        """
        mock_agg_add.side_effect = exception.ResourceProviderNotFound(
            name_or_uuid=uuidsentinel.rp_uuid)
        with mock.patch.object(self.cli, '_get_rp_uuid_for_host',
                               return_value=uuidsentinel.rp_uuid):
            result = self.cli.sync_aggregates(verbose=True)
        self.assertEqual(6, result)
        self.assertIn('Unable to find matching resource provider record in '
                      'placement with uuid for the following hosts: '
                      '(host1=%s)' % uuidsentinel.rp_uuid,
                      self.output.getvalue())
        mock_agg_add.assert_called_once_with(
            mock.ANY, uuidsentinel.aggregate, rp_uuid=uuidsentinel.rp_uuid)

    @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
                new=mock.Mock(return_value=objects.AggregateList(objects=[
                    objects.Aggregate(name='foo', hosts=['host1'],
                                      uuid=uuidsentinel.aggregate)])))
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'aggregate_add_host')
    def test_sync_aggregates_put_aggregates_fails_generation_conflict(
            self, mock_agg_add):
        """Tests the scenario that we are trying to add a provider to an
        aggregate in placement but the
        PUT /resource_providers/{rp_uuid}/aggregates call fails with a 409
        generation conflict (even after retries).
        """
        mock_agg_add.side_effect = exception.ResourceProviderUpdateConflict(
            uuid=uuidsentinel.rp_uuid, generation=1, error="Conflict!")
        with mock.patch.object(self.cli, '_get_rp_uuid_for_host',
                               return_value=uuidsentinel.rp_uuid):
            result = self.cli.sync_aggregates(verbose=True)
        self.assertEqual(3, result)
        self.assertIn("Failed updating provider aggregates for "
                      "host (host1), provider (%s) and aggregate "
                      "(%s)." % (uuidsentinel.rp_uuid, uuidsentinel.aggregate),
                      self.output.getvalue())
        self.assertIn("Conflict!", self.output.getvalue())

    @mock.patch(
        'nova.network.neutron.API.has_extended_resource_request_extension',
        new=mock.Mock(return_value=False),
    )
    def test_has_request_but_no_allocation(self):
        # False because there is a full resource_request and allocation set.
        self.assertFalse(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.healed,
                    'resource_request': {
                        'resources': {
                            'NET_BW_EGR_KILOBIT_PER_SEC': 1000,
                        },
                        'required': [
                            'CUSTOM_VNIC_TYPE_NORMAL'
                        ]
                    },
                    'binding:profile': {'allocation': uuidsentinel.rp1}
                },
                mock.sentinel.neutron,
            )
        )
        # True because there is a full resource_request but no allocation set.
        self.assertTrue(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.needs_healing,
                    'resource_request': {
                        'resources': {
                            'NET_BW_EGR_KILOBIT_PER_SEC': 1000,
                        },
                        'required': [
                            'CUSTOM_VNIC_TYPE_NORMAL'
                        ]
                    },
                    'binding:profile': {}
                },
                mock.sentinel.neutron,
            )
        )
        # True because there is a full resource_request but no allocation set.
        self.assertTrue(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.needs_healing_null_profile,
                    'resource_request': {
                        'resources': {
                            'NET_BW_EGR_KILOBIT_PER_SEC': 1000,
                        },
                        'required': [
                            'CUSTOM_VNIC_TYPE_NORMAL'
                        ]
                    },
                    'binding:profile': None,
                },
                mock.sentinel.neutron,
            )
        )
        # False because there is no resource_request.
        self.assertFalse(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.missing_resource_request,
                    'binding:profile': {}
                },
                mock.sentinel.neutron,
            )
        )

    @mock.patch(
        'nova.network.neutron.API.has_extended_resource_request_extension',
        new=mock.Mock(return_value=True),
    )
    def test_has_request_but_no_allocation_extended(self):
        # False because there is resource_request and allocation set.
        self.assertFalse(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.healed,
                    'resource_request': {
                        'request_groups': [
                            {
                                'id': uuidsentinel.group1,
                                'resources': {
                                    'NET_BW_EGR_KILOBIT_PER_SEC': 1000,
                                },
                                'required': [
                                    'CUSTOM_VNIC_TYPE_NORMAL'
                                ]
                            },
                        ],
                    },
                    'binding:profile': {
                        'allocation': {uuidsentinel.group1: uuidsentinel.rp1}
                    }
                },
                mock.sentinel.neutron,
            )
        )
        # False because there no resource_request
        self.assertFalse(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.healed,
                    'resource_request': None,
                    'binding:profile': {
                        'allocation': {uuidsentinel.group1: uuidsentinel.rp1}
                    }
                },
                mock.sentinel.neutron,
            )
        )
        # True because we have request but no allocation set
        self.assertTrue(
            self.cli._has_request_but_no_allocation(
                {
                    'id': uuidsentinel.healed,
                    'resource_request': {
                        'request_groups': [
                            {
                                'id': uuidsentinel.group1,
                                'resources': {
                                    'NET_BW_EGR_KILOBIT_PER_SEC': 1000,
                                },
                                'required': [
                                    'CUSTOM_VNIC_TYPE_NORMAL'
                                ]
                            },
                        ],
                    },
                    'binding:profile': {
                        'allocation': {}
                    }
                },
                mock.sentinel.neutron,
            )
        )

    def test_update_ports_only_updates_binding_profile(self):
        """Simple test to make sure that only the port's binding:profile is
        updated based on the provided port dict's binding:profile and not
        just the binding:profile allocation key or other fields on the port.
        """
        neutron = mock.Mock()
        output = mock.Mock()
        binding_profile = {
            'allocation': uuidsentinel.rp_uuid,
            'foo': 'bar'
        }
        ports_to_update = [{
            'id': uuidsentinel.port_id,
            'binding:profile': binding_profile,
            'bar': 'baz'
        }]
        self.cli._update_ports(neutron, ports_to_update, output)
        expected_update_body = {
            'port': {
                'binding:profile': binding_profile
            }
        }
        neutron.update_port.assert_called_once_with(
            uuidsentinel.port_id, body=expected_update_body)

    @mock.patch.object(manage.PlacementCommands,
                       '_check_orphaned_allocations_for_provider')
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.get')
    def test_audit_with_provider_uuid(
        self, get_resource_providers, check_orphaned_allocs,
    ):
        rps = [
            {
                "generation": 1,
                "uuid": uuidsentinel.rp1,
                "links": None,
                "name": "rp1",
                "parent_provider_uuid": None,
                "root_provider_uuid": uuidsentinel.rp1,
            },
        ]
        get_resource_providers.return_value = fake_requests.FakeResponse(
            200, content=jsonutils.dumps({"resource_providers": rps}))

        # we found one orphaned allocation per RP and we had no faults
        check_orphaned_allocs.side_effect = ((1, 0),)

        ret = self.cli.audit(
            verbose=True, delete=False,
            provider_uuid=uuidsentinel.fake_uuid)

        # We found orphaned allocations but we left them
        self.assertEqual(3, ret)

        get_resource_providers.assert_called_once_with(
            f'/resource_providers?uuid={uuidsentinel.fake_uuid}',
            global_request_id=mock.ANY,
            version='1.14')

        # Only the specified RP is checked
        check_orphaned_allocs.assert_has_calls([
            mock.call(mock.ANY, mock.ANY, mock.ANY, rps[0], False),
        ])

        output = self.output.getvalue()
        self.assertIn('Processed 1 allocation', output)

    def test_audit_with_invalid_provider_uuid(self):
        with mock.patch.object(
            self.cli, '_get_resource_provider',
            side_effect=exception.ResourceProviderNotFound(
                name_or_uuid=uuidsentinel.fake_uuid),
        ):
            ret = self.cli.audit(
                provider_uuid=uuidsentinel.fake_uuid)
        self.assertEqual(127, ret)
        output = self.output.getvalue()
        self.assertIn(
            'Resource provider with UUID %s' % uuidsentinel.fake_uuid,
            output)

    @mock.patch.object(manage.PlacementCommands,
                       '_check_orphaned_allocations_for_provider')
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.get')
    def _test_audit(self, get_resource_providers, check_orphaned_allocs,
                     verbose=False, delete=False, errors=False, found=False):
        rps = [
              {"generation": 1,
               "uuid": uuidsentinel.rp1,
               "links": None,
               "name": "rp1",
               "parent_provider_uuid": None,
               "root_provider_uuid": uuidsentinel.rp1},
              {"generation": 1,
               "uuid": uuidsentinel.rp2,
               "links": None,
               "name": "rp2",
               "parent_provider_uuid": None,
               "root_provider_uuid": uuidsentinel.rp2},
              ]
        get_resource_providers.return_value = fake_requests.FakeResponse(
            200, content=jsonutils.dumps({"resource_providers": rps}))

        if errors:
            # We found one orphaned allocation per RP but RP1 got a fault
            check_orphaned_allocs.side_effect = ((1, 1), (1, 0))
        elif found:
            # we found one orphaned allocation per RP and we had no faults
            check_orphaned_allocs.side_effect = ((1, 0), (1, 0))
        else:
            # No orphaned allocations are found for all the RPs
            check_orphaned_allocs.side_effect = ((0, 0), (0, 0))

        ret = self.cli.audit(verbose=verbose, delete=delete)
        if errors:
            # Any fault stops the audit and provides a return code equals to 1
            expected_ret = 1
        elif found and delete:
            # We found orphaned allocations and deleted them
            expected_ret = 4
        elif found and not delete:
            # We found orphaned allocations but we left them
            expected_ret = 3
        else:
            # Nothing was found
            expected_ret = 0
        self.assertEqual(expected_ret, ret)

        get_resource_providers.assert_called_once_with(
            '/resource_providers',
            global_request_id=mock.ANY,
            version='1.14')

        call1 = mock.call(mock.ANY, mock.ANY, mock.ANY, rps[0], delete)
        call2 = mock.call(mock.ANY, mock.ANY, mock.ANY, rps[1], delete)
        if errors:
            # We stop checking other RPs once we got a fault
            check_orphaned_allocs.assert_has_calls([call1])
        else:
            # All the RPs are checked
            check_orphaned_allocs.assert_has_calls([call1, call2])

        if verbose and found:
            output = self.output.getvalue()
            self.assertIn('Processed 2 allocations', output)
        if errors:
            output = self.output.getvalue()
            self.assertIn(
                'The Resource Provider %s had problems' % rps[0]["uuid"],
                output)

    def test_audit_not_found_orphaned_allocs(self):
        self._test_audit(found=False)

    def test_audit_found_orphaned_allocs_not_verbose(self):
        self._test_audit(found=True)

    def test_audit_found_orphaned_allocs_verbose(self):
        self._test_audit(found=True, verbose=True)

    def test_audit_found_orphaned_allocs_and_deleted_them(self):
        self._test_audit(found=True, delete=True)

    def test_audit_found_orphaned_allocs_but_got_errors(self):
        self._test_audit(errors=True)

    @mock.patch.object(manage.PlacementCommands,
                       '_delete_allocations_from_consumer')
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocations_for_resource_provider')
    @mock.patch.object(manage.PlacementCommands,
                       '_get_instances_and_current_migrations')
    def test_check_orphaned_allocations_for_provider(self,
                                                     get_insts_and_migs,
                                                     get_allocs_for_rp,
                                                     delete_allocs):
        provider = {"generation": 1,
                    "uuid": uuidsentinel.rp1,
                    "links": None,
                    "name": "rp1",
                    "parent_provider_uuid": None,
                    "root_provider_uuid": uuidsentinel.rp1}
        compute_resources = {'VCPU': 1, 'MEMORY_MB': 2048, 'DISK_GB': 20}
        allocations = {
            # Some orphaned compute allocation
            uuidsentinel.orphaned_alloc1: {'resources': compute_resources},
            # Some existing instance allocation
            uuidsentinel.inst1: {'resources': compute_resources},
            # Some existing migration allocation
            uuidsentinel.mig1: {'resources': compute_resources},
            # Some other allocation not related to Nova
            uuidsentinel.other_alloc1: {'resources': {'CUSTOM_GOO'}},
        }

        get_insts_and_migs.return_value = (
            [uuidsentinel.inst1],
            [uuidsentinel.mig1])
        get_allocs_for_rp.return_value = report.ProviderAllocInfo(allocations)

        ctxt = context.RequestContext()
        placement = report.SchedulerReportClient()
        ret = self.cli._check_orphaned_allocations_for_provider(
            ctxt, placement, lambda x: x, provider, True)
        get_allocs_for_rp.assert_called_once_with(ctxt, uuidsentinel.rp1)
        delete_allocs.assert_called_once_with(ctxt, placement, provider,
                                              uuidsentinel.orphaned_alloc1,
                                              'instance')
        self.assertEqual((1, 0), ret)


class VolumeAttachmentCommandsTestCase(test.NoDBTestCase):
    """Unit tests for the nova-manage volume_attachment commands.

    Tests in this class should be simple and can rely on mock, so they
    are usually restricted to negative or side-effect type tests.

    For more involved functional scenarios, use
    nova.tests.functional.test_nova_manage.
    """

    def setUp(self):
        super().setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        self.commands = manage.VolumeAttachmentCommands()

    @staticmethod
    def _get_fake_connector_info():
        return {
            'ip': '192.168.7.8',
            'host': 'fake-host',
            'initiator': 'fake.initiator.iqn',
            'wwpns': '100010604b019419',
            'wwnns': '200010604b019419',
            'multipath': False,
            'platform': 'x86_64',
            'os_type': 'linux2',
        }

    @mock.patch.object(manage, 'format_dict')
    @mock.patch('nova.objects.BlockDeviceMapping.get_by_volume_and_instance')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_show(self, mock_get_im, mock_get_bdm, mock_format_dict):
        """Test the 'show' command."""
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()

        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        im = objects.InstanceMapping(cell_mapping=cm)
        mock_get_im.return_value = im

        bdm = objects.BlockDeviceMapping(
            cell_ctxt, uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.attach)
        mock_get_bdm.return_value = bdm

        with test.nested(
            mock.patch('nova.context.RequestContext', return_value=ctxt),
            mock.patch('nova.context.target_cell'),
        ) as (mock_get_context, mock_target_cell):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock

            ret = self.commands.show(
                uuidsentinel.instance, uuidsentinel.volume)

        self.assertEqual(0, ret)
        mock_get_im.assert_called_once_with(ctxt, uuidsentinel.instance)
        mock_get_bdm.assert_called_once_with(
            cell_ctxt, uuidsentinel.volume, uuidsentinel.instance)
        # Don't assert the output of format_dict here, just that it's called.
        mock_format_dict.assert_called_once_with(bdm)

    @mock.patch('oslo_serialization.jsonutils.dumps')
    @mock.patch('nova.objects.BlockDeviceMapping.get_by_volume_and_instance')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_show_json(self, mock_get_im, mock_get_bdm, mock_dump):
        """Test the 'show' command with --json."""
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()

        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        im = objects.InstanceMapping(cell_mapping=cm)
        mock_get_im.return_value = im

        bdm = objects.BlockDeviceMapping(
            cell_ctxt, uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.attach)
        mock_get_bdm.return_value = bdm

        with test.nested(
            mock.patch('nova.context.RequestContext', return_value=ctxt),
            mock.patch('nova.context.target_cell'),
        ) as (mock_get_context, mock_target_cell):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock

            ret = self.commands.show(
                uuidsentinel.instance, uuidsentinel.volume, json=True)

        self.assertEqual(0, ret)
        mock_get_im.assert_called_once_with(ctxt, uuidsentinel.instance)
        mock_get_bdm.assert_called_once_with(
            cell_ctxt, uuidsentinel.volume, uuidsentinel.instance)
        # Don't assert the output of dumps here, just that it's called.
        mock_dump.assert_called_once_with(bdm)

    @mock.patch.object(manage, 'format_dict')
    @mock.patch('nova.objects.BlockDeviceMapping.get_by_volume_and_instance')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_show_connection_info(
        self, mock_get_im, mock_get_bdm, mock_format_dict
    ):
        """Test the 'show' command with --connection_info."""
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()

        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        im = objects.InstanceMapping(cell_mapping=cm)
        mock_get_im.return_value = im

        fake_connection_info = {
            'data': {
                'foo': 'bar'
            }
        }
        bdm = objects.BlockDeviceMapping(
            cell_ctxt, uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.attach,
            connection_info=jsonutils.dumps(fake_connection_info))
        mock_get_bdm.return_value = bdm

        with test.nested(
            mock.patch('nova.context.RequestContext', return_value=ctxt),
            mock.patch('nova.context.target_cell'),
        ) as (mock_get_context, mock_target_cell):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock

            ret = self.commands.show(
                uuidsentinel.instance, uuidsentinel.volume,
                connection_info=True)

        self.assertEqual(0, ret)
        mock_get_im.assert_called_once_with(ctxt, uuidsentinel.instance)
        mock_get_bdm.assert_called_once_with(
            cell_ctxt, uuidsentinel.volume, uuidsentinel.instance)
        # Don't assert the output of format_dict here, just that it's called.
        mock_format_dict.assert_called_once_with(
            fake_connection_info)

    @mock.patch('nova.objects.BlockDeviceMapping.get_by_volume_and_instance')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_show_connection_info_json(self, mock_get_im, mock_get_bdm):
        """Test the 'show' command with --json and --connection_info."""
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()

        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        im = objects.InstanceMapping(cell_mapping=cm)
        mock_get_im.return_value = im

        fake_connection_info = {
            'data': {
                'foo': 'bar'
            }
        }
        bdm = objects.BlockDeviceMapping(
            cell_ctxt, uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.attach,
            connection_info=jsonutils.dumps(fake_connection_info))
        mock_get_bdm.return_value = bdm

        with test.nested(
            mock.patch('nova.context.RequestContext', return_value=ctxt),
            mock.patch('nova.context.target_cell'),
        ) as (mock_get_context, mock_target_cell):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock

            ret = self.commands.show(
                uuidsentinel.instance, uuidsentinel.volume,
                connection_info=True, json=True)

        self.assertEqual(0, ret)
        mock_get_im.assert_called_once_with(ctxt, uuidsentinel.instance)
        mock_get_bdm.assert_called_once_with(
            cell_ctxt, uuidsentinel.volume, uuidsentinel.instance)
        output = self.output.getvalue().strip()
        # We just print bdm.connection_info here so this is all we can assert
        self.assertIn(bdm.connection_info, output)

    @mock.patch('nova.context.get_admin_context')
    def test_show_unknown_failure(self, mock_get_context):
        """Test the 'show' command with an unknown failure"""
        mock_get_context.side_effect = test.TestingException('oops')
        ret = self.commands.show(uuidsentinel.instance, uuidsentinel.volume)
        output = self.output.getvalue().strip()
        self.assertIn(
            'Unexpected error, see nova-manage.log for the full trace: oops',
            output)
        self.assertEqual(1, ret)

    @mock.patch(
        'nova.context.get_admin_context',
        new=mock.Mock(return_value=mock.sentinel.context))
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_show_instance_not_found(self, mock_get_im):
        """Test the 'show' command with a missing instance"""

        mock_get_im.side_effect = exception.InstanceNotFound(
            instance_id=uuidsentinel.instance)
        ret = self.commands.show(uuidsentinel.instance, uuidsentinel.volume)
        mock_get_im.assert_called_once_with(
            mock.sentinel.context, uuidsentinel.instance)
        self.assertEqual(2, ret)

        mock_get_im.reset_mock()
        mock_get_im.side_effect = exception.InstanceMappingNotFound(
           uuid=uuidsentinel.instance)
        ret = self.commands.show(uuidsentinel.instance, uuidsentinel.volume)
        mock_get_im.assert_called_once_with(
            mock.sentinel.context, uuidsentinel.instance)
        self.assertEqual(2, ret)

    @mock.patch('nova.objects.BlockDeviceMapping.get_by_volume_and_instance')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    def test_show_bdm_not_found(self, mock_get_im, mock_get_bdm):
        """Test the 'show' command with a missing bdm."""
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()

        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        im = objects.InstanceMapping(cell_mapping=cm)
        mock_get_im.return_value = im
        mock_get_bdm.side_effect = exception.VolumeBDMNotFound(
            volume_id=uuidsentinel.volume)

        with test.nested(
            mock.patch('nova.context.RequestContext', return_value=ctxt),
            mock.patch('nova.context.target_cell'),
        ) as (mock_get_context, mock_target_cell):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock

            ret = self.commands.show(
                uuidsentinel.instance, uuidsentinel.volume)

        self.assertEqual(3, ret)
        mock_get_im.assert_called_once_with(ctxt, uuidsentinel.instance)
        mock_get_bdm.assert_called_once_with(
            cell_ctxt, uuidsentinel.volume, uuidsentinel.instance)

    @mock.patch.object(manage, 'format_dict')
    @mock.patch('nova.utils.get_root_helper')
    @mock.patch('os_brick.initiator.connector.get_connector_properties')
    def test_get_connector(
        self, mock_get_connector, mock_get_root, mock_format_dict
    ):
        """Test the 'get_connector' command without --json."""
        fake_connector = self._get_fake_connector_info()
        mock_get_connector.return_value = fake_connector

        ret = self.commands.get_connector()

        self.assertEqual(0, ret)
        mock_get_root.assert_called_once_with()
        mock_get_connector.assert_called_once_with(
            mock_get_root.return_value, CONF.my_block_storage_ip,
            CONF.libvirt.volume_use_multipath, enforce_multipath=True,
            host=CONF.host)
        # Don't assert the output of format_dict here, just that it's called.
        mock_format_dict.assert_called_once_with(fake_connector)

    @mock.patch('oslo_serialization.jsonutils.dumps')
    @mock.patch('nova.utils.get_root_helper')
    @mock.patch('os_brick.initiator.connector.get_connector_properties')
    def test_get_connector_json(
        self, mock_get_connector, mock_get_root, mock_dump
    ):
        """Test the 'get_connector' command with --json."""
        fake_connector = self._get_fake_connector_info()
        mock_get_connector.return_value = fake_connector

        ret = self.commands.get_connector(json=True)

        self.assertEqual(0, ret)
        mock_get_root.assert_called_once_with()
        mock_get_connector.assert_called_once_with(
            mock_get_root.return_value, CONF.my_block_storage_ip,
            CONF.libvirt.volume_use_multipath, enforce_multipath=True,
            host=CONF.host)
        # Don't assert the output of dumps here, just that it's called.
        mock_dump.assert_called_once_with(fake_connector)

    @mock.patch('nova.utils.get_root_helper')
    @mock.patch('os_brick.initiator.connector.get_connector_properties')
    def test_get_connector_unknown_failure(
        self, mock_get_connector, mock_get_root
    ):
        mock_get_connector.side_effect = test.TestingException('oops')
        ret = self.commands.get_connector()

        output = self.output.getvalue().strip()
        self.assertIn(
            'Unexpected error, see nova-manage.log for the full trace: oops',
            output)
        self.assertEqual(1, ret)
        mock_get_root.assert_called_once_with()
        mock_get_connector.assert_called_once_with(
            mock_get_root.return_value, CONF.my_block_storage_ip,
            CONF.libvirt.volume_use_multipath, enforce_multipath=True,
            host=CONF.host)

    @mock.patch('os.path.exists')
    def test_refresh_missing_connector_path_file(self, mock_exists):
        """Test refresh with a missing connector_path file.

        Ensure we correctly error out.
        """
        mock_exists.return_value = False
        ret = self.commands.refresh(
            uuidsentinel.volume, uuidsentinel.instance, 'fake_path'
        )
        self.assertEqual(2, ret)
        output = self.output.getvalue().strip()
        self.assertIn('Connector file not found at fake_path', output)

    @mock.patch('os.path.exists')
    def test_refresh_invalid_connector_path_file(self, mock_exists):
        """Test refresh with invalid connector_path file.

        This is really a test of oslo_serialization.jsonutils' 'load' wrapper
        but it's useful all the same.
        """
        mock_exists.return_value = True
        with self.patch_open('fake_path', b'invalid json'):
            ret = self.commands.refresh(
                uuidsentinel.volume, uuidsentinel.instance, 'fake_path'
            )
            self.assertEqual(3, ret)
            output = self.output.getvalue().strip()
            self.assertIn('Failed to open fake_path', output)

    @mock.patch('os.path.exists')
    def test_refresh_connector_file_oserr(self, mock_exists):
        """Test refresh with connector file having no read permission.
        """
        mock_exists.return_value = True
        with self.patch_open('fake_path', b'invalid json') as mock_file:
            mock_file.side_effect = OSError("Permission denied")
            ret = self.commands.refresh(
                uuidsentinel.volume, uuidsentinel.instance, 'fake_path'
            )
            self.assertEqual(3, ret)

    @mock.patch('os.path.exists')
    def _test_refresh(self, mock_exists):
        ctxt = context.get_admin_context()
        cell_ctxt = context.get_admin_context()
        cell_ctxt.cell_uuid = '39fd7a1f-db62-45bc-a7b6-8137cef87c9d'
        fake_connector = self._get_fake_connector_info()

        mock_exists.return_value = True

        with test.nested(
            mock.patch('nova.context.RequestContext', return_value=ctxt),
            mock.patch('nova.context.target_cell'),
            self.patch_open('fake_path', None),
            mock.patch(
                'oslo_serialization.jsonutils.load',
                return_value=fake_connector,
            ),
        ) as (mock_get_context, mock_target_cell, _, _):
            fake_target_cell_mock = mock.MagicMock()
            fake_target_cell_mock.__enter__.return_value = cell_ctxt
            mock_target_cell.return_value = fake_target_cell_mock

            ret = self.commands.refresh(
                uuidsentinel.instance, uuidsentinel.volume, 'fake_path'
            )

        mock_exists.assert_called_once_with('fake_path')

        return ret

    @mock.patch.object(objects.Instance, 'get_by_uuid')
    def test_refresh_invalid_instance_uuid(self, mock_get_instance):
        """Test refresh with invalid instance UUID."""
        mock_get_instance.side_effect = exception.InstanceNotFound(
            instance_id=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED)
        ret = self._test_refresh()
        self.assertEqual(4, ret)
        output = self.output.getvalue().strip()
        self.assertIn(
            f'Instance {uuidsentinel.instance} could not be found', output)

    @mock.patch.object(
        objects.BlockDeviceMapping, 'get_by_volume_and_instance')
    @mock.patch.object(objects.Instance, 'get_by_uuid')
    def test_refresh_invalid_instance_state(
        self, mock_get_instance, mock_get_bdm,
    ):
        """Test refresh with instance in an non-stopped state."""
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.ACTIVE)
        mock_get_bdm.return_value = objects.BlockDeviceMapping(
            uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.instance)

        ret = self._test_refresh()
        self.assertEqual(5, ret)
        output = self.output.getvalue().strip()
        self.assertIn('must be stopped', output)

    @mock.patch.object(objects.InstanceAction, 'action_start')
    @mock.patch.object(manage.VolumeAttachmentCommands, '_do_refresh')
    @mock.patch.object(
        objects.BlockDeviceMapping, 'get_by_volume_and_instance')
    @mock.patch.object(objects.Instance, 'get_by_uuid')
    def test_refresh_instance_already_locked(
        self, mock_get_instance, mock_get_bdm,
        mock__do_refresh, mock_action_start
    ):
        """Test refresh with instance when instance is already locked."""
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            locked=True, locked_by='admin')
        mock_get_bdm.return_value = objects.BlockDeviceMapping(
            uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.instance)
        mock_action = mock.Mock(spec=objects.InstanceAction)
        mock_action_start.return_value = mock_action

        self._test_refresh()
        mock__do_refresh.assert_called_once()

    @mock.patch.object(
        objects.BlockDeviceMapping, 'get_by_volume_and_instance')
    @mock.patch.object(objects.Instance, 'get_by_uuid')
    def test_refresh_invalid_volume_id(self, mock_get_instance, mock_get_bdm):
        """Test refresh with invalid instance/volume combination."""
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            locked=False)
        mock_get_bdm.side_effect = exception.VolumeBDMNotFound(
            volume_id=uuidsentinel.volume)

        ret = self._test_refresh()
        self.assertEqual(6, ret)

    @mock.patch('nova.volume.cinder.API.attachment_get')
    @mock.patch('nova.volume.cinder.API.attachment_delete')
    @mock.patch('nova.volume.cinder.API.attachment_create')
    @mock.patch('nova.compute.api.API.unlock')
    @mock.patch('nova.compute.api.API.lock')
    @mock.patch.object(
        objects.BlockDeviceMapping, 'get_by_volume_and_instance')
    @mock.patch.object(objects.Instance, 'get_by_uuid')
    @mock.patch.object(objects.InstanceAction, 'action_start')
    def test_refresh_attachment_unknown_failure(
        self, mock_action_start, mock_get_instance,
        mock_get_bdm, mock_lock, mock_unlock, mock_attachment_create,
        mock_attachment_delete, mock_attachment_get
    ):
        """Test refresh with instance when any other error happens.
        """
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            locked=False)
        mock_get_bdm.return_value = objects.BlockDeviceMapping(
            uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.attachment)
        mock_attachment_create.side_effect = Exception('oops')
        mock_action = mock.Mock(spec=objects.InstanceAction)
        mock_action_start.return_value = mock_action

        ret = self._test_refresh()
        self.assertEqual(1, ret)
        output = self.output.getvalue().strip()
        self.assertIn(
            'Unexpected error, see nova-manage.log for the full trace: oops',
            output)
        mock_attachment_create.assert_called_once_with(
            mock.ANY, uuidsentinel.volume, uuidsentinel.instance)
        mock_attachment_delete.assert_not_called()
        mock_attachment_get.assert_called_once_with(
            mock.ANY, uuidsentinel.attachment)
        mock_unlock.assert_called_once_with(
            mock.ANY, mock_get_instance.return_value)
        mock_action_start.assert_called_once()
        mock_action.finish.assert_called_once()

    @mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
    @mock.patch('nova.volume.cinder.API', autospec=True)
    @mock.patch('nova.compute.api.API', autospec=True)
    @mock.patch.object(objects.BlockDeviceMapping, 'save')
    @mock.patch.object(
        objects.BlockDeviceMapping, 'get_by_volume_and_instance')
    @mock.patch.object(objects.Instance, 'get_by_uuid')
    @mock.patch.object(objects.InstanceAction, 'action_start')
    def test_refresh_invalid_connector_host(
        self, mock_action_start, mock_get_instance,
        mock_get_bdm, mock_save_bdm, mock_compute_api, mock_volume_api,
        mock_compute_rpcapi
    ):
        """Test refresh with a old host not disconnected properly
        and connector host info is not correct, a fake-host is
        passed.
        """

        fake_volume_api = mock_volume_api.return_value
        device_name = '/dev/vda'

        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            host='old-host', locked=False)
        mock_get_bdm.return_value = objects.BlockDeviceMapping(
            uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.instance,
            device_name=device_name)
        mock_action = mock.Mock(spec=objects.InstanceAction)
        mock_action_start.return_value = mock_action

        fake_volume_api.attachment_create.return_value = {
            'id': uuidsentinel.new_attachment,
        }
        # in instance we have host as 'old-host'
        # but here 'fake-host' is passed in connector info.
        fake_volume_api.attachment_update.return_value = {
            'connection_info': self._get_fake_connector_info(),
        }

        ret = self._test_refresh()
        self.assertEqual(7, ret)

    @mock.patch('nova.compute.rpcapi.ComputeAPI', autospec=True)
    @mock.patch('nova.volume.cinder.API', autospec=True)
    @mock.patch('nova.compute.api.API', autospec=True)
    @mock.patch.object(objects.BlockDeviceMapping, 'save')
    @mock.patch.object(
        objects.BlockDeviceMapping, 'get_by_volume_and_instance')
    @mock.patch.object(objects.Instance, 'get_by_uuid')
    @mock.patch.object(objects.InstanceAction, 'action_start')
    def test_refresh(
        self, mock_action_start, mock_get_instance,
        mock_get_bdm, mock_save_bdm, mock_compute_api, mock_volume_api,
        mock_compute_rpcapi
    ):
        """Test refresh with a successful code path."""
        fake_compute_api = mock_compute_api.return_value
        fake_volume_api = mock_volume_api.return_value
        fake_compute_rpcapi = mock_compute_rpcapi.return_value
        device_name = '/dev/vda'

        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            host='fake-host', locked=False)
        mock_get_bdm.return_value = objects.BlockDeviceMapping(
            uuid=uuidsentinel.bdm, volume_id=uuidsentinel.volume,
            attachment_id=uuidsentinel.instance,
            device_name=device_name)
        mock_action = mock.Mock(spec=objects.InstanceAction)
        mock_action_start.return_value = mock_action

        fake_volume_api.attachment_create.return_value = {
            'id': uuidsentinel.new_attachment,
        }
        fake_volume_api.attachment_update.return_value = {
            'connection_info': self._get_fake_connector_info(),
        }

        ret = self._test_refresh()
        self.assertEqual(0, ret)

        fake_compute_api.lock.assert_called_once_with(
            mock.ANY, mock_get_instance.return_value, reason=mock.ANY)
        fake_volume_api.attachment_create.assert_called_once_with(
            mock.ANY, uuidsentinel.volume, uuidsentinel.instance)
        fake_compute_rpcapi.remove_volume_connection.assert_called_once_with(
            mock.ANY, mock_get_instance.return_value, uuidsentinel.volume,
            mock_get_instance.return_value.host, delete_attachment=True)
        fake_volume_api.attachment_update.assert_called_once_with(
            mock.ANY, uuidsentinel.new_attachment, mock.ANY, device_name)
        fake_volume_api.attachment_complete.assert_called_once_with(
            mock.ANY, uuidsentinel.new_attachment)
        fake_compute_api.unlock.assert_called_once_with(
            mock.ANY, mock_get_instance.return_value)
        mock_action_start.assert_called_once()
        mock_action.finish.assert_called_once()


class TestNovaManageMain(test.NoDBTestCase):
    """Tests the nova-manage:main() setup code."""

    def setUp(self):
        super(TestNovaManageMain, self).setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))

    @mock.patch.object(manage.config, 'parse_args')
    @mock.patch.object(manage, 'CONF')
    def test_error_traceback(self, mock_conf, mock_parse_args):
        with mock.patch.object(manage.cmd_common, 'get_action_fn',
                               side_effect=test.TestingException('oops')):
            mock_conf.post_mortem = False
            self.assertEqual(255, manage.main())
            # assert the traceback is dumped to stdout
            output = self.output.getvalue()
            self.assertIn('An error has occurred', output)
            self.assertIn('Traceback', output)
            self.assertIn('oops', output)

    @mock.patch('pdb.post_mortem')
    @mock.patch.object(manage.config, 'parse_args')
    @mock.patch.object(manage, 'CONF')
    def test_error_post_mortem(self, mock_conf, mock_parse_args, mock_pm):
        with mock.patch.object(manage.cmd_common, 'get_action_fn',
                               side_effect=test.TestingException('oops')):
            mock_conf.post_mortem = True
            self.assertEqual(255, manage.main())
            self.assertTrue(mock_pm.called)

    def _lock_instance(self, ctxt, instance, reason):
        instance.locked = True

    def _unlock_instance(self, ctxt, instance):
        instance.locked = False

    def test_locked_instance(self):
        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        proj_uuid = uuidutils.generate_uuid()
        instance = objects.Instance(
            project_id=proj_uuid, uuid=uuidsentinel.instance)
        instance.locked = True

        with mock.patch('nova.compute.api.API') as mock_api:
            mock_api.return_value.lock.side_effect = self._lock_instance
            mock_api.return_value.unlock.side_effect = self._unlock_instance

            with manage.locked_instance(cm, instance, 'some'):
                self.assertTrue(instance.locked)

        self.assertTrue(instance.locked)

    def test_unlocked_instance(self):
        cm = objects.CellMapping(name='foo', uuid=uuidsentinel.cell)
        proj_uuid = uuidutils.generate_uuid()
        instance = objects.Instance(
            project_id=proj_uuid, uuid=uuidsentinel.instance)
        instance.locked = False

        with mock.patch('nova.compute.api.API') as mock_api:
            mock_api.return_value.lock.side_effect = self._lock_instance
            mock_api.return_value.unlock.side_effect = self._unlock_instance

            with manage.locked_instance(cm, instance, 'some'):
                self.assertTrue(instance.locked)

        self.assertFalse(instance.locked)


class LibvirtCommandsTestCase(test.NoDBTestCase):

    def setUp(self):
        super().setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        self.commands = manage.LibvirtCommands()

    @mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
    @mock.patch('nova.context.get_admin_context')
    def test_get(self, mock_get_context, mock_get_machine_type):
        mock_get_context.return_value = mock.sentinel.admin_context
        mock_get_machine_type.return_value = 'pc'
        ret = self.commands.get_machine_type(
            instance_uuid=uuidsentinel.instance
        )
        mock_get_machine_type.assert_called_once_with(
            mock.sentinel.admin_context,
            uuidsentinel.instance
        )
        output = self.output.getvalue()
        self.assertEqual(0, ret)
        self.assertIn('pc', output)

    @mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
    @mock.patch('nova.context.get_admin_context')
    def test_get_unknown_failure(
        self, mock_get_context, mock_get_machine_type
    ):
        mock_get_machine_type.side_effect = Exception('oops')
        ret = self.commands.get_machine_type(
            instance_uuid=uuidsentinel.instance
        )
        output = self.output.getvalue().strip()
        self.assertIn(
            'Unexpected error, see nova-manage.log for the full trace: oops',
            output)
        self.assertEqual(1, ret)

    @mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_get_unable_to_find_instance_mapping(self, mock_get_machine_type):
        mock_get_machine_type.side_effect = exception.InstanceMappingNotFound(
            uuid=uuidsentinel.instance)
        ret = self.commands.get_machine_type(
            instance_uuid=uuidsentinel.instance
        )
        output = self.output.getvalue()
        self.assertEqual(2, ret)
        self.assertIn(
            f"Instance {uuidsentinel.instance} has no mapping to a cell.",
            output)

    @mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_get_machine_type_unable_to_find_instance(
        self, mock_get_machine_type
    ):
        mock_get_machine_type.side_effect = exception.InstanceNotFound(
            instance_id=uuidsentinel.instance)
        ret = self.commands.get_machine_type(
            instance_uuid=uuidsentinel.instance)
        output = self.output.getvalue()
        self.assertEqual(2, ret)
        self.assertIn(
            f"Instance {uuidsentinel.instance} could not be found.",
            output)

    @mock.patch('nova.virt.libvirt.machine_type_utils.get_machine_type',
                new=mock.Mock(return_value=None))
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_get_none_found(self):
        ret = self.commands.get_machine_type(
            instance_uuid=uuidsentinel.instance
        )
        output = self.output.getvalue()
        self.assertEqual(3, ret)
        self.assertIn("No machine type registered for instance "
                      f"{uuidsentinel.instance}", output)

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context')
    def test_update(self, mock_get_context, mock_update):
        mock_update.return_value = ('pc-1.2', 'pc-1.1')
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type='pc-1.2'
        )
        mock_update.assert_called_once_with(
            mock_get_context.return_value,
            uuidsentinel.instance,
            'pc-1.2',
            force=False
        )
        output = self.output.getvalue()
        self.assertEqual(0, ret)
        self.assertIn(
            f"Updated instance {uuidsentinel.instance} machine type to pc-1.2 "
            "(previously pc-1.1)",
            output
        )

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context')
    def test_update_force(self, mock_get_context, mock_update):
        mock_update.return_value = ('q35', 'pc')
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type='q35',
            force=True
        )
        mock_update.assert_called_once_with(
            mock_get_context.return_value,
            uuidsentinel.instance,
            'q35',
            force=True
        )
        output = self.output.getvalue()
        self.assertEqual(0, ret)
        self.assertIn(
            f"Updated instance {uuidsentinel.instance} machine type to q35 "
            "(previously pc)",
            output
        )

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_update_unknown_failure(self, mock_update):
        mock_update.side_effect = Exception('oops')
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type=mock.sentinel.machine_type
        )
        output = self.output.getvalue().strip()
        self.assertIn(
            'Unexpected error, see nova-manage.log for the full trace: oops',
            output)
        self.assertEqual(1, ret)

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_update_instance_mapping_not_found(self, mock_update):
        mock_update.side_effect = exception.InstanceMappingNotFound(
            uuid=uuidsentinel.instance
        )
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type=mock.sentinel.machine_type
        )
        output = self.output.getvalue()
        self.assertEqual(2, ret)
        self.assertIn(
            f"Instance {uuidsentinel.instance} has no mapping to a cell.",
            output
        )

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_update_instance_not_found(self, mock_update):
        mock_update.side_effect = exception.InstanceNotFound(
            instance_id=uuidsentinel.instance
        )
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type=mock.sentinel.machine_type
        )
        output = self.output.getvalue()
        self.assertEqual(2, ret)
        self.assertIn(
            f"Instance {uuidsentinel.instance} could not be found.",
            output
        )

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_update_instance_invalid_state(self, mock_update):
        mock_update.side_effect = exception.InstanceInvalidState(
            instance_uuid=uuidsentinel.instance,
            attr='vm_state',
            state='ACTIVE',
            method='update machine type'
        )
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type=mock.sentinel.machine_type
        )
        output = self.output.getvalue()
        self.assertEqual(3, ret)
        self.assertIn(
            f"Instance {uuidsentinel.instance} in vm_state ACTIVE. Cannot "
            "update machine type while the instance is in this state.",
            output
        )

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_update_invalid_machine_type_update(self, mock_update):
        mock_update.side_effect = exception.InvalidMachineTypeUpdate(
            existing_machine_type='q35',
            machine_type='pc',
        )
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type=mock.sentinel.machine_type
        )
        output = self.output.getvalue()
        self.assertEqual(4, ret)
        self.assertIn(
            "Cannot update machine type q35 to pc.",
            output
        )

    @mock.patch('nova.virt.libvirt.machine_type_utils.update_machine_type')
    @mock.patch('nova.context.get_admin_context', new=mock.Mock())
    def test_update_unsupported_machine_type(self, mock_update):
        mock_update.side_effect = exception.UnsupportedMachineType(
            machine_type='foo'
        )
        ret = self.commands.update_machine_type(
            instance_uuid=uuidsentinel.instance,
            machine_type=mock.sentinel.machine_type
        )
        output = self.output.getvalue()
        self.assertEqual(5, ret)
        self.assertIn(
            "Machine type foo is not supported.",
            output
        )

    @mock.patch(
        'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
    @mock.patch('nova.context.get_admin_context')
    def test_list_unset_machine_type_none_found(
        self, mock_get_context, mock_get_instances
    ):
        mock_get_context.return_value = mock.sentinel.admin_context
        mock_get_instances.return_value = []
        ret = self.commands.list_unset_machine_type(
            cell_uuid=uuidsentinel.cell_uuid)
        mock_get_instances.assert_called_once_with(
            mock.sentinel.admin_context,
            uuidsentinel.cell_uuid
        )
        output = self.output.getvalue()
        self.assertEqual(0, ret)
        self.assertIn(
            "No instances found without hw_machine_type set.",
            output
        )

    @mock.patch(
        'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
    @mock.patch('nova.context.get_admin_context')
    def test_list_unset_machine_type_unknown_failure(
        self, mock_get_context, mock_get_instances
    ):
        mock_get_instances.side_effect = Exception('oops')
        ret = self.commands.list_unset_machine_type(
            cell_uuid=uuidsentinel.cell_uuid)
        output = self.output.getvalue().strip()
        self.assertIn(
            'Unexpected error, see nova-manage.log for the full trace: oops',
            output)
        self.assertEqual(1, ret)

    @mock.patch(
        'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
    @mock.patch('nova.context.get_admin_context')
    def test_list_unset_machine_type_cell_mapping_not_found(
        self, mock_get_context, mock_get_instances
    ):
        mock_get_context.return_value = mock.sentinel.admin_context
        mock_get_instances.side_effect = exception.CellMappingNotFound(
            uuid=uuidsentinel.cell_uuid
        )
        ret = self.commands.list_unset_machine_type(
            cell_uuid=uuidsentinel.cell_uuid)
        output = self.output.getvalue()
        self.assertEqual(2, ret)
        self.assertIn(
            f"Cell {uuidsentinel.cell_uuid} has no mapping",
            output
        )

    @mock.patch(
        'nova.virt.libvirt.machine_type_utils.get_instances_without_type')
    @mock.patch('nova.context.get_admin_context')
    def test_list_unset_machine_type(
        self, mock_get_context, mock_get_instances
    ):
        mock_get_context.return_value = mock.sentinel.admin_context
        mock_get_instances.return_value = [
            mock.Mock(spec=objects.Instance, uuid=uuidsentinel.instance)
        ]
        ret = self.commands.list_unset_machine_type(
            cell_uuid=uuidsentinel.cell_uuid)
        mock_get_instances.assert_called_once_with(
            mock.sentinel.admin_context,
            uuidsentinel.cell_uuid
        )
        output = self.output.getvalue()
        self.assertEqual(3, ret)
        self.assertIn(uuidsentinel.instance, output)


class ImagePropertyCommandsTestCase(test.NoDBTestCase):

    def setUp(self):
        super().setUp()
        self.output = StringIO()
        self.useFixture(fixtures.MonkeyPatch('sys.stdout', self.output))
        self.commands = manage.ImagePropertyCommands()

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_show_image_properties(
        self, mock_target_cell, mock_get_instance
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            }
        )
        ret = self.commands.show(
            instance_uuid=uuidsentinel.instance,
            image_property='hw_disk_bus')
        self.assertEqual(0, ret, 'return code')
        self.assertIn('virtio', self.output.getvalue(), 'command output')

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock())
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_show_image_properties_instance_not_found(
        self,
        mock_get_instance
    ):
        mock_get_instance.side_effect = exception.InstanceNotFound(
            instance_id=uuidsentinel.instance)
        ret = self.commands.show(
            instance_uuid=uuidsentinel.instance,
            image_property='hw_disk_bus')
        self.assertEqual(2, ret, 'return code')

    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_show_image_properties_instance_mapping_not_found(
        self,
        mock_get_instance_mapping
    ):
        mock_get_instance_mapping.side_effect = \
            exception.InstanceMappingNotFound(
                uuid=uuidsentinel.instance)
        ret = self.commands.show(
            instance_uuid=uuidsentinel.instance,
            image_property='hw_disk_bus')
        self.assertEqual(2, ret, 'return code')

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_show_image_properties_image_property_not_found(
        self, mock_target_cell, mock_get_instance
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            }
        )
        ret = self.commands.show(
            instance_uuid=uuidsentinel.instance,
            image_property='foo')
        self.assertEqual(3, ret, 'return code')

    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_show_image_properties_unknown_failure(
        self,
        mock_get_instance_mapping,
    ):
        mock_get_instance_mapping.side_effect = Exception()
        ret = self.commands.show(
            instance_uuid=uuidsentinel.instance,
            image_property='hw_disk_bus')
        self.assertEqual(1, ret, 'return code')

    @mock.patch('nova.objects.RequestSpec.save')
    @mock.patch('nova.objects.RequestSpec.get_by_instance_uuid')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.Instance.save')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties(
        self, mock_instance_save, mock_target_cell, mock_get_instance,
            mock_get_request_spec, mock_request_spec_save
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        instance = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            },
            image_ref=''
        )
        mock_get_instance.return_value = instance
        mock_get_request_spec.return_value = objects.RequestSpec()
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['hw_cdrom_bus=sata']
        )
        self.assertEqual(0, ret, 'return code')
        self.assertIn('image_hw_cdrom_bus', instance.system_metadata)
        self.assertEqual(
            'sata',
            instance.system_metadata.get('image_hw_cdrom_bus'),
            'image_hw_cdrom_bus'
        )
        self.assertEqual(
            'virtio',
            instance.system_metadata.get('image_hw_disk_bus'),
            'image_hw_disk_bus'
        )
        image_props = mock_get_request_spec.return_value.image.properties
        self.assertEqual('sata', image_props.get('hw_cdrom_bus'))
        self.assertEqual('virtio', image_props.get('hw_disk_bus'))

        mock_instance_save.assert_called_once()
        mock_request_spec_save.assert_called_once()

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock())
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties_instance_not_found(self, mock_get_instance):
        mock_get_instance.side_effect = exception.InstanceNotFound(
            instance_id=uuidsentinel.instance)
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['hw_disk_bus=virtio'])
        self.assertEqual(2, ret, 'return code')

    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid')
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties_instance_mapping_not_found(
        self,
        mock_get_instance_mapping
    ):
        mock_get_instance_mapping.side_effect = \
            exception.InstanceMappingNotFound(
                uuid=uuidsentinel.instance)
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['hw_disk_bus=virtio'])
        self.assertEqual(2, ret, 'return code')

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties_instance_invalid_state(
        self, mock_target_cell, mock_get_instance
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.ACTIVE,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            }
        )
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['hw_cdrom_bus=sata']
        )
        self.assertEqual(3, ret, 'return code')

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties_invalid_input(
        self, mock_target_cell, mock_get_instance
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.SHELVED,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            }
        )
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['hw_cdrom_bus'])
        self.assertEqual(4, ret, 'return code')

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties_invalid_property_name(
        self, mock_target_cell, mock_get_instance
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.SHELVED_OFFLOADED,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            }
        )
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['foo=bar'])
        self.assertEqual(5, ret, 'return code')

    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.context.target_cell')
    @mock.patch('nova.objects.InstanceMapping.get_by_instance_uuid',
                new=mock.Mock(cell_mapping=mock.sentinel.cm))
    @mock.patch('nova.context.get_admin_context',
                new=mock.Mock(return_value=mock.sentinel.ctxt))
    def test_set_image_properties_invalid_property_value(
        self, mock_target_cell, mock_get_instance
    ):
        mock_target_cell.return_value.__enter__.return_value = \
            mock.sentinel.cctxt
        mock_get_instance.return_value = objects.Instance(
            uuid=uuidsentinel.instance,
            vm_state=obj_fields.InstanceState.STOPPED,
            system_metadata={
                'image_hw_disk_bus': 'virtio',
            }
        )
        ret = self.commands.set(
            instance_uuid=uuidsentinel.instance,
            image_properties=['hw_disk_bus=bar'])
        self.assertEqual(6, ret, 'return code')
