#    Copyright 2013 IBM Corp.
#
#    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
from unittest import mock

import netaddr
from oslo_db import exception as db_exc
from oslo_serialization import jsonutils
from oslo_utils.fixture import uuidsentinel
from oslo_utils import timeutils
from oslo_versionedobjects import base as ovo_base
from oslo_versionedobjects import exception as ovo_exc

from nova import conf
from nova.db.main import api as db
from nova import exception
from nova import objects
from nova.objects import base
from nova.objects import compute_node
from nova.objects import hv_spec
from nova.objects import service
from nova.tests.unit import fake_pci_device_pools
from nova.tests.unit.objects import test_objects


NOW = timeutils.utcnow().replace(microsecond=0)
fake_stats = {'num_foo': '10'}
fake_stats_db_format = jsonutils.dumps(fake_stats)
# host_ip is coerced from a string to an IPAddress
# but needs to be converted to a string for the database format
fake_host_ip = '127.0.0.1'
fake_numa_topology = objects.NUMATopology(cells=[
    objects.NUMACell(
        id=0,
        cpuset=set([1, 2]),
        pcpuset=set(),
        memory=512,
        cpu_usage=0,
        memory_usage=0,
        mempages=[],
        pinned_cpus=set(),
        siblings=[set([1]), set([2])]),
    objects.NUMACell(
        id=1,
        cpuset=set([3, 4]),
        pcpuset=set(),
        memory=512,
        cpu_usage=0,
        memory_usage=0,
        mempages=[],
        pinned_cpus=set(),
        siblings=[set([3]), set([4])])])
fake_numa_topology_db_format = fake_numa_topology._to_json()
fake_supported_instances = [('x86_64', 'kvm', 'hvm')]
fake_hv_spec = hv_spec.HVSpec(arch=fake_supported_instances[0][0],
                              hv_type=fake_supported_instances[0][1],
                              vm_mode=fake_supported_instances[0][2])
fake_supported_hv_specs = [fake_hv_spec]
# for backward compatibility, each supported instance object
# is stored as a list in the database
fake_supported_hv_specs_db_format = jsonutils.dumps([fake_hv_spec.to_list()])
fake_pci = jsonutils.dumps(fake_pci_device_pools.fake_pool_list_primitive)
fake_compute_node = {
    'created_at': NOW,
    'updated_at': None,
    'deleted_at': None,
    'deleted': False,
    'id': 123,
    'uuid': uuidsentinel.fake_compute_node,
    'service_id': None,
    'host': 'fake',
    'vcpus': 4,
    'memory_mb': 4096,
    'local_gb': 1024,
    'vcpus_used': 2,
    'memory_mb_used': 2048,
    'local_gb_used': 512,
    'hypervisor_type': 'Hyper-Dan-VM-ware',
    'hypervisor_version': 1001,
    'hypervisor_hostname': 'vm.danplanet.com',
    'free_ram_mb': 1024,
    'free_disk_gb': 256,
    'current_workload': 100,
    'running_vms': 2013,
    'cpu_info': 'Schmintel i786',
    'disk_available_least': 256,
    'metrics': '',
    'stats': fake_stats_db_format,
    'host_ip': fake_host_ip,
    'numa_topology': fake_numa_topology_db_format,
    'supported_instances': fake_supported_hv_specs_db_format,
    'pci_stats': fake_pci,
    'cpu_allocation_ratio': 16.0,
    'ram_allocation_ratio': 1.5,
    'disk_allocation_ratio': 1.0,
    'mapped': 0,
    }
