# Copyright 2016 Canonical Ltd
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
import collections
import json

import eventlet
from oslo_config import cfg
import mock
from nova import context
from nova import exception
from nova import utils
from nova import test
from nova.compute import manager
from nova.compute import power_state
from nova.network import model as network_model
from nova.tests.unit import fake_instance
from pylxd import exceptions as lxdcore_exceptions
import six

from nova.virt.lxd import common
from nova.virt.lxd import driver

MockResponse = collections.namedtuple('Response', ['status_code'])

MockContainer = collections.namedtuple('Container', ['name'])
MockContainerState = collections.namedtuple(
    'ContainerState', ['status', 'memory', 'status_code'])

_VIF = {
    'devname': 'lol0', 'type': 'bridge', 'id': '0123456789abcdef',
    'address': 'ca:fe:de:ad:be:ef'}


def fake_connection_info(volume, location, iqn, auth=False, transport=None):
    dev_name = 'ip-%s-iscsi-%s-lun-1' % (location, iqn)
    if transport is not None:
        dev_name = 'pci-0000:00:00.0-' + dev_name
    dev_path = '/dev/disk/by-path/%s' % (dev_name)
    ret = {
        'driver_volume_type': 'iscsi',
        'data': {
            'volume_id': volume['id'],
            'target_portal': location,
            'target_iqn': iqn,
            'target_lun': 1,
            'device_path': dev_path,
            'qos_specs': {
                'total_bytes_sec': '102400',
                'read_iops_sec': '200',
            }
        }
    }
    if auth:
        ret['data']['auth_method'] = 'CHAP'
        ret['data']['auth_username'] = 'foo'
        ret['data']['auth_password'] = 'bar'
    return ret


class GetPowerStateTest(test.NoDBTestCase):
    """Tests for nova.virt.lxd.driver.LXDDriver."""

    def test_running(self):
        state = driver._get_power_state(100)
        self.assertEqual(power_state.RUNNING, state)

    def test_shutdown(self):
        state = driver._get_power_state(102)
        self.assertEqual(power_state.SHUTDOWN, state)

    def test_nostate(self):
        state = driver._get_power_state(105)
        self.assertEqual(power_state.NOSTATE, state)

    def test_crashed(self):
        state = driver._get_power_state(108)
        self.assertEqual(power_state.CRASHED, state)

    def test_suspended(self):
        state = driver._get_power_state(109)
        self.assertEqual(power_state.SUSPENDED, state)

    def test_unknown(self):
        self.assertRaises(ValueError, driver._get_power_state, 69)


