#    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 copy
import datetime

import mock
from oslo_config import cfg
from oslo_utils import timeutils
from oslo_utils import units

from nova.compute import claims
from nova.compute.monitors import base as monitor_base
from nova.compute import power_state
from nova.compute import resource_tracker
from nova.compute import task_states
from nova.compute import vm_states
from nova import context
from nova import exception as exc
from nova import objects
from nova.objects import base as obj_base
from nova.objects import fields as obj_fields
from nova.objects import pci_device
from nova.pci import manager as pci_manager
from nova.scheduler import utils as sched_utils
from nova import test
from nova.tests.unit.objects import test_pci_device as fake_pci_device
from nova.tests import uuidsentinel as uuids

_HOSTNAME = 'fake-host'
_NODENAME = 'fake-node'
CONF = cfg.CONF

_VIRT_DRIVER_AVAIL_RESOURCES = {
    'vcpus': 4,
    'memory_mb': 512,
    'local_gb': 6,
    'vcpus_used': 0,
    'memory_mb_used': 0,
    'local_gb_used': 0,
    'hypervisor_type': 'fake',
    'hypervisor_version': 0,
    'hypervisor_hostname': _NODENAME,
    'cpu_info': '',
    'numa_topology': None,
}

_COMPUTE_NODE_FIXTURES = [
    objects.ComputeNode(
        id=1,
        uuid=uuids.cn1,
        host=_HOSTNAME,
        vcpus=_VIRT_DRIVER_AVAIL_RESOURCES['vcpus'],
        memory_mb=_VIRT_DRIVER_AVAIL_RESOURCES['memory_mb'],
        local_gb=_VIRT_DRIVER_AVAIL_RESOURCES['local_gb'],
        vcpus_used=_VIRT_DRIVER_AVAIL_RESOURCES['vcpus_used'],
        memory_mb_used=_VIRT_DRIVER_AVAIL_RESOURCES['memory_mb_used'],
        local_gb_used=_VIRT_DRIVER_AVAIL_RESOURCES['local_gb_used'],
        hypervisor_type='fake',
        hypervisor_version=0,
        hypervisor_hostname=_NODENAME,
        free_ram_mb=(_VIRT_DRIVER_AVAIL_RESOURCES['memory_mb'] -
                     _VIRT_DRIVER_AVAIL_RESOURCES['memory_mb_used']),
        free_disk_gb=(_VIRT_DRIVER_AVAIL_RESOURCES['local_gb'] -
                      _VIRT_DRIVER_AVAIL_RESOURCES['local_gb_used']),
        current_workload=0,
        running_vms=0,
        cpu_info='{}',
        disk_available_least=0,
        host_ip='1.1.1.1',
        supported_hv_specs=[
            objects.HVSpec.from_list([
                obj_fields.Architecture.I686,
                obj_fields.HVType.KVM,
                obj_fields.VMMode.HVM])
        ],
        metrics=None,
        pci_device_pools=None,
        extra_resources=None,
        stats={},
        numa_topology=None,
        cpu_allocation_ratio=16.0,
        ram_allocation_ratio=1.5,
        disk_allocation_ratio=1.0,
        ),
]

_INSTANCE_TYPE_FIXTURES = {
    1: {
        'id': 1,
        'flavorid': 'fakeid-1',
        'name': 'fake1.small',
        'memory_mb': 128,
        'vcpus': 1,
        'root_gb': 1,
        'ephemeral_gb': 0,
        'swap': 0,
        'rxtx_factor': 0,
        'vcpu_weight': 1,
        'extra_specs': {},
    },
    2: {
        'id': 2,
        'flavorid': 'fakeid-2',
        'name': 'fake1.medium',
        'memory_mb': 256,
        'vcpus': 2,
        'root_gb': 5,
        'ephemeral_gb': 0,
        'swap': 0,
        'rxtx_factor': 0,
        'vcpu_weight': 1,
        'extra_specs': {},
    },
}


_INSTANCE_TYPE_OBJ_FIXTURES = {
    1: objects.Flavor(id=1, flavorid='fakeid-1', name='fake1.small',
                      memory_mb=128, vcpus=1, root_gb=1,
                      ephemeral_gb=0, swap=0, rxtx_factor=0,
                      vcpu_weight=1, extra_specs={}),
    2: objects.Flavor(id=2, flavorid='fakeid-2', name='fake1.medium',
                      memory_mb=256, vcpus=2, root_gb=5,
                      ephemeral_gb=0, swap=0, rxtx_factor=0,
                      vcpu_weight=1, extra_specs={}),
}


_2MB = 2 * units.Mi / units.Ki

_INSTANCE_NUMA_TOPOLOGIES = {
    '2mb': objects.InstanceNUMATopology(cells=[
        objects.InstanceNUMACell(
            id=0, cpuset=set([1]), memory=_2MB, pagesize=0),
        objects.InstanceNUMACell(
            id=1, cpuset=set([3]), memory=_2MB, pagesize=0)]),
}

_NUMA_LIMIT_TOPOLOGIES = {
    '2mb': objects.NUMATopologyLimits(id=0,
                                      cpu_allocation_ratio=1.0,
                                      ram_allocation_ratio=1.0),
}

_NUMA_PAGE_TOPOLOGIES = {
    '2kb*8': objects.NUMAPagesTopology(size_kb=2, total=8, used=0)
}

_NUMA_HOST_TOPOLOGIES = {
    '2mb': objects.NUMATopology(cells=[
        objects.NUMACell(id=0, cpuset=set([1, 2]), memory=_2MB,
                         cpu_usage=0, memory_usage=0,
                         mempages=[_NUMA_PAGE_TOPOLOGIES['2kb*8']],
                         siblings=[], pinned_cpus=set([])),
        objects.NUMACell(id=1, cpuset=set([3, 4]), memory=_2MB,
                         cpu_usage=0, memory_usage=0,
                         mempages=[_NUMA_PAGE_TOPOLOGIES['2kb*8']],
                         siblings=[], pinned_cpus=set([]))]),
}


_INSTANCE_FIXTURES = [
    objects.Instance(
        id=1,
        host=_HOSTNAME,
        node=_NODENAME,
        uuid='c17741a5-6f3d-44a8-ade8-773dc8c29124',
        memory_mb=_INSTANCE_TYPE_FIXTURES[1]['memory_mb'],
        vcpus=_INSTANCE_TYPE_FIXTURES[1]['vcpus'],
        root_gb=_INSTANCE_TYPE_FIXTURES[1]['root_gb'],
        ephemeral_gb=_INSTANCE_TYPE_FIXTURES[1]['ephemeral_gb'],
        numa_topology=_INSTANCE_NUMA_TOPOLOGIES['2mb'],
        pci_requests=None,
        pci_devices=None,
        instance_type_id=1,
        vm_state=vm_states.ACTIVE,
        power_state=power_state.RUNNING,
        task_state=None,
        os_type='fake-os',  # Used by the stats collector.
        project_id='fake-project',  # Used by the stats collector.
        user_id=uuids.user_id,
        flavor = _INSTANCE_TYPE_OBJ_FIXTURES[1],
        old_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[1],
        new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[1],
    ),
    objects.Instance(
        id=2,
        host=_HOSTNAME,
        node=_NODENAME,
        uuid='33805b54-dea6-47b8-acb2-22aeb1b57919',
        memory_mb=_INSTANCE_TYPE_FIXTURES[2]['memory_mb'],
        vcpus=_INSTANCE_TYPE_FIXTURES[2]['vcpus'],
        root_gb=_INSTANCE_TYPE_FIXTURES[2]['root_gb'],
        ephemeral_gb=_INSTANCE_TYPE_FIXTURES[2]['ephemeral_gb'],
        numa_topology=None,
        pci_requests=None,
        pci_devices=None,
        instance_type_id=2,
        vm_state=vm_states.DELETED,
        power_state=power_state.SHUTDOWN,
        task_state=None,
        os_type='fake-os',
        project_id='fake-project-2',
        user_id=uuids.user_id,
        flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2],
        old_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2],
        new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2],
    ),
]

_MIGRATION_FIXTURES = {
    # A migration that has only this compute node as the source host
    'source-only': objects.Migration(
        id=1,
        instance_uuid='f15ecfb0-9bf6-42db-9837-706eb2c4bf08',
        source_compute=_HOSTNAME,
        dest_compute='other-host',
        source_node=_NODENAME,
        dest_node='other-node',
        old_instance_type_id=1,
        new_instance_type_id=2,
        migration_type='resize',
        status='migrating'
    ),
    # A migration that has only this compute node as the dest host
    'dest-only': objects.Migration(
        id=2,
        instance_uuid='f6ed631a-8645-4b12-8e1e-2fff55795765',
        source_compute='other-host',
        dest_compute=_HOSTNAME,
        source_node='other-node',
        dest_node=_NODENAME,
        old_instance_type_id=1,
        new_instance_type_id=2,
        migration_type='resize',
        status='migrating'
    ),
    # A migration that has this compute node as both the source and dest host
    'source-and-dest': objects.Migration(
        id=3,
        instance_uuid='f4f0bfea-fe7e-4264-b598-01cb13ef1997',
        source_compute=_HOSTNAME,
        dest_compute=_HOSTNAME,
        source_node=_NODENAME,
        dest_node=_NODENAME,
        old_instance_type_id=1,
        new_instance_type_id=2,
        migration_type='resize',
        status='migrating'
    ),
    # A migration that has this compute node as destination and is an evac
    'dest-only-evac': objects.Migration(
        id=4,
        instance_uuid='077fb63a-bdc8-4330-90ef-f012082703dc',
        source_compute='other-host',
        dest_compute=_HOSTNAME,
        source_node='other-node',
        dest_node=_NODENAME,
        old_instance_type_id=2,
        new_instance_type_id=None,
        migration_type='evacuation',
        status='pre-migrating'
    ),
}