# FIXME(sbauza) : For compatibility checking, to be removed once we are sure
# that all computes are running latest DB version with host field in it.
fake_old_compute_node = fake_compute_node.copy()
del fake_old_compute_node['host']
# resources are passed from the virt drivers and copied into the compute_node
fake_resources = {
    'vcpus': 2,
    'memory_mb': 1024,
    'local_gb': 10,
    'cpu_info': 'fake-info',
    'vcpus_used': 1,
    'memory_mb_used': 512,
    'local_gb_used': 4,
    'numa_topology': fake_numa_topology_db_format,
    'hypervisor_type': 'fake-type',
    'hypervisor_version': 1,
    'hypervisor_hostname': 'fake-host',
    'disk_available_least': 256,
    'host_ip': fake_host_ip,
    'supported_instances': fake_supported_instances
}
fake_compute_with_resources = objects.ComputeNode(
    vcpus=fake_resources['vcpus'],
    memory_mb=fake_resources['memory_mb'],
    local_gb=fake_resources['local_gb'],
    cpu_info=fake_resources['cpu_info'],
    vcpus_used=fake_resources['vcpus_used'],
    memory_mb_used=fake_resources['memory_mb_used'],
    local_gb_used =fake_resources['local_gb_used'],
    numa_topology=fake_resources['numa_topology'],
    hypervisor_type=fake_resources['hypervisor_type'],
    hypervisor_version=fake_resources['hypervisor_version'],
    hypervisor_hostname=fake_resources['hypervisor_hostname'],
    disk_available_least=fake_resources['disk_available_least'],
    host_ip=netaddr.IPAddress(fake_resources['host_ip']),
    supported_hv_specs=fake_supported_hv_specs,
)

CONF = conf.CONF


