Files
nova/nova/tests/unit/objects/test_compute_node.py
Sylvain Bauza 4e3a0eb55b Fix inconsistencies in the ComputeNode object about service
DB API compute_node_get_by_service_id() was returning only the first
element even if there were multiple nodes per host. That left Ironic
the only possibility to access the full list of nodes by accessing the
compute property of the service DB object.
Fixed consequently get_all_by_host and get_by_host_and_nodename methods
which were buggy when providing backwards compatibility

Partially-Implements blueprint detach-service-from-computenode

Change-Id: I51be99ca45e056cae26747892bd228565d7b03b7
2015-01-16 11:49:09 +01:00

407 lines
18 KiB
Python

# 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 mock
from oslo.serialization import jsonutils
from oslo.utils import timeutils
from nova import db
from nova import exception
from nova import objects
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]), memory=512,
cpu_usage=0, memory_usage=0),
objects.NUMACell(id=1, cpuset=set([3, 4]), memory=512,
cpu_usage=0, memory_usage=0)])
fake_numa_topology_db_format = fake_numa_topology._to_json()
fake_hv_spec = hv_spec.HVSpec(arch='foo', hv_type='bar', vm_mode='foobar')
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,
'service_id': 456,
'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,
}
# 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']
class _TestComputeNodeObject(object):
def supported_hv_specs_comparator(self, expected, obj_val):
obj_val = [inst.to_list() for inst in obj_val]
self.json_comparator(expected, obj_val)
def pci_device_pools_comparator(self, expected, obj_val):
obj_val = obj_val.obj_to_primitive()
self.json_loads_comparator(expected, obj_val)
def json_loads_comparator(self, expected, obj_val):
# NOTE(edleafe): This is necessary because the dumps() version of the
# PciDevicePoolList doesn't maintain ordering, so the output string
# doesn't always match.
self.assertEqual(jsonutils.loads(expected), obj_val)
def comparators(self):
return {'stats': self.json_comparator,
'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'}
def test_get_by_id(self):
self.mox.StubOutWithMock(db, 'compute_node_get')
db.compute_node_get(self.context, 123).AndReturn(fake_compute_node)
self.mox.ReplayAll()
compute = compute_node.ComputeNode.get_by_id(self.context, 123)
self.compare_obj(compute, fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
@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_no_host = fake_compute_node.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,
subs=self.subs(),
comparators=self.comparators())
def test_get_by_service_id(self):
self.mox.StubOutWithMock(db, 'compute_nodes_get_by_service_id')
db.compute_nodes_get_by_service_id(self.context, 456).AndReturn(
[fake_compute_node])
self.mox.ReplayAll()
compute = compute_node.ComputeNode.get_by_service_id(self.context, 456)
self.compare_obj(compute, fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
@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('nova.objects.Service.get_by_id')
@mock.patch('nova.db.compute_nodes_get_by_service_id')
@mock.patch('nova.objects.Service.get_by_compute_host')
@mock.patch.object(db, 'compute_node_get_by_host_and_nodename')
def test_get_by_host_and_nodename_with_old_compute(self, cn_get_by_h_and_n,
svc_get_by_ch,
cn_get_by_svc_id,
svc_get_by_id):
cn_get_by_h_and_n.side_effect = exception.ComputeHostNotFound(
host='fake')
fake_service = service.Service(id=123)
fake_service.host = 'fake'
svc_get_by_ch.return_value = fake_service
cn_get_by_svc_id.return_value = [fake_old_compute_node]
svc_get_by_id.return_value = fake_service
compute = compute_node.ComputeNode.get_by_host_and_nodename(
self.context, 'fake', 'vm.danplanet.com')
# NOTE(sbauza): Result is still converted to new style Compute
self.compare_obj(compute, fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
@mock.patch('nova.objects.Service.get_by_id')
@mock.patch('nova.db.compute_nodes_get_by_service_id')
@mock.patch('nova.objects.Service.get_by_compute_host')
@mock.patch.object(db, 'compute_node_get_by_host_and_nodename')
def test_get_by_host_and_nodename_not_found(self, cn_get_by_h_and_n,
svc_get_by_ch,
cn_get_by_svc_id,
svc_get_by_id):
cn_get_by_h_and_n.side_effect = exception.ComputeHostNotFound(
host='fake')
fake_service = service.Service(id=123)
fake_service.host = 'fake'
another_node = fake_old_compute_node.copy()
another_node['hypervisor_hostname'] = 'elsewhere'
svc_get_by_ch.return_value = fake_service
cn_get_by_svc_id.return_value = [another_node]
svc_get_by_id.return_value = fake_service
self.assertRaises(exception.ComputeHostNotFound,
compute_node.ComputeNode.get_by_host_and_nodename,
self.context, 'fake', 'vm.danplanet.com')
@mock.patch('nova.objects.Service.get_by_id')
@mock.patch('nova.db.compute_nodes_get_by_service_id')
@mock.patch('nova.objects.Service.get_by_compute_host')
@mock.patch.object(db, 'compute_node_get_by_host_and_nodename')
def test_get_by_host_and_nodename_good_and_bad(self, cn_get_by_h_and_n,
svc_get_by_ch,
cn_get_by_svc_id,
svc_get_by_id):
cn_get_by_h_and_n.side_effect = exception.ComputeHostNotFound(
host='fake')
fake_service = service.Service(id=123)
fake_service.host = 'fake'
bad_node = fake_old_compute_node.copy()
bad_node['hypervisor_hostname'] = 'elsewhere'
good_node = fake_old_compute_node.copy()
svc_get_by_ch.return_value = fake_service
cn_get_by_svc_id.return_value = [bad_node, good_node]
svc_get_by_id.return_value = fake_service
compute = compute_node.ComputeNode.get_by_host_and_nodename(
self.context, 'fake', 'vm.danplanet.com')
# NOTE(sbauza): Result is still converted to new style Compute
self.compare_obj(compute, good_node,
subs=self.subs(),
comparators=self.comparators())
def test_create(self):
self.mox.StubOutWithMock(db, 'compute_node_create')
db.compute_node_create(
self.context,
{
'service_id': 456,
'stats': fake_stats_db_format,
'host_ip': fake_host_ip,
'supported_instances': fake_supported_hv_specs_db_format,
}).AndReturn(fake_compute_node)
self.mox.ReplayAll()
compute = compute_node.ComputeNode(context=self.context)
compute.service_id = 456
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
compute.create()
self.compare_obj(compute, fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
def test_recreate_fails(self):
self.mox.StubOutWithMock(db, 'compute_node_create')
db.compute_node_create(self.context, {'service_id': 456}).AndReturn(
fake_compute_node)
self.mox.ReplayAll()
compute = compute_node.ComputeNode(context=self.context)
compute.service_id = 456
compute.create()
self.assertRaises(exception.ObjectActionError, compute.create,
self.context)
def test_save(self):
self.mox.StubOutWithMock(db, 'compute_node_update')
db.compute_node_update(
self.context, 123,
{
'vcpus_used': 3,
'stats': fake_stats_db_format,
'host_ip': fake_host_ip,
'supported_instances': fake_supported_hv_specs_db_format,
}).AndReturn(fake_compute_node)
self.mox.ReplayAll()
compute = compute_node.ComputeNode(context=self.context)
compute.id = 123
compute.vcpus_used = 3
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
compute.save()
self.compare_obj(compute, fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
@mock.patch.object(db, 'compute_node_create',
return_value=fake_compute_node)
def test_set_id_failure(self, db_mock):
compute = compute_node.ComputeNode(context=self.context)
compute.create()
self.assertRaises(exception.ReadOnlyFieldError, setattr,
compute, 'id', 124)
def test_destroy(self):
self.mox.StubOutWithMock(db, 'compute_node_delete')
db.compute_node_delete(self.context, 123)
self.mox.ReplayAll()
compute = compute_node.ComputeNode(context=self.context)
compute.id = 123
compute.destroy()
def test_service(self):
self.mox.StubOutWithMock(service.Service, 'get_by_id')
service.Service.get_by_id(self.context, 456).AndReturn('my-service')
self.mox.ReplayAll()
compute = compute_node.ComputeNode()
compute._context = self.context
compute.id = 123
compute.service_id = 456
self.assertEqual('my-service', compute.service)
# Make sure it doesn't call Service.get_by_id() again
self.assertEqual('my-service', compute.service)
def test_get_all(self):
self.mox.StubOutWithMock(db, 'compute_node_get_all')
db.compute_node_get_all(self.context).AndReturn([fake_compute_node])
self.mox.ReplayAll()
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())
def test_get_by_hypervisor(self):
self.mox.StubOutWithMock(db, 'compute_node_search_by_hypervisor')
db.compute_node_search_by_hypervisor(self.context, 'hyper').AndReturn(
[fake_compute_node])
self.mox.ReplayAll()
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.patch('nova.db.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]
fake_service = service.Service(id=123)
computes = compute_node.ComputeNodeList.get_by_service(self.context,
fake_service)
self.assertEqual(1, len(computes))
self.compare_obj(computes[0], fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
@mock.patch('nova.db.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())
@mock.patch('nova.objects.Service.get_by_id')
@mock.patch('nova.db.compute_nodes_get_by_service_id')
@mock.patch('nova.objects.Service.get_by_compute_host')
@mock.patch('nova.db.compute_node_get_all_by_host')
def test_get_all_by_host_with_old_compute(self, cn_get_all_by_host,
svc_get_by_ch,
cn_get_by_svc_id,
svc_get_by_id):
cn_get_all_by_host.side_effect = exception.ComputeHostNotFound(
host='fake')
fake_service = service.Service(id=123)
fake_service.host = 'fake'
svc_get_by_ch.return_value = fake_service
cn_get_by_svc_id.return_value = [fake_old_compute_node]
svc_get_by_id.return_value = fake_service
computes = compute_node.ComputeNodeList.get_all_by_host(self.context,
'fake')
self.assertEqual(1, len(computes))
# NOTE(sbauza): Result is still converted to new style Compute
self.compare_obj(computes[0], fake_compute_node,
subs=self.subs(),
comparators=self.comparators())
def test_compat_numa_topology(self):
compute = compute_node.ComputeNode()
primitive = compute.obj_to_primitive(target_version='1.4')
self.assertNotIn('numa_topology', primitive)
def test_compat_supported_hv_specs(self):
compute = compute_node.ComputeNode()
compute.supported_hv_specs = fake_supported_hv_specs
primitive = compute.obj_to_primitive(target_version='1.5')
self.assertNotIn('supported_hv_specs', primitive)
def test_compat_host(self):
compute = compute_node.ComputeNode()
primitive = compute.obj_to_primitive(target_version='1.6')
self.assertNotIn('host', primitive)
def test_compat_pci_device_pools(self):
compute = compute_node.ComputeNode()
compute.pci_device_pools = fake_pci_device_pools.fake_pool_list
primitive = compute.obj_to_primitive(target_version='1.8')
self.assertNotIn('pci_device_pools', primitive)
class TestComputeNodeObject(test_objects._LocalTest,
_TestComputeNodeObject):
pass
class TestRemoteComputeNodeObject(test_objects._RemoteTest,
_TestComputeNodeObject):
pass