_MIGRATION_INSTANCE_FIXTURES = {
    # source-only
    'f15ecfb0-9bf6-42db-9837-706eb2c4bf08': objects.Instance(
        id=101,
        host=None,  # prevent RT trying to lazy-load this
        node=None,
        uuid='f15ecfb0-9bf6-42db-9837-706eb2c4bf08',
        memory_mb=_INSTANCE_TYPE_FIXTURES[1]['memory_mb'],
        vcpus=_INSTANCE_TYPE_FIXTURES[1]['vcpus'],
        root_gb=_INSTANCE_TYPE_FIXTURES[1]['root_gb'],
        ephemeral_gb=_INSTANCE_TYPE_FIXTURES[1]['ephemeral_gb'],
        numa_topology=_INSTANCE_NUMA_TOPOLOGIES['2mb'],
        pci_requests=None,
        pci_devices=None,
        instance_type_id=1,
        vm_state=vm_states.ACTIVE,
        power_state=power_state.RUNNING,
        task_state=task_states.RESIZE_MIGRATING,
        system_metadata={},
        os_type='fake-os',
        project_id='fake-project',
        flavor=_INSTANCE_TYPE_OBJ_FIXTURES[1],
        old_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[1],
        new_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
    ),
    # dest-only
    'f6ed631a-8645-4b12-8e1e-2fff55795765': objects.Instance(
        id=102,
        host=None,  # prevent RT trying to lazy-load this
        node=None,
        uuid='f6ed631a-8645-4b12-8e1e-2fff55795765',
        memory_mb=_INSTANCE_TYPE_FIXTURES[2]['memory_mb'],
        vcpus=_INSTANCE_TYPE_FIXTURES[2]['vcpus'],
        root_gb=_INSTANCE_TYPE_FIXTURES[2]['root_gb'],
        ephemeral_gb=_INSTANCE_TYPE_FIXTURES[2]['ephemeral_gb'],
        numa_topology=None,
        pci_requests=None,
        pci_devices=None,
        instance_type_id=2,
        vm_state=vm_states.ACTIVE,
        power_state=power_state.RUNNING,
        task_state=task_states.RESIZE_MIGRATING,
        system_metadata={},
        os_type='fake-os',
        project_id='fake-project',
        flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
        old_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[1],
        new_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
    ),
    # source-and-dest
    'f4f0bfea-fe7e-4264-b598-01cb13ef1997': objects.Instance(
        id=3,
        host=None,  # prevent RT trying to lazy-load this
        node=None,
        uuid='f4f0bfea-fe7e-4264-b598-01cb13ef1997',
        memory_mb=_INSTANCE_TYPE_FIXTURES[2]['memory_mb'],
        vcpus=_INSTANCE_TYPE_FIXTURES[2]['vcpus'],
        root_gb=_INSTANCE_TYPE_FIXTURES[2]['root_gb'],
        ephemeral_gb=_INSTANCE_TYPE_FIXTURES[2]['ephemeral_gb'],
        numa_topology=None,
        pci_requests=None,
        pci_devices=None,
        instance_type_id=2,
        vm_state=vm_states.ACTIVE,
        power_state=power_state.RUNNING,
        task_state=task_states.RESIZE_MIGRATING,
        system_metadata={},
        os_type='fake-os',
        project_id='fake-project',
        flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
        old_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[1],
        new_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
    ),
    # dest-only-evac
    '077fb63a-bdc8-4330-90ef-f012082703dc': objects.Instance(
        id=102,
        host=None,  # prevent RT trying to lazy-load this
        node=None,
        uuid='077fb63a-bdc8-4330-90ef-f012082703dc',
        memory_mb=_INSTANCE_TYPE_FIXTURES[2]['memory_mb'],
        vcpus=_INSTANCE_TYPE_FIXTURES[2]['vcpus'],
        root_gb=_INSTANCE_TYPE_FIXTURES[2]['root_gb'],
        ephemeral_gb=_INSTANCE_TYPE_FIXTURES[2]['ephemeral_gb'],
        numa_topology=None,
        pci_requests=None,
        pci_devices=None,
        instance_type_id=2,
        vm_state=vm_states.ACTIVE,
        power_state=power_state.RUNNING,
        task_state=task_states.REBUILDING,
        system_metadata={},
        os_type='fake-os',
        project_id='fake-project',
        flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
        old_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[1],
        new_flavor=_INSTANCE_TYPE_OBJ_FIXTURES[2],
    ),
}

_MIGRATION_CONTEXT_FIXTURES = {
    'f4f0bfea-fe7e-4264-b598-01cb13ef1997': objects.MigrationContext(
        instance_uuid='f4f0bfea-fe7e-4264-b598-01cb13ef1997',
        migration_id=3,
        new_numa_topology=None,
        old_numa_topology=None),
    'c17741a5-6f3d-44a8-ade8-773dc8c29124': objects.MigrationContext(
        instance_uuid='c17741a5-6f3d-44a8-ade8-773dc8c29124',
        migration_id=3,
        new_numa_topology=None,
        old_numa_topology=None),
    'f15ecfb0-9bf6-42db-9837-706eb2c4bf08': objects.MigrationContext(
        instance_uuid='f15ecfb0-9bf6-42db-9837-706eb2c4bf08',
        migration_id=1,
        new_numa_topology=None,
        old_numa_topology=_INSTANCE_NUMA_TOPOLOGIES['2mb']),
    'f6ed631a-8645-4b12-8e1e-2fff55795765': objects.MigrationContext(
        instance_uuid='f6ed631a-8645-4b12-8e1e-2fff55795765',
        migration_id=2,
        new_numa_topology=_INSTANCE_NUMA_TOPOLOGIES['2mb'],
        old_numa_topology=None),
    '077fb63a-bdc8-4330-90ef-f012082703dc': objects.MigrationContext(
        instance_uuid='077fb63a-bdc8-4330-90ef-f012082703dc',
        migration_id=2,
        new_numa_topology=None,
        old_numa_topology=None),
}


def overhead_zero(instance):
    # Emulate that the driver does not adjust the memory
    # of the instance...
    return {
        'memory_mb': 0,
        'disk_gb': 0,
        'vcpus': 0,
    }


def setup_rt(hostname, virt_resources=_VIRT_DRIVER_AVAIL_RESOURCES,
             estimate_overhead=overhead_zero):
    """Sets up the resource tracker instance with mock fixtures.

    :param virt_resources: Optional override of the resource representation
                           returned by the virt driver's
                           `get_available_resource()` method.
    :param estimate_overhead: Optional override of a function that should
                              return overhead of memory given an instance
                              object. Defaults to returning zero overhead.
    """
    sched_client_mock = mock.MagicMock()
    notifier_mock = mock.MagicMock()
    vd = mock.MagicMock(autospec='nova.virt.driver.ComputeDriver')
    # Make sure we don't change any global fixtures during tests
    virt_resources = copy.deepcopy(virt_resources)
    vd.get_available_resource.return_value = virt_resources
    vd.get_inventory.side_effect = NotImplementedError
    vd.get_host_ip_addr.return_value = _NODENAME
    vd.estimate_instance_overhead.side_effect = estimate_overhead

    with test.nested(
            mock.patch('nova.scheduler.client.SchedulerClient',
                       return_value=sched_client_mock),
            mock.patch('nova.rpc.get_notifier', return_value=notifier_mock)):
        rt = resource_tracker.ResourceTracker(hostname, vd)
    return (rt, sched_client_mock, vd)


def compute_update_usage(resources, flavor, sign=1):
    resources.vcpus_used += sign * flavor.vcpus
    resources.memory_mb_used += sign * flavor.memory_mb
    resources.local_gb_used += sign * (flavor.root_gb + flavor.ephemeral_gb)
    resources.free_ram_mb = resources.memory_mb - resources.memory_mb_used
    resources.free_disk_gb = resources.local_gb - resources.local_gb_used
    return resources


class BaseTestCase(test.NoDBTestCase):

    def setUp(self):
        super(BaseTestCase, self).setUp()
        self.rt = None
        self.flags(my_ip='1.1.1.1',
                   reserved_host_disk_mb=0,
                   reserved_host_memory_mb=0,
                   reserved_host_cpus=0)

    def _setup_rt(self, virt_resources=_VIRT_DRIVER_AVAIL_RESOURCES,
                  estimate_overhead=overhead_zero):
        (self.rt, self.sched_client_mock,
         self.driver_mock) = setup_rt(
                 _HOSTNAME, virt_resources, estimate_overhead)


