# Copyright (c) 2016 Red Hat, Inc
#
#    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.

from collections import deque
import copy
import textwrap

from lxml import etree
import mock
from oslo_utils.fixture import uuidsentinel as uuids
from oslo_utils import units

from nova.compute import power_state
from nova import exception
from nova.network import model as network_model
from nova import objects
from nova import test
from nova.tests import fixtures as nova_fixtures
from nova.tests.fixtures import libvirt as fakelibvirt
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt import guest as libvirt_guest
from nova.virt.libvirt import host
from nova.virt.libvirt import migration


class UtilityMigrationTestCase(test.NoDBTestCase):

    def test_graphics_listen_addrs(self):
        data = objects.LibvirtLiveMigrateData(
            graphics_listen_addr_vnc='127.0.0.1',
            graphics_listen_addr_spice='127.0.0.2')
        addrs = migration.graphics_listen_addrs(data)
        self.assertEqual({
            'vnc': '127.0.0.1',
            'spice': '127.0.0.2'}, addrs)

    def test_graphics_listen_addrs_empty(self):
        data = objects.LibvirtLiveMigrateData()
        addrs = migration.graphics_listen_addrs(data)
        self.assertIsNone(addrs)

    def test_graphics_listen_addrs_spice(self):
        data = objects.LibvirtLiveMigrateData(
            graphics_listen_addr_spice='127.0.0.2')
        addrs = migration.graphics_listen_addrs(data)
        self.assertEqual({
            'vnc': None,
            'spice': '127.0.0.2'}, addrs)

    def test_graphics_listen_addrs_vnc(self):
        data = objects.LibvirtLiveMigrateData(
            graphics_listen_addr_vnc='127.0.0.1')
        addrs = migration.graphics_listen_addrs(data)
        self.assertEqual({
            'vnc': '127.0.0.1',
            'spice': None}, addrs)

    @mock.patch('lxml.etree.tostring')
    @mock.patch.object(migration, '_update_memory_backing_xml')
    @mock.patch.object(migration, '_update_perf_events_xml')
    @mock.patch.object(migration, '_update_graphics_xml')
    @mock.patch.object(migration, '_update_serial_xml')
    @mock.patch.object(migration, '_update_volume_xml')
    def test_get_updated_guest_xml(
            self, mock_volume, mock_serial, mock_graphics,
            mock_perf_events_xml, mock_memory_backing, mock_tostring):
        data = objects.LibvirtLiveMigrateData()
        mock_guest = mock.Mock(spec=libvirt_guest.Guest)
        get_volume_config = mock.MagicMock()
        mock_guest.get_xml_desc.return_value = '<domain></domain>'

        migration.get_updated_guest_xml(
            mock.sentinel.instance, mock_guest, data, get_volume_config)
        mock_graphics.assert_called_once_with(mock.ANY, data)
        mock_serial.assert_called_once_with(mock.ANY, data)
        mock_volume.assert_called_once_with(
            mock.ANY, data, mock.sentinel.instance, get_volume_config)
        mock_perf_events_xml.assert_called_once_with(mock.ANY, data)
        mock_memory_backing.assert_called_once_with(mock.ANY, data)
        self.assertEqual(1, mock_tostring.called)

    def test_update_device_resources_xml_vpmem(self):
        # original xml for vpmems, /dev/dax0.1 and /dev/dax0.2 here
        # are vpmem device path on source host
        old_xml = textwrap.dedent("""
            <domain>
                <devices>
                    <memory model='nvdimm'>
                        <source>
                            <path>/dev/dax0.1</path>
                            <alignsize>2048</alignsize>
                            <pmem>on</pmem>
                        </source>
                        <target>
                            <size>4192256</size>
                            <label>
                                <size>2048</size>
                            </label>
                            <node>0</node>
                        </target>
                    </memory>
                    <memory model='nvdimm'>
                        <source>
                            <path>/dev/dax0.2</path>
                            <alignsize>2048</alignsize>
                            <pmem>on</pmem>
                        </source>
                        <target>
                            <size>4192256</size>
                            <label>
                                <size>2048</size>
                            </label>
                            <node>0</node>
                        </target>
                    </memory>
                </devices>
            </domain>""")
        doc = etree.fromstring(old_xml)
        vpmem_resource_0 = objects.Resource(
            provider_uuid=uuids.rp_uuid,
            resource_class="CUSTOM_PMEM_NAMESPACE_4GB",
            identifier='ns_0',
            metadata= objects.LibvirtVPMEMDevice(
                label='4GB', name='ns_0', devpath='/dev/dax1.0',
                size=4292870144, align=2097152))
        vpmem_resource_1 = objects.Resource(
            provider_uuid=uuids.rp_uuid,
            resource_class="CUSTOM_PMEM_NAMESPACE_4GB",
            identifier='ns_0',
            metadata= objects.LibvirtVPMEMDevice(
                label='4GB', name='ns_1', devpath='/dev/dax2.0',
                size=4292870144, align=2097152))
        # new_resources contains vpmems claimed on destination,
        # /dev/dax1.0 and /dev/dax2.0 are where vpmem data is migrated to
        new_resources = objects.ResourceList(
                objects=[vpmem_resource_0, vpmem_resource_1])
        res = etree.tostring(migration._update_device_resources_xml(
                                copy.deepcopy(doc), new_resources),
                             encoding='unicode')
        # we expect vpmem info will be updated in xml after invoking
        # _update_device_resources_xml
        new_xml = old_xml.replace("/dev/dax0.1", "/dev/dax1.0")
        new_xml = new_xml.replace("/dev/dax0.2", "/dev/dax2.0")
        self.assertXmlEqual(res, new_xml)

    def test_update_numa_xml(self):
        doc = etree.fromstring("""
            <domain>
              <cputune>
                <vcpupin vcpu="0" cpuset="0,1,2,^2"/>
                <vcpupin vcpu="1" cpuset="2-4,^4"/>
                <emulatorpin cpuset="8-10,^8"/>
                <vcpusched vcpus="10" priority="13" scheduler="fifo"/>
                <vcpusched vcpus="11" priority="13" scheduler="fifo"/>
              </cputune>
              <numatune>
                <memory nodeset="4,5,6,7"/>
                <memnode cellid="2" nodeset="4-6,^6"/>
                <memnode cellid="3" nodeset="6-8,^8"/>
              </numatune>
            </domain>""")
        data = objects.LibvirtLiveMigrateData(
            dst_numa_info=objects.LibvirtLiveMigrateNUMAInfo(
                cpu_pins={'0': set([10, 11]), '1': set([12, 13])},
                cell_pins={'2': set([14, 15]), '3': set([16, 17])},
                emulator_pins=set([18, 19]),
                sched_vcpus=set([20, 21]),
                sched_priority=22))

        result = etree.tostring(
            migration._update_numa_xml(copy.deepcopy(doc), data),
            encoding='unicode')

        expected = textwrap.dedent("""
            <domain>
              <cputune>
                <vcpupin vcpu="0" cpuset="10-11"/>
                <vcpupin vcpu="1" cpuset="12-13"/>
                <emulatorpin cpuset="18-19"/>
                <vcpusched vcpus="20-21" priority="22" scheduler="fifo"/>
              </cputune>
              <numatune>
                <memory nodeset="14-17"/>
                <memnode cellid="2" nodeset="14-15"/>
                <memnode cellid="3" nodeset="16-17"/>
              </numatune>
            </domain>""")

        self.assertXmlEqual(expected, result)

    def test_update_numa_xml_no_updates(self):
        xml = textwrap.dedent("""
            <domain>
                <cputune>
                    <vcpupin vcpu="0" cpuset="0,1,2,^2"/>
                    <vcpupin vcpu="1" cpuset="2-4,^4"/>
                    <emulatorpin cpuset="8-10,^8"/>
                </cputune>
                <numatune>
                    <memnode cellid="2" nodeset="4-6,^6"/>
                    <memnode cellid="3" nodeset="6-8,^8"/>
                </numatune>
                <memoryBacking>
                    <hugepages>
                        <page nodeset="8,9,10,^10"/>
                    </hugepages>
                </memoryBacking>
            </domain>""")
        doc = etree.fromstring(xml)
        data = objects.LibvirtLiveMigrateData(
            dst_numa_info=objects.LibvirtLiveMigrateNUMAInfo())
        res = etree.tostring(migration._update_numa_xml(copy.deepcopy(doc),
                                                        data),
                             encoding='unicode')
        self.assertXmlEqual(res, etree.tostring(doc, encoding='unicode'))

    def test_update_serial_xml_serial(self):
        data = objects.LibvirtLiveMigrateData(
            serial_listen_addr='127.0.0.100',
            serial_listen_ports=[2001])
        xml = """<domain>
  <devices>
    <serial type="tcp">
      <source host="127.0.0.1" service="2000"/>
      <target type="serial" port="0"/>
    </serial>
  </devices>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_serial_xml(doc, data),
                             encoding='unicode')
        new_xml = xml.replace("127.0.0.1", "127.0.0.100").replace(
            "2000", "2001")
        self.assertXmlEqual(res, new_xml)

    def test_update_serial_xml_console(self):
        data = objects.LibvirtLiveMigrateData(
            serial_listen_addr='127.0.0.100',
            serial_listen_ports=[299, 300])
        xml = """<domain>
  <devices>
    <console type="tcp">
      <source host="127.0.0.1" service="2001"/>
      <target type="serial" port="0"/>
    </console>
    <console type="tcp">
      <source host="127.0.0.1" service="2002"/>
      <target type="serial" port="1"/>
    </console>
  </devices>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_serial_xml(doc, data),
                             encoding='unicode')
        new_xml = xml.replace("127.0.0.1", "127.0.0.100").replace(
            "2001", "299").replace("2002", "300")

        self.assertXmlEqual(res, new_xml)

    def test_update_serial_xml_without_ports(self):
        # This test is for backwards compatibility when we don't
        # get the serial ports from the target node.
        data = objects.LibvirtLiveMigrateData(
            serial_listen_addr='127.0.0.100',
            serial_listen_ports=[])
        xml = """<domain>
  <devices>
    <console type="tcp">
      <source host="127.0.0.1" service="2001"/>
      <target type="serial" port="0"/>
    </console>
    <console type="tcp">
      <source host="127.0.0.1" service="2002"/>
      <target type="serial" port="1"/>
    </console>
  </devices>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_serial_xml(doc, data),
                             encoding='unicode')
        new_xml = xml.replace("127.0.0.1", "127.0.0.100")
        self.assertXmlEqual(res, new_xml)

    def test_update_graphics(self):
        data = objects.LibvirtLiveMigrateData(
            graphics_listen_addr_vnc='127.0.0.100',
            graphics_listen_addr_spice='127.0.0.200')
        xml = """<domain>
  <devices>
    <graphics type="vnc">
      <listen type="address" address="127.0.0.1"/>
    </graphics>
    <graphics type="spice">
      <listen type="address" address="127.0.0.2"/>
    </graphics>
  </devices>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_graphics_xml(doc, data),
                             encoding='unicode')
        new_xml = xml.replace("127.0.0.1", "127.0.0.100")
        new_xml = new_xml.replace("127.0.0.2", "127.0.0.200")
        self.assertXmlEqual(res, new_xml)

    def test_update_volume_xml(self):
        connection_info = {
            'driver_volume_type': 'iscsi',
            'serial': '58a84f6d-3f0c-4e19-a0af-eb657b790657',
            'data': {
                'access_mode': 'rw',
                'target_discovered': False,
                'target_iqn': 'ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z',
                'volume_id': '58a84f6d-3f0c-4e19-a0af-eb657b790657',
                'device_path':
                '/dev/disk/by-path/ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z'}}
        bdm = objects.LibvirtLiveMigrateBDMInfo(
            serial='58a84f6d-3f0c-4e19-a0af-eb657b790657',
            bus='virtio', type='disk', dev='vdb',
            connection_info=connection_info)
        data = objects.LibvirtLiveMigrateData(
            target_connect_addr='127.0.0.1',
            bdms=[bdm],
            block_migration=False)
        xml = """<domain>
 <devices>
   <disk type='block' device='disk'>
     <driver name='qemu' type='raw' cache='none'/>
     <source dev='/dev/disk/by-path/ip-1.2.3.4:3260-iqn.abc.12345.opst-lun-X'/>
     <target bus='virtio' dev='vdb'/>
     <serial>58a84f6d-3f0c-4e19-a0af-eb657b790657</serial>
     <address type='pci' domain='0x0' bus='0x0' slot='0x04' function='0x0'/>
   </disk>
 </devices>
</domain>"""
        conf = vconfig.LibvirtConfigGuestDisk()
        conf.source_device = bdm.type
        conf.driver_name = "qemu"
        conf.driver_format = "raw"
        conf.driver_cache = "none"
        conf.target_dev = bdm.dev
        conf.target_bus = bdm.bus
        conf.serial = bdm.connection_info.get('serial')
        conf.source_type = "block"
        conf.source_path = bdm.connection_info['data'].get('device_path')

        get_volume_config = mock.MagicMock(return_value=conf)
        doc = etree.fromstring(xml)
        res = etree.tostring(
            migration._update_volume_xml(
                doc,
                data,
                mock.sentinel.instance,
                get_volume_config
            ),
            encoding='unicode'
        )
        new_xml = xml.replace('ip-1.2.3.4:3260-iqn.abc.12345.opst-lun-X',
                              'ip-1.2.3.4:3260-iqn.cde.67890.opst-lun-Z')
        self.assertXmlEqual(res, new_xml)

    def test_update_volume_xml_keeps_address(self):
        # Now test to make sure address isn't altered for virtio-scsi and rbd
        connection_info = {
            'driver_volume_type': 'rbd',
            'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192',
            'data': {
                'access_mode': 'rw',
                'secret_type': 'ceph',
                'name': 'cinder-volumes/volume-d299a078',
                'encrypted': False,
                'discard': True,
                'cluster_name': 'ceph',
                'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9',
                'qos_specs': None,
                'auth_enabled': True,
                'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192',
                'hosts': ['172.16.128.101', '172.16.128.121'],
                'auth_username': 'cinder',
                'ports': ['6789', '6789', '6789']}}
        bdm = objects.LibvirtLiveMigrateBDMInfo(
            serial='d299a078-f0db-4993-bf03-f10fe44fd192',
            bus='scsi', type='disk', dev='sdc',
            connection_info=connection_info)
        data = objects.LibvirtLiveMigrateData(
            target_connect_addr=None,
            bdms=[bdm],
            block_migration=False)
        xml = """<domain>
 <devices>
    <disk type='network' device='disk'>
      <driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
      <auth username='cinder'>
        <secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
      </auth>
      <source protocol='rbd' name='cinder-volumes/volume-d299a078'>
        <host name='172.16.128.101' port='6789'/>
        <host name='172.16.128.121' port='6789'/>
      </source>
      <backingStore/>
      <target dev='sdb' bus='scsi'/>
      <serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
      <alias name='scsi0-0-0-1'/>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
 </devices>
</domain>"""
        conf = vconfig.LibvirtConfigGuestDisk()
        conf.source_device = bdm.type
        conf.driver_name = "qemu"
        conf.driver_format = "raw"
        conf.driver_cache = "writeback"
        conf.target_dev = bdm.dev
        conf.target_bus = bdm.bus
        conf.serial = bdm.connection_info.get('serial')
        conf.source_type = "network"
        conf.driver_discard = 'unmap'
        conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive()
        conf.device_addr.controller = 0

        get_volume_config = mock.MagicMock(return_value=conf)
        doc = etree.fromstring(xml)
        res = etree.tostring(
            migration._update_volume_xml(
                doc,
                data,
                mock.sentinel.instance,
                get_volume_config
            ),
            encoding='unicode'
        )
        new_xml = xml.replace('sdb', 'sdc')
        self.assertXmlEqual(res, new_xml)

    def test_update_volume_xml_add_encryption(self):
        connection_info = {
            'driver_volume_type': 'rbd',
            'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192',
            'data': {
                'access_mode': 'rw',
                'secret_type': 'ceph',
                'name': 'cinder-volumes/volume-d299a078',
                'encrypted': False,
                'discard': True,
                'cluster_name': 'ceph',
                'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9',
                'qos_specs': None,
                'auth_enabled': True,
                'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192',
                'hosts': ['172.16.128.101', '172.16.128.121'],
                'auth_username': 'cinder',
                'ports': ['6789', '6789', '6789']}}
        bdm = objects.LibvirtLiveMigrateBDMInfo(
            serial='d299a078-f0db-4993-bf03-f10fe44fd192',
            bus='scsi', type='disk', dev='sdb',
            connection_info=connection_info,
            encryption_secret_uuid=uuids.encryption_secret_uuid)
        data = objects.LibvirtLiveMigrateData(
            target_connect_addr=None,
            bdms=[bdm],
            block_migration=False)
        xml = """<domain>
 <devices>
    <disk type='network' device='disk'>
      <driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
      <auth username='cinder'>
        <secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
      </auth>
      <source protocol='rbd' name='cinder-volumes/volume-d299a078'>
        <host name='172.16.128.101' port='6789'/>
        <host name='172.16.128.121' port='6789'/>
      </source>
      <backingStore/>
      <target dev='sdb' bus='scsi'/>
      <serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
      <alias name='scsi0-0-0-1'/>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
 </devices>
</domain>"""
        new_xml = """<domain>
 <devices>
    <disk type='network' device='disk'>
      <driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
      <auth username='cinder'>
        <secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
      </auth>
      <source protocol='rbd' name='cinder-volumes/volume-d299a078'>
        <host name='172.16.128.101' port='6789'/>
        <host name='172.16.128.121' port='6789'/>
      </source>
      <backingStore/>
      <target dev='sdb' bus='scsi'/>
      <serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
      <alias name='scsi0-0-0-1'/>
      <encryption format='luks'>
        <secret type='passphrase' uuid='%(encryption_secret_uuid)s'/>
      </encryption>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
 </devices>
</domain>""" % {'encryption_secret_uuid': uuids.encryption_secret_uuid}
        conf = vconfig.LibvirtConfigGuestDisk()
        conf.source_device = bdm.type
        conf.driver_name = "qemu"
        conf.driver_format = "raw"
        conf.driver_cache = "writeback"
        conf.target_dev = bdm.dev
        conf.target_bus = bdm.bus
        conf.serial = bdm.connection_info.get('serial')
        conf.source_type = "network"
        conf.driver_discard = 'unmap'
        conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive()
        conf.device_addr.controller = 0

        get_volume_config = mock.MagicMock(return_value=conf)
        doc = etree.fromstring(xml)
        res = etree.tostring(
            migration._update_volume_xml(
                doc,
                data,
                mock.sentinel.instance,
                get_volume_config
            ),
            encoding='unicode'
        )
        self.assertXmlEqual(res, new_xml)

    def test_update_volume_xml_update_encryption(self):
        connection_info = {
            'driver_volume_type': 'rbd',
            'serial': 'd299a078-f0db-4993-bf03-f10fe44fd192',
            'data': {
                'access_mode': 'rw',
                'secret_type': 'ceph',
                'name': 'cinder-volumes/volume-d299a078',
                'encrypted': False,
                'discard': True,
                'cluster_name': 'ceph',
                'secret_uuid': '1a790a26-dd49-4825-8d16-3dd627cf05a9',
                'qos_specs': None,
                'auth_enabled': True,
                'volume_id': 'd299a078-f0db-4993-bf03-f10fe44fd192',
                'hosts': ['172.16.128.101', '172.16.128.121'],
                'auth_username': 'cinder',
                'ports': ['6789', '6789', '6789']}}
        bdm = objects.LibvirtLiveMigrateBDMInfo(
            serial='d299a078-f0db-4993-bf03-f10fe44fd192',
            bus='scsi', type='disk', dev='sdb',
            connection_info=connection_info,
            encryption_secret_uuid=uuids.encryption_secret_uuid_new)
        data = objects.LibvirtLiveMigrateData(
            target_connect_addr=None,
            bdms=[bdm],
            block_migration=False)
        xml = """<domain>
 <devices>
    <disk type='network' device='disk'>
      <driver name='qemu' type='raw' cache='writeback' discard='unmap'/>
      <auth username='cinder'>
        <secret type='ceph' uuid='1a790a26-dd49-4825-8d16-3dd627cf05a9'/>
      </auth>
      <source protocol='rbd' name='cinder-volumes/volume-d299a078'>
        <host name='172.16.128.101' port='6789'/>
        <host name='172.16.128.121' port='6789'/>
      </source>
      <backingStore/>
      <target dev='sdb' bus='scsi'/>
      <serial>d299a078-f0db-4993-bf03-f10fe44fd192</serial>
      <alias name='scsi0-0-0-1'/>
      <encryption format='luks'>
        <secret type='passphrase' uuid='%(encryption_secret_uuid)s'/>
      </encryption>
      <address type='drive' controller='0' bus='0' target='0' unit='1'/>
    </disk>
 </devices>
</domain>""" % {'encryption_secret_uuid': uuids.encryption_secret_uuid_old}
        conf = vconfig.LibvirtConfigGuestDisk()
        conf.source_device = bdm.type
        conf.driver_name = "qemu"
        conf.driver_format = "raw"
        conf.driver_cache = "writeback"
        conf.target_dev = bdm.dev
        conf.target_bus = bdm.bus
        conf.serial = bdm.connection_info.get('serial')
        conf.source_type = "network"
        conf.driver_discard = 'unmap'
        conf.device_addr = vconfig.LibvirtConfigGuestDeviceAddressDrive()
        conf.device_addr.controller = 0

        get_volume_config = mock.MagicMock(return_value=conf)
        doc = etree.fromstring(xml)
        res = etree.tostring(
            migration._update_volume_xml(
                doc,
                data,
                mock.sentinel.instance,
                get_volume_config
            ),
            encoding='unicode'
        )
        new_xml = xml.replace(uuids.encryption_secret_uuid_old,
                              uuids.encryption_secret_uuid_new)
        self.assertXmlEqual(res, new_xml)

    def test_update_perf_events_xml(self):
        data = objects.LibvirtLiveMigrateData(
            supported_perf_events=['cmt'])
        xml = """<domain>
  <perf>
    <event enabled="yes" name="cmt"/>
    <event enabled="yes" name="mbml"/>
  </perf>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_perf_events_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
  <perf>
    <event enabled="yes" name="cmt"/></perf>
</domain>""")

    def test_update_perf_events_xml_add_new_events(self):
        data = objects.LibvirtLiveMigrateData(
            supported_perf_events=['cmt'])
        xml = """<domain>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_perf_events_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
<perf><event enabled="yes" name="cmt"/></perf></domain>""")

    def test_update_perf_events_xml_add_new_events1(self):
        data = objects.LibvirtLiveMigrateData(
            supported_perf_events=['cmt', 'mbml'])
        xml = """<domain>
  <perf>
    <event enabled="yes" name="cmt"/>
  </perf>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_perf_events_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
  <perf>
    <event enabled="yes" name="cmt"/><event enabled="yes" name="mbml"/></perf>
</domain>""")

    def test_update_perf_events_xml_remove_all_events(self):
        data = objects.LibvirtLiveMigrateData(
            supported_perf_events=[])
        xml = """<domain>
  <perf>
    <event enabled="yes" name="cmt"/>
  </perf>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_perf_events_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
  <perf>
    </perf>
</domain>""")

    def test_update_memory_backing_xml_remove(self):
        data = objects.LibvirtLiveMigrateData(
            dst_wants_file_backed_memory=False)
        xml = """<domain>
  <memoryBacking>
    <source type="file"/>
    <access mode="shared"/>
    <allocation mode="immediate"/>
    <discard />
  </memoryBacking>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_memory_backing_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
  <memoryBacking/>
</domain>""")

    def test_update_memory_backing_xml_add(self):
        data = objects.LibvirtLiveMigrateData(
            dst_wants_file_backed_memory=True)
        xml = """<domain/>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_memory_backing_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
  <memoryBacking>
    <source type="file"/>
    <access mode="shared"/>
    <allocation mode="immediate"/>
    <discard />
  </memoryBacking>
</domain>""")

    def test_update_memory_backing_xml_keep(self):
        data = objects.LibvirtLiveMigrateData(
            dst_wants_file_backed_memory=True)

        xml = """<domain>
  <memoryBacking>
    <source type="file"/>
    <access mode="shared"/>
    <allocation mode="immediate"/>
    <discard />
  </memoryBacking>
</domain>"""
        doc = etree.fromstring(xml)
        res = etree.tostring(migration._update_memory_backing_xml(doc, data),
                             encoding='unicode')

        self.assertXmlEqual(res, """<domain>
  <memoryBacking>
    <source type="file"/>
    <access mode="shared"/>
    <allocation mode="immediate"/>
    <discard />
  </memoryBacking>
</domain>""")

    def _test_update_vif_xml(self, conf, original_xml, expected_xml):
        """Simulates updating the guest xml for live migrating from a host
        using OVS to a host using vhostuser as the networking backend.
        """
        vif_ovs = network_model.VIF(id=uuids.vif,
                                    address='DE:AD:BE:EF:CA:FE',
                                    details={'port_filter': False},
                                    devname='tap-xxx-yyy-zzz',
                                    ovs_interfaceid=uuids.ovs)
        source_vif = vif_ovs
        migrate_vifs = [
            objects.VIFMigrateData(
                port_id=uuids.port_id,
                vnic_type=network_model.VNIC_TYPE_NORMAL,
                vif_type=network_model.VIF_TYPE_VHOSTUSER,
                vif_details={
                    'vhostuser_socket': '/vhost-user/test.sock'
                },
                profile={},
                host='dest.host',
                source_vif=source_vif)
        ]
        data = objects.LibvirtLiveMigrateData(vifs=migrate_vifs)

        get_vif_config = mock.MagicMock(return_value=conf)
        doc = etree.fromstring(original_xml)
        updated_xml = etree.tostring(
            migration._update_vif_xml(doc, data, get_vif_config),
            encoding='unicode')
        self.assertXmlEqual(updated_xml, expected_xml)

    def test_update_vif_xml_to_vhostuser(self):
        conf = vconfig.LibvirtConfigGuestInterface()
        conf.net_type = "vhostuser"
        conf.vhostuser_type = "unix"
        conf.vhostuser_mode = "server"
        conf.mac_addr = "DE:AD:BE:EF:CA:FE"
        conf.vhostuser_path = "/vhost-user/test.sock"
        conf.model = "virtio"

        original_xml = """<domain>
 <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
 <devices>
    <interface type="bridge">
        <mac address="DE:AD:BE:EF:CA:FE"/>
        <model type="virtio"/>
        <source bridge="qbra188171c-ea"/>
        <target dev="tapa188171c-ea"/>
        <virtualport type="openvswitch">
            <parameters interfaceid="%s"/>
        </virtualport>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
    </interface>
 </devices>
</domain>""" % uuids.ovs
        # Note that <target> and <virtualport> are dropped from the ovs source
        # interface xml since they aren't applicable to the vhostuser
        # destination interface xml. The type attribute value changes and the
        # hardware address element is retained.
        expected_xml = """<domain>
 <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
 <devices>
    <interface type="vhostuser">
        <mac address="DE:AD:BE:EF:CA:FE"/>
        <model type="virtio"/>
        <source mode="server" path="/vhost-user/test.sock" type="unix"/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
    </interface>
 </devices>
</domain>"""
        self._test_update_vif_xml(conf, original_xml, expected_xml)

    def test_update_vif_xml_to_bridge_without_mtu(self):
        """This test validates _update_vif_xml to make sure it does not add
        MTU to the interface if it does not exist in the source XML.
        """
        conf = vconfig.LibvirtConfigGuestInterface()
        conf.net_type = "bridge"
        conf.source_dev = "qbra188171c-ea"
        conf.target_dev = "tapa188171c-ea"
        conf.mac_addr = "DE:AD:BE:EF:CA:FE"
        conf.vhostuser_path = "/vhost-user/test.sock"
        conf.model = "virtio"
        conf.mtu = 9000

        original_xml = """<domain>
 <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
 <devices>
    <interface type="bridge">
        <mac address="DE:AD:BE:EF:CA:FE"/>
        <model type="virtio"/>
        <source bridge="qbra188171c-ea"/>
        <target dev="tapa188171c-ea"/>
        <virtualport type="openvswitch">
            <parameters interfaceid="%s"/>
        </virtualport>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
    </interface>
 </devices>
</domain>""" % uuids.ovs
        expected_xml = """<domain>
 <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
 <devices>
    <interface type="bridge">
        <mac address="DE:AD:BE:EF:CA:FE"/>
        <model type="virtio"/>
        <source bridge="qbra188171c-ea"/>
        <target dev="tapa188171c-ea"/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
    </interface>
 </devices>
</domain>"""
        self._test_update_vif_xml(conf, original_xml, expected_xml)

    def test_update_vif_xml_to_bridge_with_mtu(self):
        """This test validates _update_vif_xml to make sure it maintains the
        interface MTU if it exists in the source XML
        """
        conf = vconfig.LibvirtConfigGuestInterface()
        conf.net_type = "bridge"
        conf.source_dev = "qbra188171c-ea"
        conf.target_dev = "tapa188171c-ea"
        conf.mac_addr = "DE:AD:BE:EF:CA:FE"
        conf.vhostuser_path = "/vhost-user/test.sock"
        conf.model = "virtio"
        conf.mtu = 9000

        original_xml = """<domain>
 <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
 <devices>
    <interface type="bridge">
        <mac address="DE:AD:BE:EF:CA:FE"/>
        <model type="virtio"/>
        <source bridge="qbra188171c-ea"/>
        <target dev="tapa188171c-ea"/>
        <mtu size="9000"/>
        <virtualport type="openvswitch">
            <parameters interfaceid="%s"/>
        </virtualport>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
    </interface>
 </devices>
</domain>""" % uuids.ovs
        expected_xml = """<domain>
 <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
 <devices>
    <interface type="bridge">
        <mac address="DE:AD:BE:EF:CA:FE"/>
        <model type="virtio"/>
        <source bridge="qbra188171c-ea"/>
        <mtu size="9000"/>
        <target dev="tapa188171c-ea"/>
        <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
    </interface>
 </devices>
</domain>"""
        self._test_update_vif_xml(conf, original_xml, expected_xml)

    def test_update_vif_xml_no_mac_address_in_xml(self):
        """Tests that the <mac address> is not found in the <interface> XML
        which results in an error.
        """
        data = objects.LibvirtLiveMigrateData(vifs=[
            objects.VIFMigrateData(source_vif=network_model.VIF(
                address="DE:AD:BE:EF:CA:FE"))])
        original_xml = """<domain>
         <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
         <devices>
            <interface type='direct'>
                <source dev='eth0' mode='private'/>
                <virtualport type='802.1Qbh'>
                  <parameters profileid='finance'/>
                </virtualport>
            </interface>
         </devices>
        </domain>"""
        get_vif_config = mock.MagicMock(new_callable=mock.NonCallableMock)
        doc = etree.fromstring(original_xml)
        ex = self.assertRaises(exception.NovaException,
                               migration._update_vif_xml,
                               doc, data, get_vif_config)
        self.assertIn('Unable to find MAC address in interface XML', str(ex))

    def test_update_vif_xml_no_matching_vif(self):
        """Tests that the vif in the migrate data is not found in the existing
        guest interfaces.
        """
        data = objects.LibvirtLiveMigrateData(vifs=[
            objects.VIFMigrateData(source_vif=network_model.VIF(
                address="DE:AD:BE:EF:CA:FE"))])
        original_xml = """<domain>
         <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
         <devices>
            <interface type="bridge">
                <mac address="CA:FE:DE:AD:BE:EF"/>
                <model type="virtio"/>
                <source bridge="qbra188171c-ea"/>
                <target dev="tapa188171c-ea"/>
            </interface>
         </devices>
        </domain>"""
        get_vif_config = mock.MagicMock(new_callable=mock.NonCallableMock)
        doc = etree.fromstring(original_xml)
        ex = self.assertRaises(KeyError, migration._update_vif_xml,
                               doc, data, get_vif_config)
        self.assertIn("ca:fe:de:ad:be:ef", str(ex))

    def test_update_vif_xml_lower_case_mac(self):
        """Tests that the vif in the migrate data is not found in the existing
        guest interfaces.
        """
        conf = vconfig.LibvirtConfigGuestInterface()
        conf.net_type = "bridge"
        conf.source_dev = "qbra188171c-ea"
        conf.target_dev = "tapa188171c-ea"
        conf.mac_addr = "DE:AD:BE:EF:CA:FE"
        conf.model = "virtio"
        original_xml = """<domain>
        <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
        <devices>
            <interface type="bridge">
                <mac address="de:ad:be:ef:ca:fe"/>
                <model type="virtio"/>
                <source bridge="qbra188171c-ea"/>
                <target dev="tapa188171c-ea"/>
                <virtualport type="openvswitch">
                    <parameters interfaceid="%s"/>
                </virtualport>
                <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                        function='0x0'/>
            </interface>
        </devices>
        </domain>""" % uuids.ovs
        expected_xml = """<domain>
         <uuid>3de6550a-8596-4937-8046-9d862036bca5</uuid>
         <devices>
            <interface type="bridge">
                <mac address="DE:AD:BE:EF:CA:FE"/>
                <model type="virtio"/>
                <source bridge="qbra188171c-ea"/>
                <target dev="tapa188171c-ea"/>
                <address type='pci' domain='0x0000' bus='0x00' slot='0x04'
                 function='0x0'/>
            </interface>
         </devices>
        </domain>"""
        self._test_update_vif_xml(conf, original_xml, expected_xml)