class _TestComputeNodeObject(object):
    def supported_hv_specs_comparator(self, expected, obj_val):
        obj_val = [inst.to_list() for inst in obj_val]
        self.assertJsonEqual(expected, obj_val)

    def pci_device_pools_comparator(self, expected, obj_val):
        if obj_val is not None:
            obj_val = obj_val.obj_to_primitive()
            self.assertJsonEqual(expected, obj_val)
        else:
            self.assertEqual(expected, obj_val)

    def comparators(self):
        return {'stats': self.assertJsonEqual,
                'host_ip': self.str_comparator,
                'supported_hv_specs': self.supported_hv_specs_comparator,
                'pci_device_pools': self.pci_device_pools_comparator,
                }

    def subs(self):
        return {'supported_hv_specs': 'supported_instances',
                'pci_device_pools': 'pci_stats'}

    @mock.patch.object(db, 'compute_node_get')
    def test_get_by_id(self, get_mock):
        get_mock.return_value = fake_compute_node
        compute = compute_node.ComputeNode.get_by_id(self.context, 123)
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        self.assertNotIn('uuid', compute.obj_what_changed())
        get_mock.assert_called_once_with(self.context, 123)

    @mock.patch.object(compute_node.ComputeNodeList, 'get_all_by_uuids')
    def test_get_by_uuid(self, get_all_by_uuids):
        fake_node = copy.copy(fake_compute_node)
        fake_node['stats'] = None
        get_all_by_uuids.return_value = objects.ComputeNodeList(
            objects=[objects.ComputeNode(**fake_node)])
        compute = compute_node.ComputeNode.get_by_uuid(
            self.context, uuidsentinel.fake_compute_node)
        self.assertEqual(uuidsentinel.fake_compute_node, compute.uuid)
        get_all_by_uuids.assert_called_once_with(
            self.context, [uuidsentinel.fake_compute_node])

    @mock.patch.object(compute_node.ComputeNodeList, 'get_all_by_uuids')
    def test_get_by_uuid_not_found(self, get_all_by_uuids):
        get_all_by_uuids.return_value = objects.ComputeNodeList()
        self.assertRaises(exception.ComputeHostNotFound,
                          compute_node.ComputeNode.get_by_uuid,
                          self.context, uuidsentinel.fake_compute_node)
        get_all_by_uuids.assert_called_once_with(
            self.context, [uuidsentinel.fake_compute_node])

    @mock.patch.object(db, 'compute_node_get')
    def test_get_without_mapped(self, get_mock):
        fake_node = copy.copy(fake_compute_node)
        fake_node['mapped'] = None
        get_mock.return_value = fake_node
        compute = compute_node.ComputeNode.get_by_id(self.context, 123)
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        self.assertIn('mapped', compute)
        self.assertEqual(0, compute.mapped)

    @mock.patch.object(objects.Service, 'get_by_id')
    @mock.patch.object(db, 'compute_node_get')
    def test_get_by_id_with_host_field_not_in_db(self, mock_cn_get,
                                                 mock_obj_svc_get):
        fake_compute_node_with_svc_id = fake_compute_node.copy()
        fake_compute_node_with_svc_id['service_id'] = 123
        fake_compute_node_with_no_host = fake_compute_node_with_svc_id.copy()
        host = fake_compute_node_with_no_host.pop('host')
        fake_service = service.Service(id=123)
        fake_service.host = host

        mock_cn_get.return_value = fake_compute_node_with_no_host
        mock_obj_svc_get.return_value = fake_service

        compute = compute_node.ComputeNode.get_by_id(self.context, 123)
        self.compare_obj(compute, fake_compute_node_with_svc_id,
                         subs=self.subs(),
                         comparators=self.comparators())

    @mock.patch.object(db, 'compute_nodes_get_by_service_id')
    def test_get_by_service_id(self, get_mock):
        get_mock.return_value = [fake_compute_node]
        compute = compute_node.ComputeNode.get_by_service_id(self.context, 456)
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        get_mock.assert_called_once_with(self.context, 456)

    @mock.patch.object(db, 'compute_node_get_by_host_and_nodename')
    def test_get_by_host_and_nodename(self, cn_get_by_h_and_n):
        cn_get_by_h_and_n.return_value = fake_compute_node

        compute = compute_node.ComputeNode.get_by_host_and_nodename(
            self.context, 'fake', 'vm.danplanet.com')
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())

    @mock.patch.object(db, 'compute_node_get_by_nodename')
    def test_get_by_nodename(self, cn_get_by_n):
        cn_get_by_n.return_value = fake_compute_node

        compute = compute_node.ComputeNode.get_by_nodename(
            self.context, 'vm.danplanet.com')
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())

    @mock.patch('nova.db.main.api.compute_node_get_all_by_host')
    def test_get_first_node_by_host_for_old_compat(
            self, cn_get_all_by_host):
        another_node = fake_compute_node.copy()
        another_node['hypervisor_hostname'] = 'neverland'
        cn_get_all_by_host.return_value = [fake_compute_node, another_node]

        compute = (
            compute_node.ComputeNode.get_first_node_by_host_for_old_compat(
                self.context, 'fake')
        )
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())

    @mock.patch('nova.objects.ComputeNodeList.get_all_by_host')
    def test_get_first_node_by_host_for_old_compat_not_found(
            self, cn_get_all_by_host):
        cn_get_all_by_host.side_effect = exception.ComputeHostNotFound(
            host='fake')

        self.assertRaises(
            exception.ComputeHostNotFound,
            compute_node.ComputeNode.get_first_node_by_host_for_old_compat,
            self.context, 'fake')

    @mock.patch.object(db, 'compute_node_create')
    @mock.patch(
        'nova.db.main.api.compute_node_get', return_value=fake_compute_node)
    def test_create(self, mock_get, mock_create):
        mock_create.return_value = fake_compute_node
        compute = compute_node.ComputeNode(context=self.context)
        compute.service_id = 456
        compute.uuid = uuidsentinel.fake_compute_node
        compute.stats = fake_stats
        # NOTE (pmurray): host_ip is coerced to an IPAddress
        compute.host_ip = fake_host_ip
        compute.supported_hv_specs = fake_supported_hv_specs
        with mock.patch('oslo_utils.uuidutils.generate_uuid') as mock_gu:
            compute.create()
            self.assertFalse(mock_gu.called)
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        param_dict = {
            'service_id': 456,
            'stats': fake_stats_db_format,
            'host_ip': fake_host_ip,
            'supported_instances': fake_supported_hv_specs_db_format,
            'uuid': uuidsentinel.fake_compute_node
        }
        mock_create.assert_called_once_with(self.context, param_dict)

    @mock.patch('nova.db.main.api.compute_node_create')
    @mock.patch('oslo_utils.uuidutils.generate_uuid')
    @mock.patch(
        'nova.db.main.api.compute_node_get', return_value=fake_compute_node)
    def test_create_allocates_uuid(self, mock_get, mock_gu, mock_create):
        mock_create.return_value = fake_compute_node
        mock_gu.return_value = fake_compute_node['uuid']
        obj = objects.ComputeNode(context=self.context)
        obj.create()
        mock_gu.assert_called_once_with()
        mock_create.assert_called_once_with(
            self.context, {'uuid': fake_compute_node['uuid']})

    @mock.patch('nova.db.main.api.compute_node_create')
    @mock.patch(
        'nova.db.main.api.compute_node_get', return_value=fake_compute_node)
    def test_recreate_fails(self, mock_get, mock_create):
        mock_create.return_value = fake_compute_node
        compute = compute_node.ComputeNode(context=self.context)
        compute.service_id = 456
        compute.uuid = uuidsentinel.fake_compute_node
        compute.create()
        self.assertRaises(exception.ObjectActionError, compute.create)
        param_dict = {'service_id': 456,
                      'uuid': uuidsentinel.fake_compute_node}
        mock_create.assert_called_once_with(self.context, param_dict)

    @mock.patch('nova.db.main.api.compute_node_create')
    def test_create_duplicate(self, mock_create):
        mock_create.side_effect = db_exc.DBDuplicateEntry
        compute = compute_node.ComputeNode(context=self.context)
        compute.service_id = 456
        compute.hypervisor_hostname = 'node1'
        self.assertRaises(exception.DuplicateRecord, compute.create)

    @mock.patch.object(db, 'compute_node_update')
    @mock.patch(
        'nova.db.main.api.compute_node_get', return_value=fake_compute_node)
    def test_save(self, mock_get, mock_update):
        mock_update.return_value = fake_compute_node
        compute = compute_node.ComputeNode(context=self.context)
        compute.id = 123
        compute.vcpus_used = 3
        compute.stats = fake_stats
        compute.uuid = uuidsentinel.fake_compute_node
        # NOTE (pmurray): host_ip is coerced to an IPAddress
        compute.host_ip = fake_host_ip
        compute.supported_hv_specs = fake_supported_hv_specs
        compute.save()
        self.compare_obj(compute, fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        param_dict = {
            'vcpus_used': 3,
            'stats': fake_stats_db_format,
            'host_ip': fake_host_ip,
            'supported_instances': fake_supported_hv_specs_db_format,
            'uuid': uuidsentinel.fake_compute_node,
        }
        mock_update.assert_called_once_with(self.context, 123, param_dict)

    @mock.patch('nova.db.main.api.compute_node_update')
    def test_save_pci_device_pools_empty(self, mock_update):
        fake_pci = jsonutils.dumps(
            objects.PciDevicePoolList(objects=[]).obj_to_primitive())
        compute_dict = fake_compute_node.copy()
        compute_dict['pci_stats'] = fake_pci
        mock_update.return_value = compute_dict

        compute = compute_node.ComputeNode(context=self.context)
        compute.id = 123
        compute.pci_device_pools = objects.PciDevicePoolList(objects=[])
        compute.save()
        self.compare_obj(compute, compute_dict,
                         subs=self.subs(),
                         comparators=self.comparators())

        mock_update.assert_called_once_with(
            self.context, 123, {'pci_stats': fake_pci})

    @mock.patch('nova.db.main.api.compute_node_update')
    def test_save_pci_device_pools_null(self, mock_update):
        compute_dict = fake_compute_node.copy()
        compute_dict['pci_stats'] = None
        mock_update.return_value = compute_dict

        compute = compute_node.ComputeNode(context=self.context)
        compute.id = 123
        compute.pci_device_pools = None
        compute.save()
        self.compare_obj(compute, compute_dict,
                         subs=self.subs(),
                         comparators=self.comparators())

        mock_update.assert_called_once_with(
            self.context, 123, {'pci_stats': None})

    @mock.patch.object(db, 'compute_node_create',
                       return_value=fake_compute_node)
    @mock.patch.object(db, 'compute_node_get',
                       return_value=fake_compute_node)
    def test_set_id_failure(self, mock_get, db_mock):
        compute = compute_node.ComputeNode(context=self.context,
                                           uuid=fake_compute_node['uuid'])
        compute.create()
        self.assertRaises(ovo_exc.ReadOnlyFieldError, setattr,
                          compute, 'id', 124)

    @mock.patch.object(db, 'compute_node_delete')
    def test_destroy(self, mock_delete):
        compute = compute_node.ComputeNode(context=self.context)
        compute.id = 123
        compute.destroy()
        mock_delete.assert_called_once_with(self.context, 123, constraint=None)

    def test_destroy_host_constraint(self):
        # Create compute node with host='fake'
        compute = fake_compute_with_resources.obj_clone()
        compute._context = self.context
        compute.host = 'fake'
        compute.create()
        # Simulate a compute node ownership change due to a node rebalance
        compute.host = 'different'
        self.assertRaises(exception.ObjectActionError, compute.destroy)

    @mock.patch.object(db, 'compute_node_get_all')
    def test_get_all(self, mock_get_all):
        mock_get_all.return_value = [fake_compute_node]
        computes = compute_node.ComputeNodeList.get_all(self.context)
        self.assertEqual(1, len(computes))
        self.compare_obj(computes[0], fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        mock_get_all.assert_called_once_with(self.context)

    @mock.patch.object(db, 'compute_node_search_by_hypervisor')
    def test_get_by_hypervisor(self, mock_search):
        mock_search.return_value = [fake_compute_node]
        computes = compute_node.ComputeNodeList.get_by_hypervisor(self.context,
                                                                  'hyper')
        self.assertEqual(1, len(computes))
        self.compare_obj(computes[0], fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())
        mock_search.assert_called_once_with(self.context, 'hyper')

    @mock.patch('nova.db.main.api.compute_node_get_all_by_pagination',
                return_value=[fake_compute_node])
    def test_get_by_pagination(self, fake_get_by_pagination):
        computes = compute_node.ComputeNodeList.get_by_pagination(
            self.context, limit=1, marker=1)
        self.assertEqual(1, len(computes))
        self.compare_obj(computes[0], fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())

    @mock.patch('nova.db.main.api.compute_nodes_get_by_service_id')
    def test__get_by_service(self, cn_get_by_svc_id):
        cn_get_by_svc_id.return_value = [fake_compute_node]
        computes = compute_node.ComputeNodeList._get_by_service(self.context,
                                                                123)
        self.assertEqual(1, len(computes))
        self.compare_obj(computes[0], fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())

    @mock.patch('nova.db.main.api.compute_node_get_all_by_host')
    def test_get_all_by_host(self, cn_get_all_by_host):
        cn_get_all_by_host.return_value = [fake_compute_node]
        computes = compute_node.ComputeNodeList.get_all_by_host(self.context,
                                                                'fake')
        self.assertEqual(1, len(computes))
        self.compare_obj(computes[0], fake_compute_node,
                         subs=self.subs(),
                         comparators=self.comparators())

    def test_compat_numa_topology(self):
        compute = compute_node.ComputeNode(numa_topology='fake-numa-topology')
        versions = ovo_base.obj_tree_get_versions('ComputeNode')
        primitive = compute.obj_to_primitive(target_version='1.4',
                                             version_manifest=versions)
        self.assertNotIn('numa_topology', primitive['nova_object.data'])

        primitive = compute.obj_to_primitive(target_version='1.5',
                                             version_manifest=versions)
        self.assertIn('numa_topology', primitive['nova_object.data'])

    def test_compat_supported_hv_specs(self):
        compute = compute_node.ComputeNode()
        compute.supported_hv_specs = fake_supported_hv_specs
        versions = ovo_base.obj_tree_get_versions('ComputeNode')
        primitive = compute.obj_to_primitive(target_version='1.5',
                                             version_manifest=versions)
        self.assertNotIn('supported_hv_specs', primitive['nova_object.data'])

        primitive = compute.obj_to_primitive(target_version='1.6',
                                             version_manifest=versions)
        self.assertIn('supported_hv_specs', primitive['nova_object.data'])

    @mock.patch('nova.objects.service.Service.get_by_compute_host')
    def test_compat_host(self, mock_get_compute):
        compute = compute_node.ComputeNode(host='fake-host')
        primitive = compute.obj_to_primitive(target_version='1.6')
        self.assertNotIn('host', primitive['nova_object.data'])

        primitive = compute.obj_to_primitive(target_version='1.7')
        self.assertIn('host', primitive['nova_object.data'])

    def test_compat_pci_device_pools(self):
        compute = compute_node.ComputeNode()
        compute.pci_device_pools = fake_pci_device_pools.fake_pool_list
        versions = ovo_base.obj_tree_get_versions('ComputeNode')
        primitive = compute.obj_to_primitive(target_version='1.8',
                                             version_manifest=versions)
        self.assertNotIn('pci_device_pools', primitive['nova_object.data'])

        primitive = compute.obj_to_primitive(target_version='1.9',
                                             version_manifest=versions)
        self.assertIn('pci_device_pools', primitive['nova_object.data'])

    @mock.patch('nova.objects.Service.get_by_compute_host')
    def test_compat_service_id(self, mock_get):
        mock_get.return_value = objects.Service(id=1)
        compute = objects.ComputeNode(host='fake-host', service_id=None)
        primitive = compute.obj_to_primitive(target_version='1.12')
        self.assertEqual(1, primitive['nova_object.data']['service_id'])

    @mock.patch('nova.objects.Service.get_by_compute_host')
    def test_compat_service_id_compute_host_not_found(self, mock_get):
        mock_get.side_effect = exception.ComputeHostNotFound(host='fake-host')
        compute = objects.ComputeNode(host='fake-host', service_id=None)
        primitive = compute.obj_to_primitive(target_version='1.12')
        self.assertEqual(-1, primitive['nova_object.data']['service_id'])

    def test_update_from_virt_driver(self):
        # copy in case the update has a side effect
        resources = copy.deepcopy(fake_resources)
        # Emulate the ironic driver which adds a uuid field.
        resources['uuid'] = uuidsentinel.node_uuid
        compute = compute_node.ComputeNode()
        compute.update_from_virt_driver(resources)
        expected = fake_compute_with_resources.obj_clone()
        expected.uuid = uuidsentinel.node_uuid
        self.assertTrue(base.obj_equal_prims(expected, compute))

    def test_update_from_virt_driver_uuid_already_set(self):
        """Tests update_from_virt_driver where the compute node object already
        has a uuid value so an error is raised.
        """
        # copy in case the update has a side effect
        resources = copy.deepcopy(fake_resources)
        # Emulate the ironic driver which adds a uuid field.
        resources['uuid'] = uuidsentinel.node_uuid
        compute = compute_node.ComputeNode(uuid=uuidsentinel.something_else)
        self.assertRaises(exception.InvalidNodeConfiguration,
            compute.update_from_virt_driver, resources)

    def test_update_from_virt_driver_missing_field(self):
        # NOTE(pmurray): update_from_virt_driver does not require
        # all fields to be present in resources. Validation of the
        # resources data structure would be done in a different method.
        resources = copy.deepcopy(fake_resources)
        del resources['vcpus']
        compute = compute_node.ComputeNode()
        compute.update_from_virt_driver(resources)
        expected = fake_compute_with_resources.obj_clone()
        del expected.vcpus
        self.assertTrue(base.obj_equal_prims(expected, compute))

    def test_update_from_virt_driver_extra_field(self):
        # copy in case the update has a side effect
        resources = copy.deepcopy(fake_resources)
        resources['extra_field'] = 'nonsense'
        compute = compute_node.ComputeNode()
        compute.update_from_virt_driver(resources)
        expected = fake_compute_with_resources
        self.assertTrue(base.obj_equal_prims(expected, compute))

    def test_update_from_virt_driver_bad_value(self):
        # copy in case the update has a side effect
        resources = copy.deepcopy(fake_resources)
        resources['vcpus'] = 'nonsense'
        compute = compute_node.ComputeNode()
        self.assertRaises(ValueError,
                          compute.update_from_virt_driver, resources)

    def test_compat_allocation_ratios(self):
        compute = compute_node.ComputeNode(
            cpu_allocation_ratio=1.0, ram_allocation_ratio=1.0)
        primitive = compute.obj_to_primitive(target_version='1.13')
        self.assertNotIn('cpu_allocation_ratio', primitive['nova_object.data'])
        self.assertNotIn('ram_allocation_ratio', primitive['nova_object.data'])

        primitive = compute.obj_to_primitive(target_version='1.14')
        self.assertIn('cpu_allocation_ratio', primitive['nova_object.data'])
        self.assertIn('ram_allocation_ratio', primitive['nova_object.data'])

    def test_compat_disk_allocation_ratio(self):
        compute = compute_node.ComputeNode(disk_allocation_ratio=1.0)
        primitive = compute.obj_to_primitive(target_version='1.15')
        self.assertNotIn(
            'disk_allocation_ratio', primitive['nova_object.data'])

        primitive = compute.obj_to_primitive(target_version='1.16')
        self.assertIn('disk_allocation_ratio', primitive['nova_object.data'])

    @mock.patch('nova.db.main.api.compute_node_update')
    def test_compat_allocation_ratios_old_compute(self, mock_update):
        """Tests the scenario that allocation ratios are overridden in config
        and the legacy compute node record from the database has None set for
        the allocation ratio values. The result is that the migrated record
        allocation ratios should reflect the config overrides.
        """
        self.flags(cpu_allocation_ratio=2.0, ram_allocation_ratio=3.0,
                   disk_allocation_ratio=0.9)
        compute_dict = fake_compute_node.copy()
        # old computes don't provide allocation ratios to the table
        compute_dict['cpu_allocation_ratio'] = None
        compute_dict['ram_allocation_ratio'] = None
        compute_dict['disk_allocation_ratio'] = None
        cls = objects.ComputeNode
        compute = cls._from_db_object(self.context, cls(), compute_dict)

        self.assertEqual(2.0, compute.cpu_allocation_ratio)
        self.assertEqual(3.0, compute.ram_allocation_ratio)
        self.assertEqual(0.9, compute.disk_allocation_ratio)

        mock_update.assert_called_once_with(
            self.context, 123, {'cpu_allocation_ratio': 2.0,
                                'ram_allocation_ratio': 3.0,
                                'disk_allocation_ratio': 0.9})

    @mock.patch('nova.db.main.api.compute_node_update')
    def test_compat_allocation_ratios_zero_conf(self, mock_update):
        """Tests that the override allocation ratios are set to 0.0 for
        whatever reason (maybe an old nova.conf sample file is being used)
        and the legacy compute node record has None for allocation ratios,
        so the resulting data migration makes the record allocation ratios
        use the CONF.initial_*_allocation_ratio values.
        """
        self.flags(cpu_allocation_ratio=0.0, ram_allocation_ratio=0.0,
                   disk_allocation_ratio=0.0)
        compute_dict = fake_compute_node.copy()
        # the computes provide allocation ratios None
        compute_dict['cpu_allocation_ratio'] = None
        compute_dict['ram_allocation_ratio'] = None
        compute_dict['disk_allocation_ratio'] = None
        cls = objects.ComputeNode
        compute = cls._from_db_object(self.context, cls(), compute_dict)

        self.assertEqual(
            CONF.initial_cpu_allocation_ratio, compute.cpu_allocation_ratio)
        self.assertEqual(
            CONF.initial_ram_allocation_ratio, compute.ram_allocation_ratio)
        self.assertEqual(
            CONF.initial_disk_allocation_ratio, compute.disk_allocation_ratio)

        mock_update.assert_called_once_with(
            self.context, 123, {'cpu_allocation_ratio': 4.0,
                                'ram_allocation_ratio': 1.0,
                                'disk_allocation_ratio': 1.0})

    @mock.patch('nova.db.main.api.compute_node_update')
    def test_compat_allocation_ratios_None_conf_zero_values(self, mock_update):
        """Tests the scenario that the CONF.*_allocation_ratio overrides are
        left to the default (None) and the compute node record allocation
        ratio values in the DB are 0.0, so they will be migrated to the
        CONF.initial_*_allocation_ratio values.
        """
        # the CONF.x_allocation_ratio is None by default
        compute_dict = fake_compute_node.copy()
        # the computes provide allocation ratios 0.0
        compute_dict['cpu_allocation_ratio'] = 0.0
        compute_dict['ram_allocation_ratio'] = 0.0
        compute_dict['disk_allocation_ratio'] = 0.0
        cls = objects.ComputeNode
        compute = cls._from_db_object(self.context, cls(), compute_dict)

        self.assertEqual(
            CONF.initial_cpu_allocation_ratio, compute.cpu_allocation_ratio)
        self.assertEqual(
            CONF.initial_ram_allocation_ratio, compute.ram_allocation_ratio)
        self.assertEqual(
            CONF.initial_disk_allocation_ratio, compute.disk_allocation_ratio)

        mock_update.assert_called_once_with(
            self.context, 123, {'cpu_allocation_ratio': 4.0,
                                'ram_allocation_ratio': 1.0,
                                'disk_allocation_ratio': 1.0})

    @mock.patch('nova.db.main.api.compute_node_update')
    def test_compat_allocation_ratios_None_conf_None_values(self, mock_update):
        """Tests the scenario that the override CONF.*_allocation_ratio options
        are the default values (None), the compute node record from the DB has
        None values for allocation ratios, so the resulting migrated record
        will have the CONF.initial_*_allocation_ratio values.
        """
        # the CONF.x_allocation_ratio is None by default
        compute_dict = fake_compute_node.copy()
        # # the computes provide allocation ratios None
        compute_dict['cpu_allocation_ratio'] = None
        compute_dict['ram_allocation_ratio'] = None
        compute_dict['disk_allocation_ratio'] = None
        cls = objects.ComputeNode
        compute = cls._from_db_object(self.context, cls(), compute_dict)

        self.assertEqual(
            CONF.initial_cpu_allocation_ratio, compute.cpu_allocation_ratio)
        self.assertEqual(
            CONF.initial_ram_allocation_ratio, compute.ram_allocation_ratio)
        self.assertEqual(
            CONF.initial_disk_allocation_ratio, compute.disk_allocation_ratio)

        mock_update.assert_called_once_with(
            self.context, 123, {'cpu_allocation_ratio': 4.0,
                                'ram_allocation_ratio': 1.0,
                                'disk_allocation_ratio': 1.0})

    def test_get_all_by_not_mapped(self):
        for mapped in (1, 0, 1, 3):
            compute = fake_compute_with_resources.obj_clone()
            compute._context = self.context
            compute.mapped = mapped
            compute.create()
        nodes = compute_node.ComputeNodeList.get_all_by_not_mapped(
            self.context, 2)
        self.assertEqual(3, len(nodes))
        self.assertEqual([0, 1, 1], sorted([x.mapped for x in nodes]))


class TestComputeNodeObject(test_objects._LocalTest,
                            _TestComputeNodeObject):
    pass


class TestRemoteComputeNodeObject(test_objects._RemoteTest,
                                  _TestComputeNodeObject):
    pass