class TestUpdateAvailableResources(BaseTestCase):

    @mock.patch('nova.objects.Service.get_minimum_version',
                return_value=22)
    def _update_available_resources(self, version_mock):
        # We test RT._update separately, since the complexity
        # of the update_available_resource() function is high enough as
        # it is, we just want to focus here on testing the resources
        # parameter that update_available_resource() eventually passes
        # to _update().
        with mock.patch.object(self.rt, '_update') as update_mock:
            self.rt.update_available_resource(mock.sentinel.ctx, _NODENAME)
        return update_mock

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_disabled(self, get_mock, migr_mock, get_cn_mock, pci_mock,
            instance_pci_mock):
        self._setup_rt()

        # Set up resource tracker in an enabled state and verify that all is
        # good before simulating a disabled node.
        get_mock.return_value = []
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        # This will call _init_compute_node() and create a ComputeNode object
        # and will also call through to InstanceList.get_by_host_and_node()
        # because the node is available.
        self._update_available_resources()

        self.assertTrue(get_mock.called)

        get_mock.reset_mock()

        # OK, now simulate a node being disabled by the Ironic virt driver.
        vd = self.driver_mock
        vd.node_is_available.return_value = False
        self._update_available_resources()

        self.assertFalse(get_mock.called)

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_no_instances_no_migrations_no_reserved(self, get_mock, migr_mock,
                                                    get_cn_mock, pci_mock,
                                                    instance_pci_mock):
        self._setup_rt()

        get_mock.return_value = []
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        update_mock = self._update_available_resources()

        vd = self.driver_mock
        vd.get_available_resource.assert_called_once_with(_NODENAME)
        get_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                         _NODENAME,
                                         expected_attrs=[
                                             'system_metadata',
                                             'numa_topology',
                                             'flavor',
                                             'migration_context'])
        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        migr_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                          _NODENAME)

        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 6,
            'local_gb': 6,
            'free_ram_mb': 512,
            'memory_mb_used': 0,
            'vcpus_used': 0,
            'local_gb_used': 0,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 0
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_no_instances_no_migrations_reserved_disk_ram_and_cpu(
            self, get_mock, migr_mock, get_cn_mock, pci_mock,
            instance_pci_mock):
        self.flags(reserved_host_disk_mb=1024,
                   reserved_host_memory_mb=512,
                   reserved_host_cpus=1)
        self._setup_rt()

        get_mock.return_value = []
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 5,  # 6GB avail - 1 GB reserved
            'local_gb': 6,
            'free_ram_mb': 0,  # 512MB avail - 512MB reserved
            'memory_mb_used': 512,  # 0MB used + 512MB reserved
            'vcpus_used': 1,
            'local_gb_used': 1,  # 0GB used + 1 GB reserved
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 0
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_some_instances_no_migrations(self, get_mock, migr_mock,
                                          get_cn_mock, pci_mock,
                                          instance_pci_mock):
        # Setup virt resources to match used resources to number
        # of defined instances on the hypervisor
        # Note that the usage numbers here correspond to only the first
        # Instance object, because the second instance object fixture is in
        # DELETED state and therefore we should not expect it to be accounted
        # for in the auditing process.
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=1,
                              memory_mb_used=128,
                              local_gb_used=1)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = _INSTANCE_FIXTURES
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 5,  # 6 - 1 used
            'local_gb': 6,
            'free_ram_mb': 384,  # 512 - 128 used
            'memory_mb_used': 128,
            'vcpus_used': 1,
            'local_gb_used': 1,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 1  # One active instance
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_orphaned_instances_no_migrations(self, get_mock, migr_mock,
                                              get_cn_mock, pci_mock,
                                              instance_pci_mock):
        # Setup virt resources to match used resources to number
        # of defined instances on the hypervisor
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(memory_mb_used=64)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = []
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        # Orphaned instances are those that the virt driver has on
        # record as consuming resources on the compute node, but the
        # Nova database has no record of the instance being active
        # on the host. For some reason, the resource tracker only
        # considers orphaned instance's memory usage in its calculations
        # of free resources...
        orphaned_usages = {
            '71ed7ef6-9d2e-4c65-9f4e-90bb6b76261d': {
                # Yes, the return result format of get_per_instance_usage
                # is indeed this stupid and redundant. Also note that the
                # libvirt driver just returns an empty dict always for this
                # method and so who the heck knows whether this stuff
                # actually works.
                'uuid': '71ed7ef6-9d2e-4c65-9f4e-90bb6b76261d',
                'memory_mb': 64
            }
        }
        vd = self.driver_mock
        vd.get_per_instance_usage.return_value = orphaned_usages

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 6,
            'local_gb': 6,
            'free_ram_mb': 448,  # 512 - 64 orphaned usage
            'memory_mb_used': 64,
            'vcpus_used': 0,
            'local_gb_used': 0,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            # Yep, for some reason, orphaned instances are not counted
            # as running VMs...
            'running_vms': 0
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_no_instances_source_migration(self, get_mock, get_inst_mock,
                                           migr_mock, get_cn_mock, pci_mock,
                                           instance_pci_mock):
        # We test the behavior of update_available_resource() when
        # there is an active migration that involves this compute node
        # as the source host not the destination host, and the resource
        # tracker does not have any instances assigned to it. This is
        # the case when a migration from this compute host to another
        # has been completed, but the user has not confirmed the resize
        # yet, so the resource tracker must continue to keep the resources
        # for the original instance type available on the source compute
        # node in case of a revert of the resize.

        # Setup virt resources to match used resources to number
        # of defined instances on the hypervisor
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=4,
                              memory_mb_used=128,
                              local_gb_used=1)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = []
        migr_obj = _MIGRATION_FIXTURES['source-only']
        migr_mock.return_value = [migr_obj]
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]
        # Migration.instance property is accessed in the migration
        # processing code, and this property calls
        # objects.Instance.get_by_uuid, so we have the migration return
        inst_uuid = migr_obj.instance_uuid
        instance = _MIGRATION_INSTANCE_FIXTURES[inst_uuid].obj_clone()
        get_inst_mock.return_value = instance
        instance.migration_context = _MIGRATION_CONTEXT_FIXTURES[inst_uuid]

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 5,
            'local_gb': 6,
            'free_ram_mb': 384,  # 512 total - 128 for possible revert of orig
            'memory_mb_used': 128,  # 128 possible revert amount
            'vcpus_used': 1,
            'local_gb_used': 1,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 0
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_no_instances_dest_migration(self, get_mock, get_inst_mock,
                                         migr_mock, get_cn_mock, pci_mock,
                                         instance_pci_mock):
        # We test the behavior of update_available_resource() when
        # there is an active migration that involves this compute node
        # as the destination host not the source host, and the resource
        # tracker does not yet have any instances assigned to it. This is
        # the case when a migration to this compute host from another host
        # is in progress, but the user has not confirmed the resize
        # yet, so the resource tracker must reserve the resources
        # for the possibly-to-be-confirmed instance's instance type
        # node in case of a confirm of the resize.

        # Setup virt resources to match used resources to number
        # of defined instances on the hypervisor
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=2,
                              memory_mb_used=256,
                              local_gb_used=5)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = []
        migr_obj = _MIGRATION_FIXTURES['dest-only']
        migr_mock.return_value = [migr_obj]
        inst_uuid = migr_obj.instance_uuid
        instance = _MIGRATION_INSTANCE_FIXTURES[inst_uuid].obj_clone()
        get_inst_mock.return_value = instance
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]
        instance.migration_context = _MIGRATION_CONTEXT_FIXTURES[inst_uuid]

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 1,
            'local_gb': 6,
            'free_ram_mb': 256,  # 512 total - 256 for possible confirm of new
            'memory_mb_used': 256,  # 256 possible confirmed amount
            'vcpus_used': 2,
            'local_gb_used': 5,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 0
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_no_instances_dest_evacuation(self, get_mock, get_inst_mock,
                                          migr_mock, get_cn_mock, pci_mock,
                                          instance_pci_mock):
        # We test the behavior of update_available_resource() when
        # there is an active evacuation that involves this compute node
        # as the destination host not the source host, and the resource
        # tracker does not yet have any instances assigned to it. This is
        # the case when a migration to this compute host from another host
        # is in progress, but not finished yet.

        # Setup virt resources to match used resources to number
        # of defined instances on the hypervisor
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=2,
                              memory_mb_used=256,
                              local_gb_used=5)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = []
        migr_obj = _MIGRATION_FIXTURES['dest-only-evac']
        migr_mock.return_value = [migr_obj]
        inst_uuid = migr_obj.instance_uuid
        instance = _MIGRATION_INSTANCE_FIXTURES[inst_uuid].obj_clone()
        get_inst_mock.return_value = instance
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]
        instance.migration_context = _MIGRATION_CONTEXT_FIXTURES[inst_uuid]

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'free_disk_gb': 1,
            'free_ram_mb': 256,  # 512 total - 256 for possible confirm of new
            'memory_mb_used': 256,  # 256 possible confirmed amount
            'vcpus_used': 2,
            'local_gb_used': 5,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 0
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.MigrationContext.get_by_instance_uuid',
                return_value=None)
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    def test_some_instances_source_and_dest_migration(self, get_mock,
                                                      get_inst_mock, migr_mock,
                                                      get_cn_mock,
                                                      get_mig_ctxt_mock,
                                                      pci_mock,
                                                      instance_pci_mock):
        # We test the behavior of update_available_resource() when
        # there is an active migration that involves this compute node
        # as the destination host AND the source host, and the resource
        # tracker has a few instances assigned to it, including the
        # instance that is resizing to this same compute node. The tracking
        # of resource amounts takes into account both the old and new
        # resize instance types as taking up space on the node.

        # Setup virt resources to match used resources to number
        # of defined instances on the hypervisor
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=4,
                              memory_mb_used=512,
                              local_gb_used=7)
        self._setup_rt(virt_resources=virt_resources)

        migr_obj = _MIGRATION_FIXTURES['source-and-dest']
        migr_mock.return_value = [migr_obj]
        inst_uuid = migr_obj.instance_uuid
        # The resizing instance has already had its instance type
        # changed to the *new* instance type (the bigger one, instance type 2)
        resizing_instance = _MIGRATION_INSTANCE_FIXTURES[inst_uuid].obj_clone()
        resizing_instance.migration_context = (
            _MIGRATION_CONTEXT_FIXTURES[resizing_instance.uuid])
        all_instances = _INSTANCE_FIXTURES + [resizing_instance]
        get_mock.return_value = all_instances
        get_inst_mock.return_value = resizing_instance
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        update_mock = self._update_available_resources()

        get_cn_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                            _NODENAME)
        expected_resources = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            # 6 total - 1G existing - 5G new flav - 1G old flav
            'free_disk_gb': -1,
            'local_gb': 6,
            # 512 total - 128 existing - 256 new flav - 128 old flav
            'free_ram_mb': 0,
            'memory_mb_used': 512,  # 128 exist + 256 new flav + 128 old flav
            'vcpus_used': 4,
            'local_gb_used': 7,  # 1G existing, 5G new flav + 1 old flav
            'memory_mb': 512,
            'current_workload': 1,  # One migrating instance...
            'vcpus': 4,
            'running_vms': 2
        }
        _update_compute_node(expected_resources, **vals)
        actual_resources = update_mock.call_args[0][1]
        self.assertTrue(obj_base.obj_equal_prims(expected_resources,
                                                 actual_resources))


class TestInitComputeNode(BaseTestCase):

    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.create')
    @mock.patch('nova.objects.Service.get_by_compute_host')
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update')
    def test_no_op_init_compute_node(self, update_mock, get_mock, service_mock,
                                     create_mock, pci_mock):
        self._setup_rt()

        resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        compute_node = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        self.rt.compute_nodes[_NODENAME] = compute_node

        self.rt._init_compute_node(mock.sentinel.ctx, resources)

        self.assertFalse(service_mock.called)
        self.assertFalse(get_mock.called)
        self.assertFalse(create_mock.called)
        self.assertTrue(pci_mock.called)
        self.assertTrue(update_mock.called)

    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.create')
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update')
    def test_compute_node_loaded(self, update_mock, get_mock, create_mock,
                                 pci_mock):
        self._setup_rt()

        def fake_get_node(_ctx, host, node):
            res = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
            return res

        get_mock.side_effect = fake_get_node
        resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)

        self.rt._init_compute_node(mock.sentinel.ctx, resources)

        get_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                         _NODENAME)
        self.assertFalse(create_mock.called)
        self.assertTrue(update_mock.called)

    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList(objects=[]))
    @mock.patch('nova.objects.ComputeNode.create')
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update')
    def test_compute_node_created_on_empty(self, update_mock, get_mock,
                                           create_mock, pci_tracker_mock):
        self.flags(cpu_allocation_ratio=1.0, ram_allocation_ratio=1.0,
                   disk_allocation_ratio=1.0)
        self._setup_rt()

        get_mock.side_effect = exc.NotFound

        resources = {
            'host_ip': '1.1.1.1',
            'numa_topology': None,
            'metrics': '[]',
            'cpu_info': '',
            'hypervisor_hostname': _NODENAME,
            'free_disk_gb': 6,
            'hypervisor_version': 0,
            'local_gb': 6,
            'free_ram_mb': 512,
            'memory_mb_used': 0,
            'pci_device_pools': [],
            'vcpus_used': 0,
            'hypervisor_type': 'fake',
            'local_gb_used': 0,
            'memory_mb': 512,
            'current_workload': 0,
            'vcpus': 4,
            'running_vms': 0,
            'pci_passthrough_devices': '[]'
        }
        # The expected compute represents the initial values used
        # when creating a compute node.
        expected_compute = objects.ComputeNode(
            id=42,
            host_ip=resources['host_ip'],
            vcpus=resources['vcpus'],
            memory_mb=resources['memory_mb'],
            local_gb=resources['local_gb'],
            cpu_info=resources['cpu_info'],
            vcpus_used=resources['vcpus_used'],
            memory_mb_used=resources['memory_mb_used'],
            local_gb_used=resources['local_gb_used'],
            numa_topology=resources['numa_topology'],
            hypervisor_type=resources['hypervisor_type'],
            hypervisor_version=resources['hypervisor_version'],
            hypervisor_hostname=resources['hypervisor_hostname'],
            # NOTE(sbauza): ResourceTracker adds host field
            host=_HOSTNAME,
            # NOTE(sbauza): ResourceTracker adds CONF allocation ratios
            ram_allocation_ratio=1.0,
            cpu_allocation_ratio=1.0,
            disk_allocation_ratio=1.0,
            stats={},
            pci_device_pools=objects.PciDevicePoolList(objects=[]),
            uuid=uuids.compute_node_uuid
        )

        def set_cn_id():
            # The PCI tracker needs the compute node's ID when starting up, so
            # make sure that we set the ID value so we don't get a Cannot load
            # 'id' in base class error
            cn = self.rt.compute_nodes[_NODENAME]
            cn.id = 42  # Has to be a number, not a mock
            cn.uuid = uuids.compute_node_uuid

        create_mock.side_effect = set_cn_id
        self.rt._init_compute_node(mock.sentinel.ctx, resources)

        cn = self.rt.compute_nodes[_NODENAME]
        get_mock.assert_called_once_with(mock.sentinel.ctx, _HOSTNAME,
                                         _NODENAME)
        create_mock.assert_called_once_with()
        self.assertTrue(obj_base.obj_equal_prims(expected_compute, cn))
        pci_tracker_mock.assert_called_once_with(mock.sentinel.ctx,
                                                 42)
        self.assertTrue(update_mock.called)