class MigrationMonitorTestCase(test.NoDBTestCase):
    def setUp(self):
        super(MigrationMonitorTestCase, self).setUp()

        self.useFixture(nova_fixtures.LibvirtFixture())

        flavor = objects.Flavor(memory_mb=2048,
                                swap=0,
                                vcpu_weight=None,
                                root_gb=1,
                                id=2,
                                name=u'm1.small',
                                ephemeral_gb=0,
                                rxtx_factor=1.0,
                                flavorid=u'1',
                                vcpus=1,
                                extra_specs={})

        instance = {
            'id': 1,
            'uuid': '32dfcb37-5af1-552b-357c-be8c3aa38310',
            'memory_kb': '1024000',
            'basepath': '/some/path',
            'bridge_name': 'br100',
            'display_name': "Acme webserver",
            'vcpus': 2,
            'project_id': 'fake',
            'bridge': 'br101',
            'image_ref': '155d900f-4e14-4e4c-a73d-069cbf4541e6',
            'root_gb': 10,
            'ephemeral_gb': 20,
            'instance_type_id': flavor.id,
            'flavor': flavor,
            'extra_specs': {},
            'system_metadata': {
                'image_disk_format': 'raw',
            },
            'new_flavor': None,
            'old_flavor': None,
            'pci_devices': objects.PciDeviceList(),
            'numa_topology': None,
            'config_drive': None,
            'vm_mode': None,
            'kernel_id': None,
            'ramdisk_id': None,
            'os_type': 'linux',
            'user_id': '838a72b0-0d54-4827-8fd6-fb1227633ceb',
            'ephemeral_key_uuid': None,
            'vcpu_model': None,
            'host': 'fake-host',
            'task_state': None,
        }
        self.instance = objects.Instance(**instance)
        self.conn = fakelibvirt.Connection("qemu:///system")
        self.dom = fakelibvirt.Domain(self.conn, "<domain/>", True)
        self.host = host.Host("qemu:///system")
        self.guest = libvirt_guest.Guest(self.dom)

    @mock.patch.object(libvirt_guest.Guest, "is_active", return_value=True)
    def test_live_migration_find_type_active(self, mock_active):
        self.assertEqual(migration.find_job_type(self.guest, self.instance),
                         fakelibvirt.VIR_DOMAIN_JOB_FAILED)

    @mock.patch.object(libvirt_guest.Guest, "is_active", return_value=False)
    def test_live_migration_find_type_inactive(self, mock_active):
        self.assertEqual(migration.find_job_type(self.guest, self.instance),
                         fakelibvirt.VIR_DOMAIN_JOB_COMPLETED)

    @mock.patch.object(libvirt_guest.Guest, "is_active")
    def test_live_migration_find_type_no_domain(self, mock_active):
        mock_active.side_effect = fakelibvirt.make_libvirtError(
            fakelibvirt.libvirtError,
            "No domain with ID",
            error_code=fakelibvirt.VIR_ERR_NO_DOMAIN)

        self.assertEqual(migration.find_job_type(self.guest, self.instance),
                         fakelibvirt.VIR_DOMAIN_JOB_COMPLETED)

    @mock.patch.object(libvirt_guest.Guest, "is_active")
    def test_live_migration_find_type_bad_err(self, mock_active):
        mock_active.side_effect = fakelibvirt.make_libvirtError(
            fakelibvirt.libvirtError,
            "Something weird happened",
            error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR)

        self.assertEqual(migration.find_job_type(self.guest, self.instance),
                         fakelibvirt.VIR_DOMAIN_JOB_FAILED)

    @mock.patch('nova.virt.libvirt.migration.LOG',
                new_callable=mock.NonCallableMock)  # asserts not called
    @mock.patch('nova.virt.libvirt.guest.Guest.is_active', return_value=True)
    def test_live_migration_find_type_no_logging(self, mock_active, _mock_log):
        self.assertEqual(fakelibvirt.VIR_DOMAIN_JOB_FAILED,
                         migration.find_job_type(self.guest, self.instance,
                                                 logging_ok=False))

    def test_live_migration_abort_too_long(self):
        # Elapsed time is over completion timeout
        self.assertTrue(migration.should_trigger_timeout_action(
            self.instance, 4500, 2000, "running"))

    def test_live_migration_abort_no_comp_timeout(self):
        # Completion timeout is disabled
        self.assertFalse(migration.should_trigger_timeout_action(
            self.instance, 4500, 0, "running"))

    def test_live_migration_abort_still_working(self):
        # Elapsed time is less than completion timeout
        self.assertFalse(migration.should_trigger_timeout_action(
            self.instance, 4500, 9000, "running"))

    @mock.patch.object(libvirt_guest.Guest,
                       "migrate_configure_max_downtime")
    def test_live_migration_update_downtime_no_steps(self, mock_dt):
        steps = []
        newdt = migration.update_downtime(self.guest, self.instance,
                                          None, steps, 5000)

        self.assertIsNone(newdt)
        self.assertFalse(mock_dt.called)

    @mock.patch.object(libvirt_guest.Guest,
                       "migrate_configure_max_downtime")
    def test_live_migration_update_downtime_too_early(self, mock_dt):
        steps = [
            (9000, 50),
            (18000, 200),
        ]
        # We shouldn't change downtime since haven't hit first time
        newdt = migration.update_downtime(self.guest, self.instance,
                                          None, steps, 5000)

        self.assertIsNone(newdt)
        self.assertFalse(mock_dt.called)

    @mock.patch.object(libvirt_guest.Guest,
                       "migrate_configure_max_downtime")
    def test_live_migration_update_downtime_step1(self, mock_dt):
        steps = [
            (9000, 50),
            (18000, 200),
        ]
        # We should pick the first downtime entry
        newdt = migration.update_downtime(self.guest, self.instance,
                                          None, steps, 11000)

        self.assertEqual(newdt, 50)
        mock_dt.assert_called_once_with(50)

    @mock.patch.object(libvirt_guest.Guest,
                       "migrate_configure_max_downtime")
    def test_live_migration_update_downtime_nostep1(self, mock_dt):
        steps = [
            (9000, 50),
            (18000, 200),
        ]
        # We shouldn't change downtime, since its already set
        newdt = migration.update_downtime(self.guest, self.instance,
                                          50, steps, 11000)

        self.assertEqual(newdt, 50)
        self.assertFalse(mock_dt.called)

    @mock.patch.object(libvirt_guest.Guest,
                       "migrate_configure_max_downtime")
    def test_live_migration_update_downtime_step2(self, mock_dt):
        steps = [
            (9000, 50),
            (18000, 200),
        ]
        newdt = migration.update_downtime(self.guest, self.instance,
                                          50, steps, 22000)

        self.assertEqual(newdt, 200)
        mock_dt.assert_called_once_with(200)

    @mock.patch.object(libvirt_guest.Guest,
                       "migrate_configure_max_downtime")
    def test_live_migration_update_downtime_err(self, mock_dt):
        steps = [
            (9000, 50),
            (18000, 200),
        ]
        mock_dt.side_effect = fakelibvirt.make_libvirtError(
            fakelibvirt.libvirtError,
            "Failed to set downtime",
            error_code=fakelibvirt.VIR_ERR_INTERNAL_ERROR)

        newdt = migration.update_downtime(self.guest, self.instance,
                                          50, steps, 22000)

        self.assertEqual(newdt, 200)
        mock_dt.assert_called_once_with(200)

    @mock.patch.object(objects.Instance, "save")
    @mock.patch.object(objects.Migration, "save")
    def test_live_migration_save_stats(self, mock_isave, mock_msave):
        mig = objects.Migration()

        info = libvirt_guest.JobInfo(
            memory_total=1 * units.Gi,
            memory_processed=5 * units.Gi,
            memory_remaining=500 * units.Mi,
            disk_total=15 * units.Gi,
            disk_processed=10 * units.Gi,
            disk_remaining=14 * units.Gi)

        migration.save_stats(self.instance, mig, info, 75)

        self.assertEqual(mig.memory_total, 1 * units.Gi)
        self.assertEqual(mig.memory_processed, 5 * units.Gi)
        self.assertEqual(mig.memory_remaining, 500 * units.Mi)
        self.assertEqual(mig.disk_total, 15 * units.Gi)
        self.assertEqual(mig.disk_processed, 10 * units.Gi)
        self.assertEqual(mig.disk_remaining, 14 * units.Gi)

        self.assertEqual(self.instance.progress, 25)

        mock_msave.assert_called_once_with()
        mock_isave.assert_called_once_with()

    @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy")
    @mock.patch.object(libvirt_guest.Guest, "pause")
    def test_live_migration_run_tasks_empty_tasks(self, mock_pause,
                                                  mock_postcopy):
        tasks = deque()
        active_migrations = {self.instance.uuid: tasks}
        on_migration_failure = deque()

        mig = objects.Migration(id=1, status="running")

        migration.run_tasks(self.guest, self.instance,
                            active_migrations, on_migration_failure,
                            mig, False)

        self.assertFalse(mock_pause.called)
        self.assertFalse(mock_postcopy.called)
        self.assertEqual(len(on_migration_failure), 0)

    @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy")
    @mock.patch.object(libvirt_guest.Guest, "pause")
    def test_live_migration_run_tasks_no_tasks(self, mock_pause,
                                               mock_postcopy):
        active_migrations = {}
        on_migration_failure = deque()

        mig = objects.Migration(id=1, status="running")

        migration.run_tasks(self.guest, self.instance,
                            active_migrations, on_migration_failure,
                            mig, False)

        self.assertFalse(mock_pause.called)
        self.assertFalse(mock_postcopy.called)
        self.assertEqual(len(on_migration_failure), 0)

    @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy")
    @mock.patch.object(libvirt_guest.Guest, "pause")
    def test_live_migration_run_tasks_no_force_complete(self, mock_pause,
                                                        mock_postcopy):
        tasks = deque()
        # Test to ensure unknown tasks are ignored
        tasks.append("wibble")
        active_migrations = {self.instance.uuid: tasks}
        on_migration_failure = deque()

        mig = objects.Migration(id=1, status="running")

        migration.run_tasks(self.guest, self.instance,
                            active_migrations, on_migration_failure,
                            mig, False)

        self.assertFalse(mock_pause.called)
        self.assertFalse(mock_postcopy.called)
        self.assertEqual(len(on_migration_failure), 0)

    @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy")
    @mock.patch.object(libvirt_guest.Guest, "pause")
    def test_live_migration_run_tasks_force_complete(self, mock_pause,
                                                     mock_postcopy):
        tasks = deque()
        tasks.append("force-complete")
        active_migrations = {self.instance.uuid: tasks}
        on_migration_failure = deque()

        mig = objects.Migration(id=1, status="running")

        migration.run_tasks(self.guest, self.instance,
                            active_migrations, on_migration_failure,
                            mig, False)

        mock_pause.assert_called_once_with()
        self.assertFalse(mock_postcopy.called)
        self.assertEqual(len(on_migration_failure), 1)
        self.assertEqual(on_migration_failure.pop(), "unpause")

    @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy")
    @mock.patch.object(libvirt_guest.Guest, "pause")
    def test_live_migration_run_tasks_force_complete_postcopy_running(self,
        mock_pause, mock_postcopy):
        tasks = deque()
        tasks.append("force-complete")
        active_migrations = {self.instance.uuid: tasks}
        on_migration_failure = deque()

        mig = objects.Migration(id=1, status="running (post-copy)")

        migration.run_tasks(self.guest, self.instance,
                            active_migrations, on_migration_failure,
                            mig, True)

        self.assertFalse(mock_pause.called)
        self.assertFalse(mock_postcopy.called)
        self.assertEqual(len(on_migration_failure), 0)

    @mock.patch.object(objects.Migration, "save")
    @mock.patch.object(libvirt_guest.Guest, "migrate_start_postcopy")
    @mock.patch.object(libvirt_guest.Guest, "pause")
    def test_live_migration_run_tasks_force_complete_postcopy(self,
        mock_pause, mock_postcopy, mock_msave):
        tasks = deque()
        tasks.append("force-complete")
        active_migrations = {self.instance.uuid: tasks}
        on_migration_failure = deque()

        mig = objects.Migration(id=1, status="running")

        migration.run_tasks(self.guest, self.instance,
                            active_migrations, on_migration_failure,
                            mig, True)

        mock_postcopy.assert_called_once_with()
        self.assertFalse(mock_pause.called)
        self.assertEqual(len(on_migration_failure), 0)

    @mock.patch.object(libvirt_guest.Guest, "resume")
    @mock.patch.object(libvirt_guest.Guest, "get_power_state",
                       return_value=power_state.PAUSED)
    def test_live_migration_recover_tasks_resume(self, mock_ps, mock_resume):
        tasks = deque()
        tasks.append("unpause")

        migration.run_recover_tasks(self.host, self.guest,
                                    self.instance, tasks)

        mock_resume.assert_called_once_with()

    @mock.patch.object(libvirt_guest.Guest, "resume")
    @mock.patch.object(libvirt_guest.Guest, "get_power_state",
                       return_value=power_state.RUNNING)
    def test_live_migration_recover_tasks_no_resume(self, mock_ps,
                                                    mock_resume):
        tasks = deque()
        tasks.append("unpause")

        migration.run_recover_tasks(self.host, self.guest,
                                    self.instance, tasks)

        self.assertFalse(mock_resume.called)

    @mock.patch.object(libvirt_guest.Guest, "resume")
    def test_live_migration_recover_tasks_empty_tasks(self, mock_resume):
        tasks = deque()

        migration.run_recover_tasks(self.host, self.guest,
                                    self.instance, tasks)

        self.assertFalse(mock_resume.called)

    @mock.patch.object(libvirt_guest.Guest, "resume")
    def test_live_migration_recover_tasks_no_pause(self, mock_resume):
        tasks = deque()
        # Test to ensure unknown tasks are ignored
        tasks.append("wibble")

        migration.run_recover_tasks(self.host, self.guest,
                                    self.instance, tasks)

        self.assertFalse(mock_resume.called)

    def test_live_migration_downtime_steps(self):
        self.flags(live_migration_downtime=400, group='libvirt')
        self.flags(live_migration_downtime_steps=10, group='libvirt')
        self.flags(live_migration_downtime_delay=30, group='libvirt')

        steps = migration.downtime_steps(3.0)

        self.assertEqual([
            (0, 40),
            (90, 76),
            (180, 112),
            (270, 148),
            (360, 184),
            (450, 220),
            (540, 256),
            (630, 292),
            (720, 328),
            (810, 364),
            (900, 400),
        ], list(steps))