class LXDDriverTest(test.NoDBTestCase):
    """Tests for nova.virt.lxd.driver.LXDDriver."""

    def setUp(self):
        super(LXDDriverTest, self).setUp()

        self.Client_patcher = mock.patch('nova.virt.lxd.driver.pylxd.Client')
        self.Client = self.Client_patcher.start()

        self.client = mock.Mock()
        self.client.host_info = {
            'environment': {
                'storage': 'zfs',
            }
        }
        self.Client.return_value = self.client

        self.patchers = []

        CONF_patcher = mock.patch('nova.virt.lxd.driver.CONF')
        self.patchers.append(CONF_patcher)
        self.CONF = CONF_patcher.start()
        self.CONF.instances_path = '/path/to/instances'
        self.CONF.my_ip = '0.0.0.0'
        self.CONF.config_drive_format = 'iso9660'

        # XXX: rockstar (03 Nov 2016) - This should be removed once
        # everything is where it should live.
        CONF2_patcher = mock.patch('nova.virt.lxd.driver.nova.conf.CONF')
        self.patchers.append(CONF2_patcher)
        self.CONF2 = CONF2_patcher.start()
        self.CONF2.lxd.root_dir = '/lxd'
        self.CONF2.instances_path = '/i'

        # LXDDriver._after_reboot reads from the database and syncs container
        # state. These tests can't read from the database.
        after_reboot_patcher = mock.patch(
            'nova.virt.lxd.driver.LXDDriver._after_reboot')
        self.patchers.append(after_reboot_patcher)
        self.after_reboot = after_reboot_patcher.start()

        bdige_patcher = mock.patch(
            'nova.virt.lxd.driver.driver.block_device_info_get_ephemerals')
        self.patchers.append(bdige_patcher)
        self.block_device_info_get_ephemerals = bdige_patcher.start()
        self.block_device_info_get_ephemerals.return_value = []

        vif_driver_patcher = mock.patch(
            'nova.virt.lxd.driver.lxd_vif.LXDGenericVifDriver')
        self.patchers.append(vif_driver_patcher)
        self.LXDGenericVifDriver = vif_driver_patcher.start()
        self.vif_driver = mock.Mock()
        self.LXDGenericVifDriver.return_value = self.vif_driver

        vif_gc_patcher = mock.patch('nova.virt.lxd.driver.lxd_vif.get_config')
        self.patchers.append(vif_gc_patcher)
        self.get_config = vif_gc_patcher.start()
        self.get_config.return_value = {
            'mac_address': '00:11:22:33:44:55', 'bridge': 'qbr0123456789a',
        }

        # NOTE: mock out fileutils to ensure that unit tests don't try
        #       to manipulate the filesystem (breaks in package builds).
        driver.fileutils = mock.Mock()

    def tearDown(self):
        super(LXDDriverTest, self).tearDown()
        self.Client_patcher.stop()
        for patcher in self.patchers:
            patcher.stop()

    def test_init_host(self):
        """init_host initializes the pylxd Client."""
        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        self.Client.assert_called_once_with()
        self.assertEqual(self.client, lxd_driver.client)

    def test_init_host_fail(self):
        def side_effect():
            raise lxdcore_exceptions.ClientConnectionFailed()
        self.Client.side_effect = side_effect
        self.Client.return_value = None

        lxd_driver = driver.LXDDriver(None)

        self.assertRaises(exception.HostNotFound, lxd_driver.init_host, None)

    def test_get_info(self):
        container = mock.Mock()
        container.state.return_value = MockContainerState(
            'Running', {'usage': 4000, 'usage_peak': 4500}, 100)
        self.client.containers.get.return_value = container

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        info = lxd_driver.get_info(instance)

        self.assertEqual(power_state.RUNNING, info.state)
        self.assertEqual(3, info.mem_kb)
        self.assertEqual(4, info.max_mem_kb)
        self.assertEqual(1, info.num_cpu)
        self.assertEqual(0, info.cpu_time_ns)

    def test_list_instances(self):
        self.client.containers.all.return_value = [
            MockContainer('mock-instance-1'),
            MockContainer('mock-instance-2'),
        ]
        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        instances = lxd_driver.list_instances()

        self.assertEqual(['mock-instance-1', 'mock-instance-2'], instances)

    @mock.patch('nova.virt.configdrive.required_by')
    def test_spawn(self, configdrive, neutron_failure=None):
        def container_get(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))
        self.client.containers.get.side_effect = container_get
        configdrive.return_value = False
        container = mock.Mock()
        self.client.containers.create.return_value = container

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = mock.Mock()
        injected_files = mock.Mock()
        admin_password = mock.Mock()
        network_info = [_VIF]
        block_device_info = mock.Mock()
        virtapi = manager.ComputeVirtAPI(mock.MagicMock())

        lxd_driver = driver.LXDDriver(virtapi)
        lxd_driver.init_host(None)
        # XXX: rockstar (6 Jul 2016) - There are a number of XXX comments
        # related to these calls in spawn. They require some work before we
        # can take out these mocks and follow the real codepaths.
        lxd_driver.firewall_driver = mock.Mock()

        lxd_driver.spawn(
            ctx, instance, image_meta, injected_files, admin_password,
            network_info, block_device_info)

        self.vif_driver.plug.assert_called_once_with(
            instance, network_info[0])
        fd = lxd_driver.firewall_driver
        fd.setup_basic_filtering.assert_called_once_with(
            instance, network_info)
        fd.apply_instance_filter.assert_called_once_with(
            instance, network_info)
        container.start.assert_called_once_with(wait=True)

    def test_spawn_already_exists(self):
        """InstanceExists is raised if the container already exists."""
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = mock.Mock()
        injected_files = mock.Mock()
        admin_password = mock.Mock()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        self.assertRaises(
            exception.InstanceExists,

            lxd_driver.spawn,
            ctx, instance, image_meta, injected_files, admin_password,
            None, None)

    @mock.patch('nova.virt.configdrive.required_by')
    def test_spawn_with_configdrive(self, configdrive):
        def container_get(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))

        self.client.containers.get.side_effect = container_get
        configdrive.return_value = True

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = mock.Mock()
        injected_files = mock.Mock()
        admin_password = mock.Mock()
        network_info = [_VIF]
        block_device_info = mock.Mock()
        virtapi = manager.ComputeVirtAPI(mock.MagicMock())

        lxd_driver = driver.LXDDriver(virtapi)
        lxd_driver.init_host(None)
        # XXX: rockstar (6 Jul 2016) - There are a number of XXX comments
        # related to these calls in spawn. They require some work before we
        # can take out these mocks and follow the real codepaths.
        lxd_driver.firewall_driver = mock.Mock()
        lxd_driver._add_configdrive = mock.Mock()

        lxd_driver.spawn(
            ctx, instance, image_meta, injected_files, admin_password,
            network_info, block_device_info)

        self.vif_driver.plug.assert_called_once_with(
            instance, network_info[0])
        fd = lxd_driver.firewall_driver
        fd.setup_basic_filtering.assert_called_once_with(
            instance, network_info)
        fd.apply_instance_filter.assert_called_once_with(
            instance, network_info)
        configdrive.assert_called_once_with(instance)
        lxd_driver.client.profiles.get.assert_called_once_with(instance.name)

    @mock.patch('nova.virt.configdrive.required_by')
    def test_spawn_profile_fail(self, configdrive, neutron_failure=None):
        """Cleanup is called when profile creation fails."""
        def container_get(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))

        def profile_create(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(500))
        self.client.containers.get.side_effect = container_get
        self.client.profiles.create.side_effect = profile_create
        configdrive.return_value = False
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = mock.Mock()
        injected_files = mock.Mock()
        admin_password = mock.Mock()
        network_info = [_VIF]
        block_device_info = mock.Mock()
        virtapi = manager.ComputeVirtAPI(mock.MagicMock())

        lxd_driver = driver.LXDDriver(virtapi)
        lxd_driver.init_host(None)
        lxd_driver.cleanup = mock.Mock()

        self.assertRaises(
            lxdcore_exceptions.LXDAPIException,
            lxd_driver.spawn,
            ctx, instance, image_meta, injected_files, admin_password,
            network_info, block_device_info)
        lxd_driver.cleanup.assert_called_once_with(
            ctx, instance, network_info, block_device_info)

    @mock.patch('nova.virt.configdrive.required_by')
    def test_spawn_container_fail(self, configdrive, neutron_failure=None):
        """Cleanup is called when container creation fails."""
        def container_get(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))

        def container_create(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(500))
        self.client.containers.get.side_effect = container_get
        self.client.containers.create.side_effect = container_create
        configdrive.return_value = False
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = mock.Mock()
        injected_files = mock.Mock()
        admin_password = mock.Mock()
        network_info = [_VIF]
        block_device_info = mock.Mock()
        virtapi = manager.ComputeVirtAPI(mock.MagicMock())

        lxd_driver = driver.LXDDriver(virtapi)
        lxd_driver.init_host(None)
        lxd_driver.cleanup = mock.Mock()

        self.assertRaises(
            lxdcore_exceptions.LXDAPIException,
            lxd_driver.spawn,
            ctx, instance, image_meta, injected_files, admin_password,
            network_info, block_device_info)
        lxd_driver.cleanup.assert_called_once_with(
            ctx, instance, network_info, block_device_info)

    def test_spawn_container_start_fail(self, neutron_failure=None):
        def container_get(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))

        def side_effect(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(200))

        self.client.containers.get.side_effect = container_get
        container = mock.Mock()
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = mock.Mock()
        injected_files = mock.Mock()
        admin_password = mock.Mock()
        network_info = [_VIF]
        block_device_info = mock.Mock()
        virtapi = manager.ComputeVirtAPI(mock.MagicMock())

        lxd_driver = driver.LXDDriver(virtapi)
        lxd_driver.init_host(None)
        lxd_driver.cleanup = mock.Mock()
        lxd_driver.client.containers.create = mock.Mock(
            side_effect=side_effect)
        container.start.side_effect = side_effect

        self.assertRaises(
            lxdcore_exceptions.LXDAPIException,
            lxd_driver.spawn,
            ctx, instance, image_meta, injected_files, admin_password,
            network_info, block_device_info)
        lxd_driver.cleanup.assert_called_once_with(
            ctx, instance, network_info, block_device_info)

    def _test_spawn_instance_with_network_events(self, neutron_failure=None):
        generated_events = []

        def wait_timeout():
            event = mock.MagicMock()
            if neutron_failure == 'timeout':
                raise eventlet.timeout.Timeout()
            elif neutron_failure == 'error':
                event.status = 'failed'
            else:
                event.status = 'completed'
            return event

        def fake_prepare(instance, event_name):
            m = mock.MagicMock()
            m.instance = instance
            m.event_name = event_name
            m.wait.side_effect = wait_timeout
            generated_events.append(m)
            return m

        virtapi = manager.ComputeVirtAPI(mock.MagicMock())
        prepare = virtapi._compute.instance_events.prepare_for_instance_event
        prepare.side_effect = fake_prepare
        drv = driver.LXDDriver(virtapi)

        instance_href = fake_instance.fake_instance_obj(
            context.get_admin_context(), name='test', memory_mb=0)

        @mock.patch.object(drv, 'plug_vifs')
        @mock.patch('nova.virt.configdrive.required_by')
        def test_spawn(configdrive, plug_vifs):
            def container_get(*args, **kwargs):
                raise lxdcore_exceptions.LXDAPIException(MockResponse(404))
            self.client.containers.get.side_effect = container_get
            configdrive.return_value = False

            ctx = context.get_admin_context()
            instance = fake_instance.fake_instance_obj(
                ctx, name='test', memory_mb=0)
            image_meta = mock.Mock()
            injected_files = mock.Mock()
            admin_password = mock.Mock()
            network_info = [_VIF]
            block_device_info = mock.Mock()

            drv.init_host(None)
            drv.spawn(
                ctx, instance, image_meta, injected_files, admin_password,
                network_info, block_device_info)

        test_spawn()

        if cfg.CONF.vif_plugging_timeout and utils.is_neutron():
            prepare.assert_has_calls([
                mock.call(instance_href, 'network-vif-plugged-vif1'),
                mock.call(instance_href, 'network-vif-plugged-vif2')])
            for event in generated_events:
                if neutron_failure and generated_events.index(event) != 0:
                    self.assertEqual(0, event.call_count)
        else:
            self.assertEqual(0, prepare.call_count)

    @mock.patch('nova.utils.is_neutron', return_value=True)
    def test_spawn_instance_with_network_events(self, is_neutron):
        self.flags(vif_plugging_timeout=0)
        self._test_spawn_instance_with_network_events()

    @mock.patch('nova.utils.is_neutron', return_value=True)
    def test_spawn_instance_with_events_neutron_failed_nonfatal_timeout(
            self, is_neutron):
        self.flags(vif_plugging_timeout=0)
        self.flags(vif_plugging_is_fatal=False)
        self._test_spawn_instance_with_network_events(
            neutron_failure='timeout')

    def test_destroy(self):
        mock_container = mock.Mock()
        mock_container.status = 'Running'
        self.client.containers.get.return_value = mock_container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        network_info = [_VIF]

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        lxd_driver.cleanup = mock.Mock()  # There is a separate cleanup test

        lxd_driver.destroy(ctx, instance, network_info)

        lxd_driver.cleanup.assert_called_once_with(
            ctx, instance, network_info, None)
        lxd_driver.client.containers.get.assert_called_once_with(instance.name)
        mock_container.stop.assert_called_once_with(wait=True)
        mock_container.delete.assert_called_once_with(wait=True)

    def test_destroy_without_instance(self):
        def side_effect(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))
        self.client.containers.get.side_effect = side_effect

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        network_info = [_VIF]

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        lxd_driver.cleanup = mock.Mock()  # There is a separate cleanup test

        lxd_driver.destroy(ctx, instance, network_info)
        lxd_driver.cleanup.assert_called_once_with(
            ctx, instance, network_info, None)

    @mock.patch('nova.virt.lxd.driver.network')
    @mock.patch('os.path.exists', mock.Mock(return_value=True))
    @mock.patch('pwd.getpwuid')
    @mock.patch('shutil.rmtree')
    @mock.patch.object(driver.utils, 'execute')
    def test_cleanup(self, execute, rmtree, getpwuid, _):
        mock_profile = mock.Mock()
        self.client.profiles.get.return_value = mock_profile
        pwuid = mock.Mock()
        pwuid.pw_name = 'user'
        getpwuid.return_value = pwuid

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        network_info = [_VIF]
        instance_dir = common.InstanceAttributes(instance).instance_dir
        block_device_info = mock.Mock()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        lxd_driver.firewall_driver = mock.Mock()

        lxd_driver.cleanup(ctx, instance, network_info, block_device_info)

        self.vif_driver.unplug.assert_called_once_with(
            instance, network_info[0])
        lxd_driver.firewall_driver.unfilter_instance.assert_called_once_with(
            instance, network_info)
        execute.assert_called_once_with(
            'chown', '-R', 'user:user', instance_dir, run_as_root=True)
        rmtree.assert_called_once_with(instance_dir)
        mock_profile.delete.assert_called_once_with()

    def test_reboot(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.reboot(ctx, instance, None, None)

        self.client.containers.get.assert_called_once_with(instance.name)

    @mock.patch('nova.virt.lxd.driver.network')
    @mock.patch('pwd.getpwuid', mock.Mock(return_value=mock.Mock(pw_uid=1234)))
    @mock.patch('os.getuid', mock.Mock())
    @mock.patch('os.path.exists', mock.Mock(return_value=True))
    @mock.patch('six.moves.builtins.open')
    @mock.patch.object(driver.utils, 'execute')
    def test_get_console_output(self, execute, _open, _):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        expected_calls = [
            mock.call(
                'chown', '1234:1234', '/var/log/lxd/{}/console.log'.format(
                    instance.name),
                run_as_root=True),
            mock.call(
                'chmod', '755', '/lxd/containers/{}'.format(
                    instance.name),
                run_as_root=True),
        ]
        _open.return_value.__enter__.return_value = six.BytesIO(b'output')

        lxd_driver = driver.LXDDriver(None)

        contents = lxd_driver.get_console_output(context, instance)

        self.assertEqual(b'output', contents)
        self.assertEqual(expected_calls, execute.call_args_list)

    def test_get_host_ip_addr(self):
        lxd_driver = driver.LXDDriver(None)

        result = lxd_driver.get_host_ip_addr()

        self.assertEqual('0.0.0.0', result)

    def test_attach_interface(self):
        expected = {
            'hwaddr': '00:11:22:33:44:55',
            'parent': 'qbr0123456789a',
            'nictype': 'bridged',
            'type': 'nic',
        }

        profile = mock.Mock()
        profile.devices = {
            'eth0': {
                'name': 'eth0',
                'nictype': 'bridged',
                'parent': 'lxdbr0',
                'type': 'nic'
            },
            'root': {
                'path': '/',
                'type': 'disk'
            },
        }
        self.client.profiles.get.return_value = profile

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_meta = None
        vif = {
            'id': '0123456789abcdef',
            'type': network_model.VIF_TYPE_OVS,
            'address': '00:11:22:33:44:55',
            'network': {
                'bridge': 'fakebr'}}

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        lxd_driver.firewall_driver = mock.Mock()

        lxd_driver.attach_interface(ctx, instance, image_meta, vif)

        self.assertTrue('eth1' in profile.devices)
        self.assertEqual(expected, profile.devices['eth1'])
        profile.save.assert_called_once_with(wait=True)

    def test_detach_interface(self):
        profile = mock.Mock()
        profile.devices = {
            'eth0': {
                'name': 'eth0',
                'nictype': 'bridged',
                'parent': 'lxdbr0',
                'hwaddr': '00:11:22:33:44:55',
                'type': 'nic'
            },
            'root': {
                'path': '/',
                'type': 'disk'
            },
        }
        self.client.profiles.get.return_value = profile

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        vif = {
            'id': '0123456789abcdef',
            'type': network_model.VIF_TYPE_OVS,
            'address': '00:11:22:33:44:55',
            'network': {
                'bridge': 'fakebr'}}

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.detach_interface(ctx, instance, vif)

        self.vif_driver.unplug.assert_called_once_with(
            instance, vif)
        self.assertEqual(['root'], sorted(profile.devices.keys()))
        profile.save.assert_called_once_with(wait=True)

    def test_migrate_disk_and_power_off(self):
        container = mock.Mock()
        self.client.containers.get.return_value = container
        profile = mock.Mock()
        self.client.profiles.get.return_value = profile

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        dest = '0.0.0.0'
        flavor = mock.Mock()
        network_info = []

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.migrate_disk_and_power_off(
            ctx, instance, dest, flavor, network_info)

        profile.save.assert_called_once_with()
        container.stop.assert_called_once_with(wait=True)

    def test_migrate_disk_and_power_off_different_host(self):
        """Migrating to a different host only shuts down the container."""
        container = mock.Mock()
        self.client.containers.get.return_value = container

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        dest = '0.0.0.1'
        flavor = mock.Mock()
        network_info = []

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.migrate_disk_and_power_off(
            ctx, instance, dest, flavor, network_info)

        self.assertEqual(0, self.client.profiles.get.call_count)
        container.stop.assert_called_once_with(wait=True)

    @mock.patch('nova.virt.lxd.driver.network')
    @mock.patch('os.major')
    @mock.patch('os.minor')
    @mock.patch('os.stat')
    @mock.patch('os.path.realpath')
    def test_attach_volume(self, realpath, stat, minor, major, _):
        profile = mock.Mock()
        self.client.profiles.get.return_value = profile
        realpath.return_value = '/dev/sdc'
        stat.return_value.st_rdev = 2080
        minor.return_value = 32
        major.return_value = 8
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        connection_info = fake_connection_info(
            {'id': 1, 'name': 'volume-00000001'},
            '10.0.2.15:3260', 'iqn.2010-10.org.openstack:volume-00000001',
            auth=True)
        mountpoint = '/dev/sdd'

        driver.brick_get_connector = mock.MagicMock()
        driver.brick_get_connector_properties = mock.MagicMock()
        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        # driver.brick_get_connector = mock.MagicMock()
        # lxd_driver.storage_driver.connect_volume = mock.MagicMock()
        lxd_driver.attach_volume(
            ctx, connection_info, instance, mountpoint, None, None, None)

        lxd_driver.client.profiles.get.assert_called_once_with(instance.name)
        # driver.brick_get_connector.connect_volume.assert_called_once_with(
        #     connection_info['data'])
        profile.save.assert_called_once_with()

    def test_detach_volume(self):
        profile = mock.Mock()
        profile.devices = {
            'eth0': {
                'name': 'eth0',
                'nictype': 'bridged',
                'parent': 'lxdbr0',
                'type': 'nic'
            },
            'root': {
                'path': '/',
                'type': 'disk'
            },
            1: {
                'path': '/dev/sdc',
                'type': 'unix-block'
            },
        }

        expected = {
            'eth0': {
                'name': 'eth0',
                'nictype': 'bridged',
                'parent': 'lxdbr0',
                'type': 'nic'
            },
            'root': {
                'path': '/',
                'type': 'disk'
            },
        }

        self.client.profiles.get.return_value = profile
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        connection_info = fake_connection_info(
            {'id': 1, 'name': 'volume-00000001'},
            '10.0.2.15:3260', 'iqn.2010-10.org.openstack:volume-00000001',
            auth=True)
        mountpoint = mock.Mock()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        driver.brick_get_connector = mock.MagicMock()
        driver.brick_get_connector_properties = mock.MagicMock()
        lxd_driver.detach_volume(connection_info, instance, mountpoint, None)

        lxd_driver.client.profiles.get.assert_called_once_with(instance.name)

        self.assertEqual(expected, profile.devices)
        profile.save.assert_called_once_with()

    def test_pause(self):
        container = mock.Mock()
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.pause(instance)

        self.client.containers.get.assert_called_once_with(instance.name)
        container.freeze.assert_called_once_with(wait=True)

    def test_unpause(self):
        container = mock.Mock()
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.unpause(instance)

        self.client.containers.get.assert_called_once_with(instance.name)
        container.unfreeze.assert_called_once_with(wait=True)

    def test_suspend(self):
        container = mock.Mock()
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.suspend(ctx, instance)

        self.client.containers.get.assert_called_once_with(instance.name)
        container.freeze.assert_called_once_with(wait=True)

    def test_resume(self):
        container = mock.Mock()
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.resume(ctx, instance, None, None)

        self.client.containers.get.assert_called_once_with(instance.name)
        container.unfreeze.assert_called_once_with(wait=True)

    def test_rescue(self):
        profile = mock.Mock()
        profile.devices = {
            'root': {
                'type': 'disk',
                'path': '/',
                'size': '1GB'
            }
        }
        container = mock.Mock()
        self.client.profiles.get.return_value = profile
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        profile.name = instance.name
        network_info = [_VIF]
        image_meta = mock.Mock()
        rescue_password = mock.Mock()
        rescue = '%s-rescue' % instance.name

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.rescue(
            ctx, instance, network_info, image_meta, rescue_password)

        lxd_driver.client.containers.get.assert_called_once_with(instance.name)
        container.rename.assert_called_once_with(rescue, wait=True)
        lxd_driver.client.profiles.get.assert_called_once_with(instance.name)
        lxd_driver.client.containers.create.assert_called_once_with(
            {'name': instance.name, 'profiles': [profile.name],
             'source': {'type': 'image', 'alias': None},
             }, wait=True)

        self.assertTrue('rescue' in profile.devices)

    def test_unrescue(self):
        container = mock.Mock()
        container.status = 'Running'
        self.client.containers.get.return_value = container
        profile = mock.Mock()
        profile.devices = {
            'root': {
                'type': 'disk',
                'path': '/',
                'size': '1GB'
            },
            'rescue': {
                'source': '/path',
                'path': '/mnt',
                'type': 'disk'
            }
        }
        self.client.profiles.get.return_value = profile

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        network_info = [_VIF]
        rescue = '%s-rescue' % instance.name

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.unrescue(instance, network_info)

        container.stop.assert_called_once_with(wait=True)
        container.delete.assert_called_once_with(wait=True)
        lxd_driver.client.profiles.get.assert_called_once_with(instance.name)
        profile.save.assert_called_once_with()
        lxd_driver.client.containers.get.assert_called_with(rescue)
        container.rename.assert_called_once_with(instance.name, wait=True)
        container.start.assert_called_once_with(wait=True)
        self.assertTrue('rescue' not in profile.devices)

    def test_power_off(self):
        container = mock.Mock()
        container.status = 'Running'
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        lxd_driver.power_off(instance)

        self.client.containers.get.assert_called_once_with(instance.name)
        container.stop.assert_called_once_with(wait=True)

    def test_power_on(self):
        container = mock.Mock()
        container.status = 'Stopped'
        self.client.containers.get.return_value = container
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)
        lxd_driver.power_on(ctx, instance, None)

        self.client.containers.get.assert_called_once_with(instance.name)
        container.start.assert_called_once_with(wait=True)

    @mock.patch('socket.gethostname', mock.Mock(return_value='fake_hostname'))
    @mock.patch('os.statvfs', return_value=mock.Mock(
        f_blocks=131072000, f_bsize=8192, f_bavail=65536000))
    @mock.patch('nova.virt.lxd.driver.open')
    @mock.patch.object(driver.utils, 'execute')
    def test_get_available_resource(self, execute, open, statvfs):
        expected = {
            'cpu_info': {
                "features": "fake flag goes here",
                "model": "Fake CPU",
                "topology": {"sockets": "10", "threads": "4", "cores": "5"},
                "arch": "x86_64", "vendor": "FakeVendor"
            },
            'hypervisor_hostname': 'fake_hostname',
            'hypervisor_type': 'lxd',
            'hypervisor_version': '011',
            'local_gb': 1000,
            'local_gb_used': 500,
            'memory_mb': 10000,
            'memory_mb_used': 8000,
            'numa_topology': None,
            'supported_instances': [
                ('i686', 'lxd', 'exe'),
                ('x86_64', 'lxd', 'exe'),
                ('i686', 'lxc', 'exe'),
                ('x86_64', 'lxc', 'exe')],
            'vcpus': 200,
            'vcpus_used': 0}

        execute.return_value = (
            'Model name:          Fake CPU\n'
            'Vendor ID:           FakeVendor\n'
            'Socket(s):           10\n'
            'Core(s) per socket:  5\n'
            'Thread(s) per core:  4\n\n',
            None)
        meminfo = mock.MagicMock()
        meminfo.__enter__.return_value = six.moves.cStringIO(
            'MemTotal: 10240000 kB\n'
            'MemFree:   2000000 kB\n'
            'Buffers:     24000 kB\n'
            'Cached:      24000 kB\n')

        open.side_effect = [
            six.moves.cStringIO('flags: fake flag goes here\n'
                                'processor: 2\n'
                                '\n'),
            meminfo,
        ]
        lxd_config = {
            'environment': {
                'storage': 'dir',
            },
            'config': {}
        }
        lxd_driver = driver.LXDDriver(None)
        lxd_driver.client = mock.MagicMock()
        lxd_driver.client.host_info = lxd_config
        value = lxd_driver.get_available_resource(None)
        # This is funky, but json strings make for fragile tests.
        value['cpu_info'] = json.loads(value['cpu_info'])

        self.assertEqual(expected, value)

    @mock.patch('socket.gethostname', mock.Mock(return_value='fake_hostname'))
    @mock.patch('nova.virt.lxd.driver.open')
    @mock.patch.object(driver.utils, 'execute')
    def test_get_available_resource_zfs(self, execute, open):
        expected = {
            'cpu_info': {
                "features": "fake flag goes here",
                "model": "Fake CPU",
                "topology": {"sockets": "10", "threads": "4", "cores": "5"},
                "arch": "x86_64", "vendor": "FakeVendor"
            },
            'hypervisor_hostname': 'fake_hostname',
            'hypervisor_type': 'lxd',
            'hypervisor_version': '011',
            'local_gb': 2222,
            'local_gb_used': 200,
            'memory_mb': 10000,
            'memory_mb_used': 8000,
            'numa_topology': None,
            'supported_instances': [
                ('i686', 'lxd', 'exe'),
                ('x86_64', 'lxd', 'exe'),
                ('i686', 'lxc', 'exe'),
                ('x86_64', 'lxc', 'exe')],
            'vcpus': 200,
            'vcpus_used': 0}

        execute.side_effect = [
            ('Model name:          Fake CPU\n'
             'Vendor ID:           FakeVendor\n'
             'Socket(s):           10\n'
             'Core(s) per socket:  5\n'
             'Thread(s) per core:  4\n\n',
             None),
            ('2.17T\n', None),
            ('200.4G\n', None),
            ('1.8T\n', None)
        ]

        meminfo = mock.MagicMock()
        meminfo.__enter__.return_value = six.moves.cStringIO(
            'MemTotal: 10240000 kB\n'
            'MemFree:   2000000 kB\n'
            'Buffers:     24000 kB\n'
            'Cached:      24000 kB\n')

        open.side_effect = [
            six.moves.cStringIO('flags: fake flag goes here\n'
                                'processor: 2\n'
                                '\n'),
            meminfo,
        ]
        lxd_config = {
            'environment': {
                'storage': 'zfs',
            },
            'config': {
                'storage.zfs_pool_name': 'lxd',
            }
        }
        lxd_driver = driver.LXDDriver(None)
        lxd_driver.client = mock.MagicMock()
        lxd_driver.client.host_info = lxd_config
        value = lxd_driver.get_available_resource(None)
        # This is funky, but json strings make for fragile tests.
        value['cpu_info'] = json.loads(value['cpu_info'])

        self.assertEqual(expected, value)

    def test_refresh_instance_security_rules(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        firewall = mock.Mock()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.firewall_driver = firewall
        lxd_driver.refresh_instance_security_rules(instance)

        firewall.refresh_instance_security_rules.assert_called_once_with(
            instance)

    def test_ensure_filtering_rules_for_instance(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        firewall = mock.Mock()
        network_info = object()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.firewall_driver = firewall
        lxd_driver.ensure_filtering_rules_for_instance(instance, network_info)

        firewall.ensure_filtering_rules_for_instance.assert_called_once_with(
            instance, network_info)

    def test_filter_defer_apply_on(self):
        firewall = mock.Mock()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.firewall_driver = firewall
        lxd_driver.filter_defer_apply_on()

        firewall.filter_defer_apply_on.assert_called_once_with()

    def test_filter_defer_apply_off(self):
        firewall = mock.Mock()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.firewall_driver = firewall
        lxd_driver.filter_defer_apply_off()

        firewall.filter_defer_apply_off.assert_called_once_with()

    def test_unfilter_instance(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        firewall = mock.Mock()
        network_info = object()

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.firewall_driver = firewall
        lxd_driver.unfilter_instance(instance, network_info)

        firewall.unfilter_instance.assert_called_once_with(
            instance, network_info)

    @mock.patch.object(driver.utils, 'execute')
    def test_get_host_uptime(self, execute):
        expected = '00:00:00 up 0 days, 0:00 , 0 users, load average: 0'
        execute.return_value = (expected, 'stderr')

        lxd_driver = driver.LXDDriver(None)
        result = lxd_driver.get_host_uptime()

        self.assertEqual(expected, result)

    @mock.patch('nova.virt.lxd.driver.psutil.cpu_times')
    @mock.patch('nova.virt.lxd.driver.open')
    @mock.patch.object(driver.utils, 'execute')
    def test_get_host_cpu_stats(self, execute, open, cpu_times):
        cpu_times.return_value = [
            '1', 'b', '2', '3', '4'
        ]
        execute.return_value = (
            'Model name:          Fake CPU\n'
            'Vendor ID:           FakeVendor\n'
            'Socket(s):           10\n'
            'Core(s) per socket:  5\n'
            'Thread(s) per core:  4\n\n',
            None)
        open.return_value = six.moves.cStringIO(
            'flags: fake flag goes here\n'
            'processor: 2\n\n')

        expected = {
            'user': 1, 'iowait': 4, 'frequency': 0, 'kernel': 2, 'idle': 3}

        lxd_driver = driver.LXDDriver(None)
        result = lxd_driver.get_host_cpu_stats()

        self.assertEqual(expected, result)

    def test_get_volume_connector(self):
        expected = {
            'host': 'fakehost',
            'initiator': 'fake',
            'ip': self.CONF.my_block_storage_ip
        }

        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)

        lxd_driver = driver.LXDDriver(None)
        result = lxd_driver.get_volume_connector(instance)

        self.assertEqual(expected, result)

    @mock.patch('nova.virt.lxd.driver.socket.gethostname')
    def test_get_available_nodes(self, gethostname):
        gethostname.return_value = 'nova-lxd'

        expected = ['nova-lxd']

        lxd_driver = driver.LXDDriver(None)
        result = lxd_driver.get_available_nodes()

        self.assertEqual(expected, result)

    @mock.patch('nova.virt.lxd.driver.IMAGE_API')
    @mock.patch('nova.virt.lxd.driver.lockutils.lock')
    def test_snapshot(self, lock, IMAGE_API):
        update_task_state_expected = [
            mock.call(task_state='image_pending_upload'),
            mock.call(
                expected_state='image_pending_upload',
                task_state='image_uploading'),
        ]

        container = mock.Mock()
        self.client.containers.get.return_value = container
        image = mock.Mock()
        container.publish.return_value = image
        data = mock.Mock()
        image.export.return_value = data
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        image_id = mock.Mock()
        update_task_state = mock.Mock()
        snapshot = {'name': mock.Mock()}
        IMAGE_API.get.return_value = snapshot

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.snapshot(ctx, instance, image_id, update_task_state)

        self.assertEqual(
            update_task_state_expected, update_task_state.call_args_list)
        IMAGE_API.get.assert_called_once_with(ctx, image_id)
        IMAGE_API.update.assert_called_once_with(
            ctx, image_id, {
                'name': snapshot['name'],
                'disk_format': 'raw',
                'container_format': 'bare'},
            data)

    def test_finish_revert_migration(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        network_info = []

        container = mock.Mock()
        self.client.containers.get.return_value = container

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.finish_revert_migration(ctx, instance, network_info)

        container.start.assert_called_once_with(wait=True)

    def test_check_can_live_migrate_destination(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        src_compute_info = mock.Mock()
        dst_compute_info = mock.Mock()

        def container_get(*args, **kwargs):
            raise lxdcore_exceptions.LXDAPIException(MockResponse(404))
        self.client.containers.get.side_effect = container_get

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        retval = lxd_driver.check_can_live_migrate_destination(
            ctx, instance, src_compute_info, dst_compute_info)

        self.assertIsInstance(retval, driver.LXDLiveMigrateData)

    def test_confirm_migration(self):
        migration = mock.Mock()
        instance = fake_instance.fake_instance_obj(
            context.get_admin_context, name='test', memory_mb=0)
        network_info = []
        profile = mock.Mock()
        container = mock.Mock()
        self.client.profiles.get.return_value = profile
        self.client.containers.get.return_value = container

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.confirm_migration(migration, instance, network_info)

        profile.delete.assert_called_once_with()
        container.delete.assert_called_once_with(wait=True)

    def test_post_live_migration(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        container = mock.Mock()
        self.client.containers.get.return_value = container

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.init_host(None)

        lxd_driver.post_live_migration(context, instance, None)

        container.delete.assert_called_once_with(wait=True)

    def test_post_live_migration_at_source(self):
        ctx = context.get_admin_context()
        instance = fake_instance.fake_instance_obj(
            ctx, name='test', memory_mb=0)
        network_info = []
        profile = mock.Mock()
        self.client.profiles.get.return_value = profile

        lxd_driver = driver.LXDDriver(None)
        lxd_driver.cleanup = mock.Mock()
        lxd_driver.init_host(None)

        lxd_driver.post_live_migration_at_source(
            ctx, instance, network_info)

        profile.delete.assert_called_once_with()
        lxd_driver.cleanup.assert_called_once_with(ctx, instance, network_info)