class TestUpdateComputeNode(BaseTestCase):

    @mock.patch('nova.objects.ComputeNode.save')
    def test_existing_compute_node_updated_same_resources(self, save_mock):
        self._setup_rt()

        # This is the same set of resources as the fixture, deliberately. We
        # are checking below to see that update_compute_node() is not
        # needlessly called when the resources don't actually change.
        orig_compute = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        self.rt.compute_nodes[_NODENAME] = orig_compute
        self.rt.old_resources[_NODENAME] = orig_compute

        new_compute = orig_compute.obj_clone()

        # Here, we check that if we call _update() with the same resources that
        # are already stored in the resource tracker, that the scheduler client
        # won't be called again to update those (unchanged) resources for the
        # compute node
        ucn_mock = self.sched_client_mock.update_compute_node
        self.rt._update(mock.sentinel.ctx, new_compute)
        self.assertFalse(ucn_mock.called)
        self.assertFalse(save_mock.called)

    @mock.patch('nova.objects.ComputeNode.save')
    def test_existing_compute_node_updated_diff_updated_at(self, save_mock):
        self._setup_rt()
        ts1 = timeutils.utcnow()
        ts2 = ts1 + datetime.timedelta(seconds=10)

        orig_compute = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        orig_compute.updated_at = ts1
        self.rt.compute_nodes[_NODENAME] = orig_compute
        self.rt.old_resources[_NODENAME] = orig_compute

        # Make the new_compute object have a different timestamp
        # from orig_compute.
        new_compute = orig_compute.obj_clone()
        new_compute.updated_at = ts2

        ucn_mock = self.sched_client_mock.update_compute_node
        self.rt._update(mock.sentinel.ctx, new_compute)
        self.assertFalse(save_mock.called)
        self.assertFalse(ucn_mock.called)

    @mock.patch('nova.objects.ComputeNode.save')
    def test_existing_compute_node_updated_new_resources(self, save_mock):
        self._setup_rt()

        orig_compute = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        self.rt.compute_nodes[_NODENAME] = orig_compute
        self.rt.old_resources[_NODENAME] = orig_compute

        # Deliberately changing local_gb_used, vcpus_used, and memory_mb_used
        # below to be different from the compute node fixture's base usages.
        # We want to check that the code paths update the stored compute node
        # usage records with what is supplied to _update().
        new_compute = orig_compute.obj_clone()
        new_compute.memory_mb_used = 128
        new_compute.vcpus_used = 2
        new_compute.local_gb_used = 4

        ucn_mock = self.sched_client_mock.update_compute_node
        self.rt._update(mock.sentinel.ctx, new_compute)
        save_mock.assert_called_once_with()
        ucn_mock.assert_called_once_with(new_compute)

    @mock.patch('nova.compute.resource_tracker.'
                '_normalize_inventory_from_cn_obj')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_existing_node_get_inventory_implemented(self, save_mock,
            norm_mock):
        """The get_inventory() virt driver method is only implemented for some
        virt drivers. This method returns inventory information for a
        node/provider in a way that the placement API better understands, and
        if this method doesn't raise a NotImplementedError, this triggers
        _update() to call the set_inventory_for_provider() method of the
        reporting client instead of the update_compute_node() method.
        """
        self._setup_rt()

        # Emulate a driver that has implemented the new get_inventory() virt
        # driver method
        self.driver_mock.get_inventory.side_effect = [mock.sentinel.inv_data]

        orig_compute = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        self.rt.compute_nodes[_NODENAME] = orig_compute
        self.rt.old_resources[_NODENAME] = orig_compute

        # Deliberately changing local_gb to trigger updating inventory
        new_compute = orig_compute.obj_clone()
        new_compute.local_gb = 210000

        ucn_mock = self.sched_client_mock.update_compute_node
        sifp_mock = self.sched_client_mock.set_inventory_for_provider
        self.rt._update(mock.sentinel.ctx, new_compute)
        save_mock.assert_called_once_with()
        sifp_mock.assert_called_once_with(
            new_compute.uuid,
            new_compute.hypervisor_hostname,
            mock.sentinel.inv_data,
        )
        self.assertFalse(ucn_mock.called)


class TestNormalizatInventoryFromComputeNode(test.NoDBTestCase):
    def test_normalize_libvirt(self):
        self.flags(reserved_host_disk_mb=100,
                   reserved_host_memory_mb=10,
                   reserved_host_cpus=1)
        vcpus = 24
        memory_mb = 1024
        disk_gb = 200
        cn = objects.ComputeNode(
            mock.sentinel.ctx,
            ram_allocation_ratio=1.5,
            cpu_allocation_ratio=16.0,
            disk_allocation_ratio=1.0,
        )
        # What we get back from libvirt driver, for instance, doesn't contain
        # allocation_ratio or reserved amounts for some resources. Verify that
        # the information on the compute node fills in this information...
        inv = {
            obj_fields.ResourceClass.VCPU: {
                'total': vcpus,
                'min_unit': 1,
                'max_unit': vcpus,
                'step_size': 1,
            },
            obj_fields.ResourceClass.MEMORY_MB: {
                'total': memory_mb,
                'min_unit': 1,
                'max_unit': memory_mb,
                'step_size': 1,
            },
            obj_fields.ResourceClass.DISK_GB: {
                'total': disk_gb,
                'min_unit': 1,
                'max_unit': disk_gb,
                'step_size': 1,
            },
        }
        expected = {
            obj_fields.ResourceClass.VCPU: {
                'total': vcpus,
                'reserved': 1,
                'min_unit': 1,
                'max_unit': vcpus,
                'step_size': 1,
                'allocation_ratio': 16.0,
            },
            obj_fields.ResourceClass.MEMORY_MB: {
                'total': memory_mb,
                'reserved': 10,
                'min_unit': 1,
                'max_unit': memory_mb,
                'step_size': 1,
                'allocation_ratio': 1.5,
            },
            obj_fields.ResourceClass.DISK_GB: {
                'total': disk_gb,
                'reserved': 1,  # Rounded up from CONF.reserved_host_disk_mb
                'min_unit': 1,
                'max_unit': disk_gb,
                'step_size': 1,
                'allocation_ratio': 1.0,
            },
        }
        self.assertNotEqual(expected, inv)
        resource_tracker._normalize_inventory_from_cn_obj(inv, cn)
        self.assertEqual(expected, inv)

    def test_normalize_ironic(self):
        """Test that when normalizing the return from Ironic virt driver's
        get_inventory() method, we don't overwrite the information that the
        virt driver gave us.
        """
        self.flags(reserved_host_disk_mb=100,
                   reserved_host_memory_mb=10,
                   reserved_host_cpus=1)
        vcpus = 24
        memory_mb = 1024
        disk_gb = 200
        # We will make sure that these field values do NOT override what the
        # Ironic virt driver sets (which is, for example, that allocation
        # ratios are all 1.0 for Ironic baremetal nodes)
        cn = objects.ComputeNode(
            mock.sentinel.ctx,
            ram_allocation_ratio=1.5,
            cpu_allocation_ratio=16.0,
            disk_allocation_ratio=1.0,
        )
        # What we get back from Ironic driver contains fully-filled-out info
        # blocks for VCPU, MEMORY_MB, DISK_GB and the custom resource class
        # inventory items
        inv = {
            obj_fields.ResourceClass.VCPU: {
                'total': vcpus,
                'reserved': 0,
                'min_unit': 1,
                'max_unit': vcpus,
                'step_size': 1,
                'allocation_ratio': 1.0,
            },
            obj_fields.ResourceClass.MEMORY_MB: {
                'total': memory_mb,
                'reserved': 0,
                'min_unit': 1,
                'max_unit': memory_mb,
                'step_size': 1,
                'allocation_ratio': 1.0,
            },
            obj_fields.ResourceClass.DISK_GB: {
                'total': disk_gb,
                'reserved': 0,
                'min_unit': 1,
                'max_unit': disk_gb,
                'step_size': 1,
                'allocation_ratio': 1.0,
            },
        }
        # We are expecting that NOTHING changes after calling the normalization
        # function
        expected = copy.deepcopy(inv)
        resource_tracker._normalize_inventory_from_cn_obj(inv, cn)
        self.assertEqual(expected, inv)


