placement: refactor translate from node to dict

This patch refactors the previous object method on the scheduler report
client _compute_node_inventory() to being a module-level function called
_compute_node_to_inventory_dict(). The spurious top-level 'inventories'
key is removed from the returned dict and the payload for PUT
/r-p/{uuid}/inventories is constructed explicitly now.

Refactors the unit tests for the report client that handle inventory to
remove unnecessary mocking and correct some improper stub values that
were being masked by the mocks.

Change-Id: I70e5b649e59598b1fe356fa401769e8a7ce60c72
This commit is contained in:
Jay Pipes 2016-09-19 18:25:03 -04:00
parent fe46d482d6
commit 310e4dcbd8
2 changed files with 159 additions and 107 deletions

View File

@ -59,6 +59,40 @@ def safe_connect(f):
return wrapper
def _compute_node_to_inventory_dict(compute_node):
"""Given a supplied `objects.ComputeNode` object, return a dict, keyed
by resource class, of various inventory information.
:param compute_node: `objects.ComputeNode` object to translate
"""
return {
VCPU: {
'total': compute_node.vcpus,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.cpu_allocation_ratio,
},
MEMORY_MB: {
'total': compute_node.memory_mb,
'reserved': CONF.reserved_host_memory_mb,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.ram_allocation_ratio,
},
DISK_GB: {
'total': compute_node.local_gb,
'reserved': CONF.reserved_host_disk_mb * 1024,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.disk_allocation_ratio,
},
}
class SchedulerReportClient(object):
"""Client class for updating the scheduler."""
@ -211,38 +245,6 @@ class SchedulerReportClient(object):
self._resource_providers[uuid] = rp
return rp
def _compute_node_inventory(self, compute_node):
inventories = {
'VCPU': {
'total': compute_node.vcpus,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.cpu_allocation_ratio,
},
'MEMORY_MB': {
'total': compute_node.memory_mb,
'reserved': CONF.reserved_host_memory_mb,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.ram_allocation_ratio,
},
'DISK_GB': {
'total': compute_node.local_gb,
'reserved': CONF.reserved_host_disk_mb * 1024,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.disk_allocation_ratio,
},
}
data = {
'inventories': inventories,
}
return data
def _get_inventory(self, compute_node):
url = '/resource_providers/%s/inventories' % compute_node.uuid
result = self.get(url)
@ -257,7 +259,7 @@ class SchedulerReportClient(object):
:returns: True if the inventory was updated (or did not need to be),
False otherwise.
"""
data = self._compute_node_inventory(compute_node)
inv_data = _compute_node_to_inventory_dict(compute_node)
curr = self._get_inventory(compute_node)
# Update our generation immediately, if possible. Even if there
@ -274,13 +276,16 @@ class SchedulerReportClient(object):
my_rp.generation = server_gen
# Check to see if we need to update placement's view
if data['inventories'] == curr.get('inventories', {}):
if inv_data == curr.get('inventories', {}):
return True
data['resource_provider_generation'] = (
self._resource_providers[compute_node.uuid].generation)
cur_rp_gen = self._resource_providers[compute_node.uuid].generation
payload = {
'resource_provider_generation': cur_rp_gen,
'inventories': inv_data,
}
url = '/resource_providers/%s/inventories' % compute_node.uuid
result = self.put(url, data)
result = self.put(url, payload)
if result.status_code == 409:
LOG.info(_LI('Inventory update conflict for %s'),
compute_node.uuid)

View File

@ -351,8 +351,6 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
self.assertFalse(result)
def test_compute_node_inventory(self):
# This is for making sure we only check once the I/O so we can directly
# call this helper method for the next tests.
uuid = uuids.compute_node
name = 'computehost'
compute_node = objects.ComputeNode(uuid=uuid,
@ -363,15 +361,13 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
ram_allocation_ratio=1.5,
local_gb=10,
disk_allocation_ratio=1.0)
rp = objects.ResourceProvider(uuid=uuid, name=name, generation=42)
self.client._resource_providers[uuid] = rp
self.flags(reserved_host_memory_mb=1000)
self.flags(reserved_host_disk_mb=2000)
result = self.client._compute_node_inventory(compute_node)
result = report._compute_node_to_inventory_dict(compute_node)
expected_inventories = {
expected = {
'VCPU': {
'total': compute_node.vcpus,
'reserved': 0,
@ -397,36 +393,75 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
'allocation_ratio': compute_node.disk_allocation_ratio,
},
}
expected = {
'inventories': expected_inventories,
}
self.assertEqual(expected, result)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_update_inventory_attempt')
def test_update_resource_stats_rp_fail(self, mock_ui, mock_erp):
cn = mock.MagicMock()
self.client.update_resource_stats(cn)
cn.save.assert_called_once_with()
mock_erp.assert_called_once_with(cn.uuid, cn.hypervisor_hostname)
self.assertFalse(mock_ui.called)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider')
@mock.patch.object(objects.ComputeNode, 'save')
def test_update_resource_stats_saves(self, mock_save, mock_ensure):
cn = objects.ComputeNode(context=self.context,
uuid=uuids.compute_node,
hypervisor_hostname='host1')
self.client.update_resource_stats(cn)
mock_save.assert_called_once_with()
mock_ensure.assert_called_once_with(uuids.compute_node, 'host1')
class TestInventory(SchedulerReportClientTestCase):
def setUp(self):
super(TestInventory, self).setUp()
self.compute_node = objects.ComputeNode(
uuid=uuids.compute_node,
hypervisor_hostname='foo',
vcpus=8,
cpu_allocation_ratio=16.0,
memory_mb=1024,
ram_allocation_ratio=1.5,
local_gb=10,
disk_allocation_ratio=1.0,
)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'get')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_compute_node_inventory')
def test_update_inventory(self, mock_inv, mock_put, mock_get):
def test_update_inventory(self, mock_put, mock_get):
# Ensure _update_inventory() returns a list of Inventories objects
# after creating or updating the existing values
uuid = uuids.compute_node
compute_node = objects.ComputeNode(uuid=uuid,
hypervisor_hostname='foo')
compute_node = self.compute_node
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
# Make sure the ResourceProvider exists for preventing to call the API
self.client._resource_providers[uuid] = rp
mock_inv.return_value = {'inventories': []}
mock_get.return_value.json.return_value = {
'resource_provider_generation': 43,
'inventories': {'VCPU': {'total': 16}},
'inventories': {
'VCPU': {'total': 16},
'MEMORY_MB': {'total': 1024},
'DISK_GB': {'total': 10},
}
}
mock_put.return_value.status_code = 200
mock_put.return_value.json.return_value = {
'resource_provider_generation': 44,
'inventories': {'VCPU': {'total': 16}},
'inventories': {
'VCPU': {'total': 16},
'MEMORY_MB': {'total': 1024},
'DISK_GB': {'total': 10},
}
}
result = self.client._update_inventory_attempt(compute_node)
@ -434,32 +469,77 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
exp_url = '/resource_providers/%s/inventories' % uuid
mock_get.assert_called_once_with(exp_url)
# Called with the newly-found generation from the existing inventory
self.assertEqual(43,
mock_inv.return_value['resource_provider_generation'])
# Updated with the new inventory from the PUT call
self.assertEqual(44, rp.generation)
mock_put.assert_called_once_with(exp_url, mock_inv.return_value)
expected = {
# Called with the newly-found generation from the existing
# inventory
'resource_provider_generation': 43,
'inventories': {
'VCPU': {
'total': 8,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.cpu_allocation_ratio,
},
'MEMORY_MB': {
'total': 1024,
'reserved': CONF.reserved_host_memory_mb,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.ram_allocation_ratio,
},
'DISK_GB': {
'total': 10,
'reserved': CONF.reserved_host_disk_mb * 1024,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.disk_allocation_ratio,
},
}
}
mock_put.assert_called_once_with(exp_url, expected)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'get')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_compute_node_inventory')
def test_update_inventory_no_update(self, mock_inv, mock_put, mock_get):
def test_update_inventory_no_update(self, mock_put, mock_get):
uuid = uuids.compute_node
compute_node = objects.ComputeNode(uuid=uuid,
hypervisor_hostname='foo')
compute_node = self.compute_node
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
self.client._resource_providers[uuid] = rp
mock_inv.return_value = {'inventories': {
'VCPU': {'total': 8},
}}
mock_get.return_value.json.return_value = {
'resource_provider_generation': 43,
'inventories': {
'VCPU': {'total': 8}
'VCPU': {
'total': 8,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.cpu_allocation_ratio,
},
'MEMORY_MB': {
'total': 1024,
'reserved': CONF.reserved_host_memory_mb,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.ram_allocation_ratio,
},
'DISK_GB': {
'total': 10,
'reserved': CONF.reserved_host_disk_mb * 1024,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': compute_node.disk_allocation_ratio,
},
}
}
result = self.client._update_inventory_attempt(compute_node)
@ -475,22 +555,18 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
'_get_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_compute_node_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider')
def test_update_inventory_conflicts(self, mock_ensure, mock_inv,
def test_update_inventory_conflicts(self, mock_ensure,
mock_put, mock_get):
# Ensure _update_inventory() returns a list of Inventories objects
# after creating or updating the existing values
uuid = uuids.compute_node
compute_node = objects.ComputeNode(uuid=uuid,
hypervisor_hostname='foo')
compute_node = self.compute_node
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
# Make sure the ResourceProvider exists for preventing to call the API
self.client._resource_providers[uuid] = rp
mock_inv.return_value = {'inventories': [{'resource_class': 'VCPU'}]}
mock_get.return_value = {}
mock_put.return_value.status_code = 409
@ -506,20 +582,15 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
'_get_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_compute_node_inventory')
def test_update_inventory_unknown_response(self, mock_inv,
mock_put, mock_get):
def test_update_inventory_unknown_response(self, mock_put, mock_get):
# Ensure _update_inventory() returns a list of Inventories objects
# after creating or updating the existing values
uuid = uuids.compute_node
compute_node = objects.ComputeNode(uuid=uuid,
hypervisor_hostname='foo')
compute_node = self.compute_node
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
# Make sure the ResourceProvider exists for preventing to call the API
self.client._resource_providers[uuid] = rp
mock_inv.return_value = {'inventories': [{'resource_class': 'VCPU'}]}
mock_get.return_value = {}
mock_put.return_value.status_code = 234
@ -533,20 +604,15 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
'_get_inventory')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_compute_node_inventory')
def test_update_inventory_failed(self, mock_inv,
mock_put, mock_get):
def test_update_inventory_failed(self, mock_put, mock_get):
# Ensure _update_inventory() returns a list of Inventories objects
# after creating or updating the existing values
uuid = uuids.compute_node
compute_node = objects.ComputeNode(uuid=uuid,
hypervisor_hostname='foo')
compute_node = self.compute_node
rp = objects.ResourceProvider(uuid=uuid, name='foo', generation=42)
# Make sure the ResourceProvider exists for preventing to call the API
self.client._resource_providers[uuid] = rp
mock_inv.return_value = {'inventories': [{'resource_class': 'VCPU'}]}
mock_get.return_value = {}
try:
mock_put.return_value.__nonzero__.return_value = False
@ -606,27 +672,8 @@ class SchedulerReportClientTestCase(test.NoDBTestCase):
# Slept three times
mock_sleep.assert_has_calls([mock.call(1), mock.call(1), mock.call(1)])
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_update_inventory_attempt')
def test_update_resource_stats_rp_fail(self, mock_ui, mock_erp):
cn = mock.MagicMock()
self.client.update_resource_stats(cn)
cn.save.assert_called_once_with()
mock_erp.assert_called_once_with(cn.uuid, cn.hypervisor_hostname)
self.assertFalse(mock_ui.called)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'_ensure_resource_provider')
@mock.patch.object(objects.ComputeNode, 'save')
def test_update_resource_stats_saves(self, mock_save, mock_ensure):
cn = objects.ComputeNode(context=self.context,
uuid=uuids.compute_node,
hypervisor_hostname='host1')
self.client.update_resource_stats(cn)
mock_save.assert_called_once_with()
mock_ensure.assert_called_once_with(uuids.compute_node, 'host1')
class TestAllocations(SchedulerReportClientTestCase):
@mock.patch('nova.compute.utils.is_volume_backed_instance')
def test_allocations(self, mock_vbi):