class TestInstanceClaim(BaseTestCase):

    def setUp(self):
        super(TestInstanceClaim, self).setUp()
        self.flags(reserved_host_disk_mb=0, reserved_host_memory_mb=0)

        self._setup_rt()
        cn = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        self.rt.compute_nodes[_NODENAME] = cn

        # not using mock.sentinel.ctx because instance_claim calls #elevated
        self.ctx = mock.MagicMock()
        self.elevated = mock.MagicMock()
        self.ctx.elevated.return_value = self.elevated

        self.instance = _INSTANCE_FIXTURES[0].obj_clone()

    def assertEqualNUMAHostTopology(self, expected, got):
        attrs = ('cpuset', 'memory', 'id', 'cpu_usage', 'memory_usage')
        if None in (expected, got):
            if expected != got:
                raise AssertionError("Topologies don't match. Expected: "
                                     "%(expected)s, but got: %(got)s" %
                                     {'expected': expected, 'got': got})
            else:
                return

        if len(expected) != len(got):
            raise AssertionError("Topologies don't match due to different "
                                 "number of cells. Expected: "
                                 "%(expected)s, but got: %(got)s" %
                                 {'expected': expected, 'got': got})
        for exp_cell, got_cell in zip(expected.cells, got.cells):
            for attr in attrs:
                if getattr(exp_cell, attr) != getattr(got_cell, attr):
                    raise AssertionError("Topologies don't match. Expected: "
                                         "%(expected)s, but got: %(got)s" %
                                         {'expected': expected, 'got': got})

    def test_claim_disabled(self):
        self.rt.compute_nodes = {}
        self.assertTrue(self.rt.disabled(_NODENAME))

        with mock.patch.object(self.instance, 'save'):
            claim = self.rt.instance_claim(mock.sentinel.ctx, self.instance,
                                           _NODENAME, None)

        self.assertEqual(self.rt.host, self.instance.host)
        self.assertEqual(self.rt.host, self.instance.launched_on)
        self.assertEqual(_NODENAME, self.instance.node)
        self.assertIsInstance(claim, claims.NopClaim)

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    def test_update_usage_with_claim(self, migr_mock, pci_mock):
        # Test that RT.update_usage() only changes the compute node
        # resources if there has been a claim first.
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])

        expected = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        self.rt.update_usage(self.ctx, self.instance, _NODENAME)
        cn = self.rt.compute_nodes[_NODENAME]
        self.assertTrue(obj_base.obj_equal_prims(expected, cn))

        disk_used = self.instance.root_gb + self.instance.ephemeral_gb
        vals = {
            'local_gb_used': disk_used,
            'memory_mb_used': self.instance.memory_mb,
            'free_disk_gb': expected.local_gb - disk_used,
            "free_ram_mb": expected.memory_mb - self.instance.memory_mb,
            'running_vms': 1,
            'vcpus_used': 1,
            'pci_device_pools': objects.PciDevicePoolList(),
            'stats': {
                'io_workload': 0,
                'num_instances': 1,
                'num_task_None': 1,
                'num_os_type_' + self.instance.os_type: 1,
                'num_proj_' + self.instance.project_id: 1,
                'num_vm_' + self.instance.vm_state: 1,
            },
        }
        _update_compute_node(expected, **vals)
        with mock.patch.object(self.rt, '_update') as update_mock:
            with mock.patch.object(self.instance, 'save'):
                self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                       None)
            cn = self.rt.compute_nodes[_NODENAME]
            update_mock.assert_called_once_with(self.elevated, cn)
            self.assertTrue(obj_base.obj_equal_prims(expected, cn))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    def test_update_usage_removed(self, migr_mock, pci_mock):
        # Test that RT.update_usage() removes the instance when update is
        # called in a removed state
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])

        expected = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        disk_used = self.instance.root_gb + self.instance.ephemeral_gb
        vals = {
            'local_gb_used': disk_used,
            'memory_mb_used': self.instance.memory_mb,
            'free_disk_gb': expected.local_gb - disk_used,
            "free_ram_mb": expected.memory_mb - self.instance.memory_mb,
            'running_vms': 1,
            'vcpus_used': 1,
            'pci_device_pools': objects.PciDevicePoolList(),
            'stats': {
                'io_workload': 0,
                'num_instances': 1,
                'num_task_None': 1,
                'num_os_type_' + self.instance.os_type: 1,
                'num_proj_' + self.instance.project_id: 1,
                'num_vm_' + self.instance.vm_state: 1,
            },
        }
        _update_compute_node(expected, **vals)
        with mock.patch.object(self.rt, '_update') as update_mock:
            with mock.patch.object(self.instance, 'save'):
                self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                       None)
            cn = self.rt.compute_nodes[_NODENAME]
            update_mock.assert_called_once_with(self.elevated, cn)
            self.assertTrue(obj_base.obj_equal_prims(expected, cn))

        expected_updated = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'pci_device_pools': objects.PciDevicePoolList(),
            'stats': {
                'io_workload': 0,
                'num_instances': 0,
                'num_task_None': 0,
                'num_os_type_' + self.instance.os_type: 0,
                'num_proj_' + self.instance.project_id: 0,
                'num_vm_' + self.instance.vm_state: 0,
            },
        }
        _update_compute_node(expected_updated, **vals)

        self.instance.vm_state = vm_states.SHELVED_OFFLOADED
        with mock.patch.object(self.rt, '_update') as update_mock:
            self.rt.update_usage(self.ctx, self.instance, _NODENAME)
        cn = self.rt.compute_nodes[_NODENAME]
        self.assertTrue(obj_base.obj_equal_prims(expected_updated, cn))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    def test_claim(self, migr_mock, pci_mock):
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])

        disk_used = self.instance.root_gb + self.instance.ephemeral_gb
        expected = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'local_gb_used': disk_used,
            'memory_mb_used': self.instance.memory_mb,
            'free_disk_gb': expected.local_gb - disk_used,
            "free_ram_mb": expected.memory_mb - self.instance.memory_mb,
            'running_vms': 1,
            'vcpus_used': 1,
            'pci_device_pools': objects.PciDevicePoolList(),
            'stats': {
                'io_workload': 0,
                'num_instances': 1,
                'num_task_None': 1,
                'num_os_type_' + self.instance.os_type: 1,
                'num_proj_' + self.instance.project_id: 1,
                'num_vm_' + self.instance.vm_state: 1,
            },
        }
        _update_compute_node(expected, **vals)
        with mock.patch.object(self.rt, '_update') as update_mock:
            with mock.patch.object(self.instance, 'save'):
                self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                       None)
            cn = self.rt.compute_nodes[_NODENAME]
            update_mock.assert_called_once_with(self.elevated, cn)
            self.assertTrue(obj_base.obj_equal_prims(expected, cn))

        self.assertEqual(self.rt.host, self.instance.host)
        self.assertEqual(self.rt.host, self.instance.launched_on)
        self.assertEqual(_NODENAME, self.instance.node)

    @mock.patch('nova.pci.stats.PciDeviceStats.support_requests',
                return_value=True)
    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    def test_claim_with_pci(self, migr_mock, pci_mock, pci_stats_mock):
        # Test that a claim involving PCI requests correctly claims
        # PCI devices on the host and sends an updated pci_device_pools
        # attribute of the ComputeNode object.

        # TODO(jaypipes): Remove once the PCI tracker is always created
        # upon the resource tracker being initialized...
        self.rt.pci_tracker = pci_manager.PciDevTracker(mock.sentinel.ctx)

        pci_dev = pci_device.PciDevice.create(
            None, fake_pci_device.dev_dict)
        pci_devs = [pci_dev]
        self.rt.pci_tracker.pci_devs = objects.PciDeviceList(objects=pci_devs)

        request = objects.InstancePCIRequest(count=1,
            spec=[{'vendor_id': 'v', 'product_id': 'p'}])
        pci_requests = objects.InstancePCIRequests(
                requests=[request],
                instance_uuid=self.instance.uuid)
        pci_mock.return_value = pci_requests

        disk_used = self.instance.root_gb + self.instance.ephemeral_gb
        expected = copy.deepcopy(_COMPUTE_NODE_FIXTURES[0])
        vals = {
            'local_gb_used': disk_used,
            'memory_mb_used': self.instance.memory_mb,
            'free_disk_gb': expected.local_gb - disk_used,
            "free_ram_mb": expected.memory_mb - self.instance.memory_mb,
            'running_vms': 1,
            'vcpus_used': 1,
            'pci_device_pools': objects.PciDevicePoolList(),
            'stats': {
                'io_workload': 0,
                'num_instances': 1,
                'num_task_None': 1,
                'num_os_type_' + self.instance.os_type: 1,
                'num_proj_' + self.instance.project_id: 1,
                'num_vm_' + self.instance.vm_state: 1,
            },
        }
        _update_compute_node(expected, **vals)
        with mock.patch.object(self.rt, '_update') as update_mock:
            with mock.patch.object(self.instance, 'save'):
                self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                       None)
            cn = self.rt.compute_nodes[_NODENAME]
            update_mock.assert_called_once_with(self.elevated, cn)
            pci_stats_mock.assert_called_once_with([request])
            self.assertTrue(obj_base.obj_equal_prims(expected, cn))

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_claim_abort_context_manager(self, save_mock, migr_mock, pci_mock):
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])

        cn = self.rt.compute_nodes[_NODENAME]
        self.assertEqual(0, cn.local_gb_used)
        self.assertEqual(0, cn.memory_mb_used)
        self.assertEqual(0, cn.running_vms)

        mock_save = mock.MagicMock()
        mock_clear_numa = mock.MagicMock()

        @mock.patch.object(self.instance, 'save', mock_save)
        @mock.patch.object(self.instance, 'clear_numa_topology',
                           mock_clear_numa)
        @mock.patch.object(objects.Instance, 'obj_clone',
                           return_value=self.instance)
        def _doit(mock_clone):
            with self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                        None):
                # Raise an exception. Just make sure below that the abort()
                # method of the claim object was called (and the resulting
                # resources reset to the pre-claimed amounts)
                raise test.TestingException()

        self.assertRaises(test.TestingException, _doit)
        self.assertEqual(2, mock_save.call_count)
        mock_clear_numa.assert_called_once_with()
        self.assertIsNone(self.instance.host)
        self.assertIsNone(self.instance.node)

        # Assert that the resources claimed by the Claim() constructor
        # are returned to the resource tracker due to the claim's abort()
        # method being called when triggered by the exception raised above.
        self.assertEqual(0, cn.local_gb_used)
        self.assertEqual(0, cn.memory_mb_used)
        self.assertEqual(0, cn.running_vms)

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_claim_abort(self, save_mock, migr_mock, pci_mock):
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])
        disk_used = self.instance.root_gb + self.instance.ephemeral_gb

        @mock.patch.object(objects.Instance, 'obj_clone',
                           return_value=self.instance)
        @mock.patch.object(self.instance, 'save')
        def _claim(mock_save, mock_clone):
            return self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                          None)

        cn = self.rt.compute_nodes[_NODENAME]

        claim = _claim()
        self.assertEqual(disk_used, cn.local_gb_used)
        self.assertEqual(self.instance.memory_mb,
                         cn.memory_mb_used)
        self.assertEqual(1, cn.running_vms)

        mock_save = mock.MagicMock()
        mock_clear_numa = mock.MagicMock()

        @mock.patch.object(self.instance, 'save', mock_save)
        @mock.patch.object(self.instance, 'clear_numa_topology',
                           mock_clear_numa)
        def _abort():
            claim.abort()

        _abort()
        mock_save.assert_called_once_with()
        mock_clear_numa.assert_called_once_with()
        self.assertIsNone(self.instance.host)
        self.assertIsNone(self.instance.node)

        self.assertEqual(0, cn.local_gb_used)
        self.assertEqual(0, cn.memory_mb_used)
        self.assertEqual(0, cn.running_vms)

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_claim_limits(self, save_mock, migr_mock, pci_mock):
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])

        good_limits = {
            'memory_mb': _COMPUTE_NODE_FIXTURES[0].memory_mb,
            'disk_gb': _COMPUTE_NODE_FIXTURES[0].local_gb,
            'vcpu': _COMPUTE_NODE_FIXTURES[0].vcpus,
        }
        for key in good_limits.keys():
            bad_limits = copy.deepcopy(good_limits)
            bad_limits[key] = 0

            self.assertRaises(exc.ComputeResourcesUnavailable,
                    self.rt.instance_claim,
                    self.ctx, self.instance, _NODENAME, bad_limits)

    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_claim_numa(self, save_mock, migr_mock, pci_mock):
        pci_mock.return_value = objects.InstancePCIRequests(requests=[])
        cn = self.rt.compute_nodes[_NODENAME]

        self.instance.numa_topology = _INSTANCE_NUMA_TOPOLOGIES['2mb']
        host_topology = _NUMA_HOST_TOPOLOGIES['2mb']
        cn.numa_topology = host_topology._to_json()
        limits = {'numa_topology': _NUMA_LIMIT_TOPOLOGIES['2mb']}

        expected_numa = copy.deepcopy(host_topology)
        for cell in expected_numa.cells:
            cell.memory_usage += _2MB
            cell.cpu_usage += 1
        with mock.patch.object(self.rt, '_update') as update_mock:
            with mock.patch.object(self.instance, 'save'):
                self.rt.instance_claim(self.ctx, self.instance, _NODENAME,
                                       limits)
            update_mock.assert_called_once_with(self.ctx.elevated(), cn)
            new_numa = cn.numa_topology
            new_numa = objects.NUMATopology.obj_from_db_obj(new_numa)
            self.assertEqualNUMAHostTopology(expected_numa, new_numa)


class TestResize(BaseTestCase):
    @mock.patch('nova.compute.utils.is_volume_backed_instance',
                return_value=False)
    @mock.patch('nova.objects.Service.get_minimum_version',
                return_value=22)
    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_resize_claim_same_host(self, save_mock, get_mock, migr_mock,
                                    get_cn_mock, pci_mock, instance_pci_mock,
                                    version_mock, is_bfv_mock):
        # Resize an existing instance from its current flavor (instance type
        # 1) to a new flavor (instance type 2) and verify that the compute
        # node's resources are appropriately updated to account for the new
        # flavor's resources. In this scenario, we use an Instance that has not
        # already had its "current" flavor set to the new flavor.
        self.flags(reserved_host_disk_mb=0,
                   reserved_host_memory_mb=0)
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=1,
                              memory_mb_used=128,
                              local_gb_used=1)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = _INSTANCE_FIXTURES
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]
        # This migration context is fine, it points to the first instance
        # fixture and indicates a source-and-dest resize.
        mig_context_obj = _MIGRATION_CONTEXT_FIXTURES[instance.uuid]
        instance.migration_context = mig_context_obj

        self.rt.update_available_resource(mock.sentinel.ctx, _NODENAME)

        migration = objects.Migration(
            id=3,
            instance_uuid=instance.uuid,
            source_compute=_HOSTNAME,
            dest_compute=_HOSTNAME,
            source_node=_NODENAME,
            dest_node=_NODENAME,
            old_instance_type_id=1,
            new_instance_type_id=2,
            migration_type='resize',
            status='migrating'
        )
        new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        # not using mock.sentinel.ctx because resize_claim calls #elevated
        ctx = mock.MagicMock()

        expected = self.rt.compute_nodes[_NODENAME].obj_clone()
        expected.vcpus_used = (expected.vcpus_used +
                               new_flavor.vcpus)
        expected.memory_mb_used = (expected.memory_mb_used +
                                   new_flavor.memory_mb)
        expected.free_ram_mb = expected.memory_mb - expected.memory_mb_used
        expected.local_gb_used = (expected.local_gb_used +
                                 (new_flavor.root_gb +
                                    new_flavor.ephemeral_gb))
        expected.free_disk_gb = (expected.free_disk_gb -
                                (new_flavor.root_gb +
                                    new_flavor.ephemeral_gb))

        with test.nested(
            mock.patch('nova.compute.resource_tracker.ResourceTracker'
                       '._create_migration',
                       return_value=migration),
            mock.patch('nova.objects.MigrationContext',
                       return_value=mig_context_obj),
            mock.patch('nova.objects.Instance.save'),
        ) as (create_mig_mock, ctxt_mock, inst_save_mock):
            claim = self.rt.resize_claim(ctx, instance, new_flavor, _NODENAME)

        create_mig_mock.assert_called_once_with(
                ctx, instance, new_flavor, _NODENAME,
                None  # move_type is None for resize...
        )
        self.assertIsInstance(claim, claims.MoveClaim)
        cn = self.rt.compute_nodes[_NODENAME]
        self.assertTrue(obj_base.obj_equal_prims(expected, cn))
        self.assertEqual(1, len(self.rt.tracked_migrations))

        # Now abort the resize claim and check that the resources have been set
        # back to their original values.
        with mock.patch('nova.objects.Instance.'
                        'drop_migration_context') as drop_migr_mock:
            claim.abort()
        drop_migr_mock.assert_called_once_with()

        self.assertEqual(1, cn.vcpus_used)
        self.assertEqual(1, cn.local_gb_used)
        self.assertEqual(128, cn.memory_mb_used)
        self.assertEqual(0, len(self.rt.tracked_migrations))

    @mock.patch('nova.compute.utils.is_volume_backed_instance',
                return_value=False)
    @mock.patch('nova.objects.Service.get_minimum_version',
                return_value=22)
    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance_uuid',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename',
                return_value=_COMPUTE_NODE_FIXTURES[0])
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node',
                return_value=[])
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node',
                return_value=[])
    @mock.patch('nova.objects.ComputeNode.save')
    def _test_instance_build_resize(self,
                                    save_mock,
                                    get_by_host_and_node_mock,
                                    get_in_progress_by_host_and_node_mock,
                                    get_by_host_and_nodename_mock,
                                    pci_get_by_compute_node_mock,
                                    pci_get_by_instance_mock,
                                    pci_get_by_instance_uuid_mock,
                                    version_mock,
                                    is_bfv_mock,
                                    revert=False):

        self.flags(reserved_host_disk_mb=0,
                   reserved_host_memory_mb=0)
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        self._setup_rt(virt_resources=virt_resources)

        # not using mock.sentinel.ctx because resize_claim calls #elevated
        ctx = mock.MagicMock()

        # Init compute node
        self.rt.update_available_resource(mock.sentinel.ctx, _NODENAME)
        expected = self.rt.compute_nodes[_NODENAME].obj_clone()

        instance = _INSTANCE_FIXTURES[0].obj_clone()
        old_flavor = instance.flavor
        instance.new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        # Build instance
        with mock.patch.object(instance, 'save'):
            self.rt.instance_claim(ctx, instance, _NODENAME, None)

        expected = compute_update_usage(expected, old_flavor, sign=1)
        expected.running_vms = 1
        self.assertTrue(obj_base.obj_equal_prims(
            expected,
            self.rt.compute_nodes[_NODENAME],
            ignore=['stats']
        ))

        # This migration context is fine, it points to the first instance
        # fixture and indicates a source-and-dest resize.
        mig_context_obj = _MIGRATION_CONTEXT_FIXTURES[instance.uuid]
        instance.migration_context = mig_context_obj

        migration = objects.Migration(
            id=3,
            instance_uuid=instance.uuid,
            source_compute=_HOSTNAME,
            dest_compute=_HOSTNAME,
            source_node=_NODENAME,
            dest_node=_NODENAME,
            old_instance_type_id=1,
            new_instance_type_id=2,
            migration_type='resize',
            status='migrating'
        )
        new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        # Resize instance
        with test.nested(
            mock.patch('nova.compute.resource_tracker.ResourceTracker'
                       '._create_migration',
                       return_value=migration),
            mock.patch('nova.objects.MigrationContext',
                       return_value=mig_context_obj),
            mock.patch('nova.objects.Instance.save'),
        ) as (create_mig_mock, ctxt_mock, inst_save_mock):
            self.rt.resize_claim(ctx, instance, new_flavor, _NODENAME)

        expected = compute_update_usage(expected, new_flavor, sign=1)
        self.assertTrue(obj_base.obj_equal_prims(
            expected,
            self.rt.compute_nodes[_NODENAME],
            ignore=['stats']
        ))
        # Confirm or revert resize
        if revert:
            flavor = new_flavor
            prefix = 'new_'
        else:
            flavor = old_flavor
            prefix = 'old_'

        self.rt.drop_move_claim(ctx, instance, _NODENAME, flavor,
                                prefix=prefix)

        expected = compute_update_usage(expected, flavor, sign=-1)
        self.assertTrue(obj_base.obj_equal_prims(
            expected,
            self.rt.compute_nodes[_NODENAME],
            ignore=['stats']
        ))

        cn = self.rt.compute_nodes[_NODENAME]
        cn_uuid = cn.uuid
        rc = self.sched_client_mock.reportclient
        remove_method = rc.remove_provider_from_instance_allocation
        expected_resources = sched_utils.resources_from_flavor(instance,
            flavor)
        remove_method.assert_called_once_with(instance.uuid, cn_uuid,
            instance.user_id, instance.project_id, expected_resources)

    def test_instance_build_resize_revert(self):
        self._test_instance_build_resize(revert=True)

    def test_instance_build_resize_confirm(self):
        self._test_instance_build_resize()

    @mock.patch('nova.objects.Service.get_minimum_version',
                return_value=22)
    @mock.patch('nova.pci.stats.PciDeviceStats.support_requests',
                return_value=True)
    @mock.patch('nova.objects.PciDevice.save')
    @mock.patch('nova.pci.manager.PciDevTracker.claim_instance')
    @mock.patch('nova.pci.request.get_pci_requests_from_flavor')
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node')
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_resize_claim_dest_host_with_pci(self, save_mock, get_mock,
            migr_mock, get_cn_mock, pci_mock, pci_req_mock, pci_claim_mock,
            pci_dev_save_mock, pci_supports_mock, version_mock):
        # Starting from an empty destination compute node, perform a resize
        # operation for an instance containing SR-IOV PCI devices on the
        # original host.
        self.flags(reserved_host_disk_mb=0,
                   reserved_host_memory_mb=0)
        self._setup_rt()

        # TODO(jaypipes): Remove once the PCI tracker is always created
        # upon the resource tracker being initialized...
        self.rt.pci_tracker = pci_manager.PciDevTracker(mock.sentinel.ctx)

        pci_dev = pci_device.PciDevice.create(
            None, fake_pci_device.dev_dict)
        pci_devs = [pci_dev]
        self.rt.pci_tracker.pci_devs = objects.PciDeviceList(objects=pci_devs)
        pci_claim_mock.return_value = [pci_dev]

        # start with an empty dest compute node. No migrations, no instances
        get_mock.return_value = []
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0]

        self.rt.update_available_resource(mock.sentinel.ctx, _NODENAME)

        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.task_state = task_states.RESIZE_MIGRATING
        instance.new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        # A destination-only migration
        migration = objects.Migration(
            id=3,
            instance_uuid=instance.uuid,
            source_compute="other-host",
            dest_compute=_HOSTNAME,
            source_node="other-node",
            dest_node=_NODENAME,
            old_instance_type_id=1,
            new_instance_type_id=2,
            migration_type='resize',
            status='migrating',
            instance=instance,
        )
        mig_context_obj = objects.MigrationContext(
            instance_uuid=instance.uuid,
            migration_id=3,
            new_numa_topology=None,
            old_numa_topology=None,
        )
        instance.migration_context = mig_context_obj
        new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        request = objects.InstancePCIRequest(count=1,
            spec=[{'vendor_id': 'v', 'product_id': 'p'}])
        pci_requests = objects.InstancePCIRequests(
                requests=[request],
                instance_uuid=instance.uuid,
        )
        instance.pci_requests = pci_requests
        # NOTE(jaypipes): This looks weird, so let me explain. The Instance PCI
        # requests on a resize come from two places. The first is the PCI
        # information from the new flavor. The second is for SR-IOV devices
        # that are directly attached to the migrating instance. The
        # pci_req_mock.return value here is for the flavor PCI device requests
        # (which is nothing). This empty list will be merged with the Instance
        # PCI requests defined directly above.
        pci_req_mock.return_value = objects.InstancePCIRequests(requests=[])

        # not using mock.sentinel.ctx because resize_claim calls elevated
        ctx = mock.MagicMock()

        with test.nested(
            mock.patch('nova.pci.manager.PciDevTracker.allocate_instance'),
            mock.patch('nova.compute.resource_tracker.ResourceTracker'
                       '._create_migration',
                       return_value=migration),
            mock.patch('nova.objects.MigrationContext',
                       return_value=mig_context_obj),
            mock.patch('nova.objects.Instance.save'),
        ) as (alloc_mock, create_mig_mock, ctxt_mock, inst_save_mock):
            self.rt.resize_claim(ctx, instance, new_flavor, _NODENAME)

        pci_claim_mock.assert_called_once_with(ctx, pci_req_mock.return_value,
                                               None)
        # Validate that the pci.request.get_pci_request_from_flavor() return
        # value was merged with the instance PCI requests from the Instance
        # itself that represent the SR-IOV devices from the original host.
        pci_req_mock.assert_called_once_with(new_flavor)
        self.assertEqual(1, len(pci_req_mock.return_value.requests))
        self.assertEqual(request, pci_req_mock.return_value.requests[0])
        alloc_mock.assert_called_once_with(instance)

    @mock.patch('nova.scheduler.utils.resources_from_flavor')
    def test_drop_move_claim_on_revert(self, mock_resources):
        self._setup_rt()
        cn = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        self.rt.compute_nodes[_NODENAME] = cn

        # TODO(jaypipes): Remove once the PCI tracker is always created
        # upon the resource tracker being initialized...
        self.rt.pci_tracker = pci_manager.PciDevTracker(mock.sentinel.ctx)

        pci_dev = pci_device.PciDevice.create(
            None, fake_pci_device.dev_dict)
        pci_devs = [pci_dev]

        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.task_state = task_states.RESIZE_MIGRATING
        instance.flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]
        instance.migration_context = objects.MigrationContext()
        instance.migration_context.new_pci_devices = objects.PciDeviceList(
            objects=pci_devs)

        self.rt.tracked_instances = {
            instance.uuid: obj_base.obj_to_primitive(instance)
        }

        # not using mock.sentinel.ctx because drop_move_claim calls elevated
        ctx = mock.MagicMock()

        with test.nested(
            mock.patch.object(self.rt, '_update'),
            mock.patch.object(self.rt.pci_tracker, 'free_device')
            ) as (update_mock, mock_pci_free_device):
                self.rt.drop_move_claim(ctx, instance, _NODENAME)
                mock_pci_free_device.assert_called_once_with(
                    pci_dev, mock.ANY)
                # Check that we grabbed resourced for the right flavor...
                mock_resources.assert_called_once_with(instance,
                    instance.flavor)

    @mock.patch('nova.objects.Service.get_minimum_version',
                return_value=22)
    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_resize_claim_two_instances(self, save_mock, get_mock, migr_mock,
            get_cn_mock, pci_mock, instance_pci_mock, version_mock):
        # Issue two resize claims against a destination host with no prior
        # instances on it and validate that the accounting for resources is
        # correct.
        self.flags(reserved_host_disk_mb=0,
                   reserved_host_memory_mb=0)
        self._setup_rt()

        get_mock.return_value = []
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0].obj_clone()

        self.rt.update_available_resource(mock.sentinel.ctx, _NODENAME)

        # Instance #1 is resizing to instance type 2 which has 2 vCPUs, 256MB
        # RAM and 5GB root disk.
        instance1 = _INSTANCE_FIXTURES[0].obj_clone()
        instance1.id = 1
        instance1.uuid = uuids.instance1
        instance1.task_state = task_states.RESIZE_MIGRATING
        instance1.new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        migration1 = objects.Migration(
            id=1,
            instance_uuid=instance1.uuid,
            source_compute="other-host",
            dest_compute=_HOSTNAME,
            source_node="other-node",
            dest_node=_NODENAME,
            old_instance_type_id=1,
            new_instance_type_id=2,
            migration_type='resize',
            status='migrating',
            instance=instance1,
        )
        mig_context_obj1 = objects.MigrationContext(
            instance_uuid=instance1.uuid,
            migration_id=1,
            new_numa_topology=None,
            old_numa_topology=None,
        )
        instance1.migration_context = mig_context_obj1
        flavor1 = _INSTANCE_TYPE_OBJ_FIXTURES[2]

        # Instance #2 is resizing to instance type 1 which has 1 vCPU, 128MB
        # RAM and 1GB root disk.
        instance2 = _INSTANCE_FIXTURES[0].obj_clone()
        instance2.id = 2
        instance2.uuid = uuids.instance2
        instance2.task_state = task_states.RESIZE_MIGRATING
        instance2.old_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2]
        instance2.new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[1]

        migration2 = objects.Migration(
            id=2,
            instance_uuid=instance2.uuid,
            source_compute="other-host",
            dest_compute=_HOSTNAME,
            source_node="other-node",
            dest_node=_NODENAME,
            old_instance_type_id=2,
            new_instance_type_id=1,
            migration_type='resize',
            status='migrating',
            instance=instance1,
        )
        mig_context_obj2 = objects.MigrationContext(
            instance_uuid=instance2.uuid,
            migration_id=2,
            new_numa_topology=None,
            old_numa_topology=None,
        )
        instance2.migration_context = mig_context_obj2
        flavor2 = _INSTANCE_TYPE_OBJ_FIXTURES[1]

        expected = self.rt.compute_nodes[_NODENAME].obj_clone()
        expected.vcpus_used = (expected.vcpus_used +
                               flavor1.vcpus +
                               flavor2.vcpus)
        expected.memory_mb_used = (expected.memory_mb_used +
                                   flavor1.memory_mb +
                                   flavor2.memory_mb)
        expected.free_ram_mb = expected.memory_mb - expected.memory_mb_used
        expected.local_gb_used = (expected.local_gb_used +
                                 (flavor1.root_gb +
                                  flavor1.ephemeral_gb +
                                  flavor2.root_gb +
                                  flavor2.ephemeral_gb))
        expected.free_disk_gb = (expected.free_disk_gb -
                                (flavor1.root_gb +
                                 flavor1.ephemeral_gb +
                                 flavor2.root_gb +
                                 flavor2.ephemeral_gb))

        # not using mock.sentinel.ctx because resize_claim calls #elevated
        ctx = mock.MagicMock()

        with test.nested(
            mock.patch('nova.compute.resource_tracker.ResourceTracker'
                       '._create_migration',
                       side_effect=[migration1, migration2]),
            mock.patch('nova.objects.MigrationContext',
                       side_effect=[mig_context_obj1, mig_context_obj2]),
            mock.patch('nova.objects.Instance.save'),
        ) as (create_mig_mock, ctxt_mock, inst_save_mock):
            self.rt.resize_claim(ctx, instance1, flavor1, _NODENAME)
            self.rt.resize_claim(ctx, instance2, flavor2, _NODENAME)
        cn = self.rt.compute_nodes[_NODENAME]
        self.assertTrue(obj_base.obj_equal_prims(expected, cn))
        self.assertEqual(2, len(self.rt.tracked_migrations),
                         "Expected 2 tracked migrations but got %s"
                         % self.rt.tracked_migrations)


class TestRebuild(BaseTestCase):
    @mock.patch('nova.objects.Service.get_minimum_version',
                return_value=22)
    @mock.patch('nova.objects.InstancePCIRequests.get_by_instance',
                return_value=objects.InstancePCIRequests(requests=[]))
    @mock.patch('nova.objects.PciDeviceList.get_by_compute_node',
                return_value=objects.PciDeviceList())
    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename')
    @mock.patch('nova.objects.MigrationList.get_in_progress_by_host_and_node')
    @mock.patch('nova.objects.InstanceList.get_by_host_and_node')
    @mock.patch('nova.objects.ComputeNode.save')
    def test_rebuild_claim(self, save_mock, get_mock, migr_mock, get_cn_mock,
            pci_mock, instance_pci_mock, version_mock):
        # Rebuild an instance, emulating an evacuate command issued against the
        # original instance. The rebuild operation uses the resource tracker's
        # _move_claim() method, but unlike with resize_claim(), rebuild_claim()
        # passes in a pre-created Migration object from the destination compute
        # manager.
        self.flags(reserved_host_disk_mb=0,
                   reserved_host_memory_mb=0)

        # Starting state for the destination node of the rebuild claim is the
        # normal compute node fixture containing a single active running VM
        # having instance type #1.
        virt_resources = copy.deepcopy(_VIRT_DRIVER_AVAIL_RESOURCES)
        virt_resources.update(vcpus_used=1,
                              memory_mb_used=128,
                              local_gb_used=1)
        self._setup_rt(virt_resources=virt_resources)

        get_mock.return_value = _INSTANCE_FIXTURES
        migr_mock.return_value = []
        get_cn_mock.return_value = _COMPUTE_NODE_FIXTURES[0].obj_clone()

        self.rt.update_available_resource(mock.sentinel.ctx, _NODENAME)

        # Now emulate the evacuate command by calling rebuild_claim() on the
        # resource tracker as the compute manager does, supplying a Migration
        # object that corresponds to the evacuation.
        migration = objects.Migration(
            mock.sentinel.ctx,
            id=1,
            instance_uuid=uuids.rebuilding_instance,
            source_compute='fake-other-compute',
            source_node='fake-other-node',
            status='accepted',
            migration_type='evacuation'
        )
        instance = objects.Instance(
            id=1,
            host=None,
            node=None,
            uuid='abef5b54-dea6-47b8-acb2-22aeb1b57919',
            memory_mb=_INSTANCE_TYPE_FIXTURES[2]['memory_mb'],
            vcpus=_INSTANCE_TYPE_FIXTURES[2]['vcpus'],
            root_gb=_INSTANCE_TYPE_FIXTURES[2]['root_gb'],
            ephemeral_gb=_INSTANCE_TYPE_FIXTURES[2]['ephemeral_gb'],
            numa_topology=None,
            pci_requests=None,
            pci_devices=None,
            instance_type_id=2,
            vm_state=vm_states.ACTIVE,
            power_state=power_state.RUNNING,
            task_state=task_states.REBUILDING,
            os_type='fake-os',
            project_id='fake-project',
            flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2],
            old_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2],
            new_flavor = _INSTANCE_TYPE_OBJ_FIXTURES[2],
        )

        # not using mock.sentinel.ctx because resize_claim calls #elevated
        ctx = mock.MagicMock()

        with test.nested(
            mock.patch('nova.objects.Migration.save'),
            mock.patch('nova.objects.Instance.save'),
        ) as (mig_save_mock, inst_save_mock):
            self.rt.rebuild_claim(ctx, instance, _NODENAME,
                                  migration=migration)

        self.assertEqual(_HOSTNAME, migration.dest_compute)
        self.assertEqual(_NODENAME, migration.dest_node)
        self.assertEqual("pre-migrating", migration.status)
        self.assertEqual(1, len(self.rt.tracked_migrations))
        mig_save_mock.assert_called_once_with()
        inst_save_mock.assert_called_once_with()


class TestUpdateUsageFromMigration(test.NoDBTestCase):
    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_get_instance_type')
    def test_unsupported_move_type(self, get_mock):
        rt = resource_tracker.ResourceTracker(mock.sentinel.virt_driver,
                                              _HOSTNAME)
        migration = objects.Migration(migration_type='live-migration')
        # For same-node migrations, the RT's _get_instance_type() method is
        # called if there is a migration that is trackable. Here, we want to
        # ensure that this method isn't called for live-migration migrations.
        rt._update_usage_from_migration(mock.sentinel.ctx,
                                        mock.sentinel.instance,
                                        migration,
                                        _NODENAME)
        self.assertFalse(get_mock.called)


class TestUpdateUsageFromMigrations(BaseTestCase):
    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage_from_migration')
    def test_no_migrations(self, mock_update_usage):
        migrations = []
        self._setup_rt()
        self.rt._update_usage_from_migrations(mock.sentinel.ctx, migrations,
                                              _NODENAME)
        self.assertFalse(mock_update_usage.called)

    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage_from_migration')
    @mock.patch('nova.objects.instance.Instance.get_by_uuid')
    def test_instance_not_found(self, mock_get_instance, mock_update_usage):
        mock_get_instance.side_effect = exc.InstanceNotFound(
            instance_id='some_id',
        )
        migration = objects.Migration(
            context=mock.sentinel.ctx,
            instance_uuid='some_uuid',
        )
        self._setup_rt()
        self.rt._update_usage_from_migrations(mock.sentinel.ctx, [migration],
                                              _NODENAME)
        mock_get_instance.assert_called_once_with(mock.sentinel.ctx,
                                                  'some_uuid')
        self.assertFalse(mock_update_usage.called)

    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage_from_migration')
    def test_duplicate_migrations_filtered(self, upd_mock):
        # The wrapper function _update_usage_from_migrations() looks at the
        # list of migration objects returned from
        # MigrationList.get_in_progress_by_host_and_node() and ensures that
        # only the most recent migration record for an instance is used in
        # determining the usage records. Here we pass multiple migration
        # objects for a single instance and ensure that we only call the
        # _update_usage_from_migration() (note: not migration*s*...) once with
        # the migration object with greatest updated_at value. We also pass
        # some None values for various updated_at attributes to exercise some
        # of the code paths in the filtering logic.
        self._setup_rt()

        instance = objects.Instance(vm_state=vm_states.RESIZED,
                                    task_state=None)
        ts1 = timeutils.utcnow()
        ts0 = ts1 - datetime.timedelta(seconds=10)
        ts2 = ts1 + datetime.timedelta(seconds=10)

        migrations = [
            objects.Migration(source_compute=_HOSTNAME,
                              source_node=_NODENAME,
                              dest_compute=_HOSTNAME,
                              dest_node=_NODENAME,
                              instance_uuid=uuids.instance,
                              created_at=ts0,
                              updated_at=ts1,
                              instance=instance),
            objects.Migration(source_compute=_HOSTNAME,
                              source_node=_NODENAME,
                              dest_compute=_HOSTNAME,
                              dest_node=_NODENAME,
                              instance_uuid=uuids.instance,
                              created_at=ts0,
                              updated_at=ts2,
                              instance=instance)
        ]
        mig1, mig2 = migrations
        mig_list = objects.MigrationList(objects=migrations)
        self.rt._update_usage_from_migrations(mock.sentinel.ctx, mig_list,
                                              _NODENAME)
        upd_mock.assert_called_once_with(mock.sentinel.ctx, instance, mig2,
                                         _NODENAME)

        upd_mock.reset_mock()
        mig2.updated_at = None
        self.rt._update_usage_from_migrations(mock.sentinel.ctx, mig_list,
                _NODENAME)
        upd_mock.assert_called_once_with(mock.sentinel.ctx, instance, mig1,
                _NODENAME)


class TestUpdateUsageFromInstance(BaseTestCase):

    def setUp(self):
        super(TestUpdateUsageFromInstance, self).setUp()
        self._setup_rt()
        cn = _COMPUTE_NODE_FIXTURES[0].obj_clone()
        self.rt.compute_nodes[_NODENAME] = cn
        self.instance = _INSTANCE_FIXTURES[0].obj_clone()

    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage')
    def test_building(self, mock_update_usage):
        self.instance.vm_state = vm_states.BUILDING
        self.rt._update_usage_from_instance(mock.sentinel.ctx, self.instance,
                                            _NODENAME)

        mock_update_usage.assert_called_once_with(
            self.rt._get_usage_dict(self.instance), _NODENAME, sign=1)

    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage')
    def test_shelve_offloading(self, mock_update_usage):
        self.instance.vm_state = vm_states.SHELVED_OFFLOADED
        self.rt.tracked_instances = {
            self.instance.uuid: obj_base.obj_to_primitive(self.instance)
        }
        self.rt._update_usage_from_instance(mock.sentinel.ctx, self.instance,
                                            _NODENAME)

        mock_update_usage.assert_called_once_with(
            self.rt._get_usage_dict(self.instance), _NODENAME, sign=-1)

    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage')
    def test_unshelving(self, mock_update_usage):
        self.instance.vm_state = vm_states.SHELVED_OFFLOADED
        self.rt._update_usage_from_instance(mock.sentinel.ctx, self.instance,
                                            _NODENAME)

        mock_update_usage.assert_called_once_with(
            self.rt._get_usage_dict(self.instance), _NODENAME, sign=1)

    @mock.patch('nova.compute.resource_tracker.ResourceTracker.'
                '_update_usage')
    def test_deleted(self, mock_update_usage):
        self.instance.vm_state = vm_states.DELETED
        self.rt.tracked_instances = {
                self.instance.uuid: obj_base.obj_to_primitive(self.instance)
        }
        self.rt._update_usage_from_instance(mock.sentinel.ctx,
                                            self.instance, _NODENAME, True)

        mock_update_usage.assert_called_once_with(
            self.rt._get_usage_dict(self.instance), _NODENAME, sign=-1)

    @mock.patch('nova.objects.Instance.get_by_uuid')
    def test_remove_deleted_instances_allocations_deleted_instance(self,
            mock_inst_get):
        rc = self.rt.reportclient
        self.rt.tracked_instances = {}
        allocs = {uuids.deleted: "fake_deleted_instance"}
        rc.get_allocations_for_resource_provider = mock.MagicMock(
            return_value=allocs)
        rc.delete_allocation_for_instance = mock.MagicMock()
        mock_inst_get.side_effect = exc.InstanceNotFound(
            instance_id=uuids.deleted)
        cn = self.rt.compute_nodes[_NODENAME]
        ctx = mock.sentinel.ctx
        # Call the method.
        self.rt._remove_deleted_instances_allocations(ctx, cn)
        # Only one call should be made to delete allocations, and that should
        # be for the first instance created above
        rc.delete_allocation_for_instance.assert_called_once_with(
            uuids.deleted)

    @mock.patch('nova.objects.Instance.get_by_uuid')
    def test_remove_deleted_instances_allocations_scheduled_instance(self,
            mock_inst_get):
        rc = self.rt.reportclient
        self.rt.tracked_instances = {}
        allocs = {uuids.scheduled: "fake_scheduled_instance"}
        rc.get_allocations_for_resource_provider = mock.MagicMock(
            return_value=allocs)
        rc.delete_allocation_for_instance = mock.MagicMock()

        def get_by_uuid(ctx, inst_uuid, expected_attrs=None):
            ret = _INSTANCE_FIXTURES[0].obj_clone()
            ret.uuid = inst_uuid
            ret.host = None  # This indicates the instance is being scheduled
            return ret

        mock_inst_get.side_effect = get_by_uuid
        cn = self.rt.compute_nodes[_NODENAME]
        ctx = mock.sentinel.ctx
        # Call the method.
        self.rt._remove_deleted_instances_allocations(ctx, cn)
        # Scheduled instances should not have their allocations removed
        rc.delete_allocation_for_instance.assert_not_called()

    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'get_allocations_for_resource_provider')
    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
                'delete_allocation_for_instance')
    @mock.patch('nova.objects.Instance.get_by_uuid')
    def test_remove_deleted_instances_allocations_move_ops(self, mock_get,
            mock_delete_allocs, mock_get_allocs):
        """Test that we do NOT delete allocations for instances that are
        currently undergoing move operations.
        """
        self.rt.tracked_instances = {}
        # Create 1 instance
        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.uuid = uuids.moving_instance
        instance.host = uuids.destination
        # Instances in resizing/move will be ACTIVE or STOPPED
        instance.vm_state = vm_states.ACTIVE
        # Mock out the allocation call
        allocs = {uuids.inst0: mock.sentinel.moving_instance}
        mock_get_allocs.return_value = allocs
        mock_get.return_value = instance

        cn = self.rt.compute_nodes[_NODENAME]
        ctx = mock.sentinel.ctx
        self.rt._remove_deleted_instances_allocations(ctx, cn)
        mock_delete_allocs.assert_not_called()

    @mock.patch('nova.objects.Instance.get_by_uuid')
    def test_remove_deleted_instances_allocations_no_instance(self,
            mock_inst_get):
        # If for some reason an instance is no longer available, but
        # there are allocations for it, we want to be sure those
        # allocations are removed, not that an InstanceNotFound
        # exception is not caught.  Here we set up some allocations,
        # one of which is for an instance that can no longer be
        # found.
        rc = self.rt.reportclient
        self.rt.tracked_instances = {}
        # Create 1 instance
        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.uuid = uuids.inst0
        # Mock out the allocation call
        allocs = {uuids.scheduled: "fake_scheduled_instance",
                  uuids.inst0: "fake_instance_gone"}
        rc.get_allocations_for_resource_provider = mock.MagicMock(
            return_value=allocs)
        rc.delete_allocation_for_instance = mock.MagicMock()

        def get_by_uuid(ctx, inst_uuid, expected_attrs=None):
            ret = _INSTANCE_FIXTURES[0].obj_clone()
            ret.uuid = inst_uuid
            if inst_uuid == uuids.scheduled:
                ret.host = None
                return ret
            raise exc.InstanceNotFound(instance_id=inst_uuid)

        mock_inst_get.side_effect = get_by_uuid
        cn = self.rt.compute_nodes[_NODENAME]
        ctx = mock.sentinel.ctx
        # Call the method.
        self.rt._remove_deleted_instances_allocations(ctx, cn)
        # One call should be made to delete allocations, for our
        # instance that no longer exists.
        rc.delete_allocation_for_instance.assert_called_once_with(uuids.inst0)

    def test_delete_allocation_for_shelve_offloaded_instance(self):
        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.uuid = uuids.inst0

        self.rt.delete_allocation_for_shelve_offloaded_instance(instance)

        rc = self.rt.reportclient
        mock_remove_allocation = rc.delete_allocation_for_instance
        mock_remove_allocation.assert_called_once_with(instance.uuid)

    def test_update_usage_from_instances_goes_negative(self):
        # NOTE(danms): The resource tracker _should_ report negative resources
        # for things like free_ram_mb if overcommit is being used. This test
        # ensures that we don't collapse negative values to zero.
        self.flags(reserved_host_memory_mb=2048)
        self.flags(reserved_host_disk_mb=(11 * 1024))
        cn = objects.ComputeNode(memory_mb=1024, local_gb=10)
        self.rt.compute_nodes['foo'] = cn

        @mock.patch.object(self.rt,
                           '_remove_deleted_instances_allocations')
        @mock.patch.object(self.rt, '_update_usage_from_instance')
        @mock.patch('nova.objects.Service.get_minimum_version',
                    return_value=22)
        def test(version_mock, uufi, rdia):
            self.rt._update_usage_from_instances('ctxt', [], 'foo')

        test()

        self.assertEqual(-1024, cn.free_ram_mb)
        self.assertEqual(-1, cn.free_disk_gb)

    @mock.patch('nova.scheduler.utils.resources_from_flavor')
    def test_delete_allocation_for_evacuated_instance(
            self, mock_resource_from_flavor):
        mock_resource = mock.Mock()
        mock_resource_from_flavor.return_value = mock_resource
        instance = _INSTANCE_FIXTURES[0].obj_clone()
        instance.uuid = uuids.inst0

        self.rt.delete_allocation_for_evacuated_instance(instance, _NODENAME)

        rc = self.rt.reportclient
        mock_remove_allocation = rc.remove_provider_from_instance_allocation
        mock_remove_allocation.assert_called_once_with(
            instance.uuid, self.rt.compute_nodes[_NODENAME].uuid,
            instance.user_id, instance.project_id, mock_resource)


class TestInstanceInResizeState(test.NoDBTestCase):
    def test_active_suspending(self):
        instance = objects.Instance(vm_state=vm_states.ACTIVE,
                                    task_state=task_states.SUSPENDING)
        self.assertFalse(resource_tracker._instance_in_resize_state(instance))

    def test_resized_suspending(self):
        instance = objects.Instance(vm_state=vm_states.RESIZED,
                                    task_state=task_states.SUSPENDING)
        self.assertTrue(resource_tracker._instance_in_resize_state(instance))

    def test_resized_resize_migrating(self):
        instance = objects.Instance(vm_state=vm_states.RESIZED,
                                    task_state=task_states.RESIZE_MIGRATING)
        self.assertTrue(resource_tracker._instance_in_resize_state(instance))

    def test_resized_resize_finish(self):
        instance = objects.Instance(vm_state=vm_states.RESIZED,
                                    task_state=task_states.RESIZE_FINISH)
        self.assertTrue(resource_tracker._instance_in_resize_state(instance))


class TestSetInstanceHostAndNode(BaseTestCase):

    def setUp(self):
        super(TestSetInstanceHostAndNode, self).setUp()
        self._setup_rt()

    @mock.patch('nova.objects.Instance.save')
    def test_set_instance_host_and_node(self, save_mock):
        inst = objects.Instance()
        self.rt._set_instance_host_and_node(inst, _NODENAME)
        save_mock.assert_called_once_with()
        self.assertEqual(self.rt.host, inst.host)
        self.assertEqual(_NODENAME, inst.node)
        self.assertEqual(self.rt.host, inst.launched_on)

    @mock.patch('nova.objects.Instance.save')
    def test_unset_instance_host_and_node(self, save_mock):
        inst = objects.Instance()
        self.rt._set_instance_host_and_node(inst, _NODENAME)
        self.rt._unset_instance_host_and_node(inst)
        self.assertEqual(2, save_mock.call_count)
        self.assertIsNone(inst.host)
        self.assertIsNone(inst.node)
        self.assertEqual(self.rt.host, inst.launched_on)


def _update_compute_node(node, **kwargs):
    for key, value in kwargs.items():
        setattr(node, key, value)


class ComputeMonitorTestCase(BaseTestCase):
    def setUp(self):
        super(ComputeMonitorTestCase, self).setUp()
        self._setup_rt()
        self.info = {}
        self.context = context.RequestContext(mock.sentinel.user_id,
                                              mock.sentinel.project_id)

    def test_get_host_metrics_none(self):
        self.rt.monitors = []
        metrics = self.rt._get_host_metrics(self.context, _NODENAME)
        self.assertEqual(len(metrics), 0)

    @mock.patch.object(resource_tracker.LOG, 'warning')
    def test_get_host_metrics_exception(self, mock_LOG_warning):
        monitor = mock.MagicMock()
        monitor.populate_metrics.side_effect = Exception
        self.rt.monitors = [monitor]
        metrics = self.rt._get_host_metrics(self.context, _NODENAME)
        mock_LOG_warning.assert_called_once_with(
            u'Cannot get the metrics from %(mon)s; error: %(exc)s', mock.ANY)
        self.assertEqual(0, len(metrics))

    @mock.patch('nova.rpc.get_notifier')
    def test_get_host_metrics(self, rpc_mock):
        class FakeCPUMonitor(monitor_base.MonitorBase):

            NOW_TS = timeutils.utcnow()

            def __init__(self, *args):
                super(FakeCPUMonitor, self).__init__(*args)
                self.source = 'FakeCPUMonitor'

            def get_metric_names(self):
                return set(["cpu.frequency"])

            def populate_metrics(self, monitor_list):
                metric_object = objects.MonitorMetric()
                metric_object.name = 'cpu.frequency'
                metric_object.value = 100
                metric_object.timestamp = self.NOW_TS
                metric_object.source = self.source
                monitor_list.objects.append(metric_object)

        self.rt.monitors = [FakeCPUMonitor(None)]

        metrics = self.rt._get_host_metrics(self.context, _NODENAME)
        rpc_mock.assert_called_once_with(service='compute', host=_NODENAME)

        expected_metrics = [
            {
                'timestamp': FakeCPUMonitor.NOW_TS.isoformat(),
                'name': 'cpu.frequency',
                'value': 100,
                'source': 'FakeCPUMonitor'
            },
        ]

        payload = {
            'metrics': expected_metrics,
            'host': _HOSTNAME,
            'host_ip': '1.1.1.1',
            'nodename': _NODENAME,
        }

        rpc_mock.return_value.info.assert_called_once_with(
            self.context, 'compute.metrics.update', payload)

        self.assertEqual(metrics, expected_metrics)


class TestIsTrackableMigration(test.NoDBTestCase):
    def test_true(self):
        mig = objects.Migration()
        for mig_type in ('resize', 'migration', 'evacuation'):
            mig.migration_type = mig_type

            self.assertTrue(resource_tracker._is_trackable_migration(mig))

    def test_false(self):
        mig = objects.Migration()
        for mig_type in ('live-migration',):
            mig.migration_type = mig_type

            self.assertFalse(resource_tracker._is_trackable_migration(mig))


class OverCommitTestCase(BaseTestCase):
    def test_cpu_allocation_ratio_none_negative(self):
        self.assertRaises(ValueError,
                          CONF.set_default, 'cpu_allocation_ratio', -1.0)

    def test_ram_allocation_ratio_none_negative(self):
        self.assertRaises(ValueError,
                          CONF.set_default, 'ram_allocation_ratio', -1.0)

    def test_disk_allocation_ratio_none_negative(self):
        self.assertRaises(ValueError,
                          CONF.set_default, 'disk_allocation_ratio', -1.0)
