diff options
-rw-r--r-- | nova/compute/resource_tracker.py | 2 | ||||
-rw-r--r-- | nova/scheduler/client/report.py | 67 | ||||
-rw-r--r-- | nova/tests/unit/scheduler/client/test_report.py | 59 |
3 files changed, 128 insertions, 0 deletions
diff --git a/nova/compute/resource_tracker.py b/nova/compute/resource_tracker.py index 82856196a0..972b0847f9 100644 --- a/nova/compute/resource_tracker.py +++ b/nova/compute/resource_tracker.py @@ -861,6 +861,8 @@ class ResourceTracker(object): self.pci_tracker.update_pci_for_instance(context, instance, sign=sign) + self.scheduler_client.reportclient.update_instance_allocation( + self.compute_node, instance, sign) # new instance, update compute node resource usage: self._update_usage(self._get_usage_dict(instance), sign=sign) diff --git a/nova/scheduler/client/report.py b/nova/scheduler/client/report.py index a7b470881d..f97ef1a764 100644 --- a/nova/scheduler/client/report.py +++ b/nova/scheduler/client/report.py @@ -20,6 +20,7 @@ from keystoneauth1 import loading as keystone from keystoneauth1 import session from oslo_log import log as logging +from nova.compute import utils as compute_utils import nova.conf from nova.i18n import _LE, _LI, _LW from nova import objects @@ -94,6 +95,11 @@ class SchedulerReportClient(object): url, json=data, endpoint_filter=self.ks_filter, raise_exc=False) + def delete(self, url): + return self._client.delete( + url, + endpoint_filter=self.ks_filter, raise_exc=False) + @safe_connect def _get_resource_provider(self, uuid): """Queries the placement API for a resource provider record with the @@ -293,3 +299,64 @@ class SchedulerReportClient(object): compute_node.hypervisor_hostname) if compute_node.uuid in self._resource_providers: self._update_inventory(compute_node) + + def _allocations(self, instance): + # NOTE(danms): Boot-from-volume instances consume no local disk + is_bfv = compute_utils.is_volume_backed_instance(instance._context, + instance) + disk = ((0 if is_bfv else instance.flavor.root_gb) + + instance.flavor.swap + + instance.flavor.ephemeral_gb) + return { + 'MEMORY_MB': instance.flavor.memory_mb, + 'VCPU': instance.flavor.vcpus, + 'DISK_GB': disk, + } + + @safe_connect + def _allocate_for_instance(self, compute_node, instance): + url = '/allocations/%s' % instance.uuid + allocations = { + 'allocations': [ + { + 'resource_provider': { + 'uuid': compute_node.uuid, + }, + 'resources': self._allocations(instance), + }, + ], + } + LOG.debug('Sending allocation for instance %s: %s' % ( + instance.uuid, allocations)) + r = self.put(url, allocations) + if not r: + LOG.warning( + _LW('Unable to submit allocation for instance ' + '%(uuid)s (%(code)i %(text)s)'), + {'uuid': instance.uuid, + 'code': r.status_code, + 'text': r.text}) + else: + LOG.info(_LI('Submitted allocation for instance %s'), + instance.uuid) + + @safe_connect + def _delete_allocation_for_instance(self, instance): + url = '/allocations/%s' % instance.uuid + r = self.delete(url) + if r: + LOG.info(_LI('Deleted allocation for instance %s'), + instance.uuid) + else: + LOG.warning( + _LW('Unable to delete allocation for instance ' + '%(uuid)s: (%(code)i %(text)s)'), + {'uuid': instance.uuid, + 'code': r.status_code, + 'text': r.text}) + + def update_instance_allocation(self, compute_node, instance, sign): + if sign > 0: + self._allocate_for_instance(compute_node, instance) + else: + self._delete_allocation_for_instance(instance) diff --git a/nova/tests/unit/scheduler/client/test_report.py b/nova/tests/unit/scheduler/client/test_report.py index bb16f2f914..1bba8c29a6 100644 --- a/nova/tests/unit/scheduler/client/test_report.py +++ b/nova/tests/unit/scheduler/client/test_report.py @@ -535,3 +535,62 @@ class SchedulerReportClientTestCase(test.NoDBTestCase): self.client.update_resource_stats(cn) mock_save.assert_called_once_with() mock_ensure.assert_called_once_with(uuids.compute_node, 'host1') + + @mock.patch('nova.compute.utils.is_volume_backed_instance') + def test_allocations(self, mock_vbi): + mock_vbi.return_value = False + inst = objects.Instance( + uuid=uuids.inst, + flavor=objects.Flavor(root_gb=10, + swap=1, + ephemeral_gb=100, + memory_mb=1024, + vcpus=2)) + expected = { + 'MEMORY_MB': 1024, + 'VCPU': 2, + 'DISK_GB': 111, + } + self.assertEqual(expected, self.client._allocations(inst)) + + @mock.patch('nova.compute.utils.is_volume_backed_instance') + def test_allocations_boot_from_volume(self, mock_vbi): + mock_vbi.return_value = True + inst = objects.Instance( + uuid=uuids.inst, + flavor=objects.Flavor(root_gb=10, + swap=1, + ephemeral_gb=100, + memory_mb=1024, + vcpus=2)) + expected = { + 'MEMORY_MB': 1024, + 'VCPU': 2, + 'DISK_GB': 101, + } + self.assertEqual(expected, self.client._allocations(inst)) + + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'put') + def test_update_instance_allocation_new(self, mock_put): + cn = objects.ComputeNode(uuid=uuids.cn) + inst = objects.Instance(uuid=uuids.inst) + with mock.patch.object(self.client, '_allocations') as mock_a: + expected = { + 'allocations': [ + {'resource_provider': {'uuid': cn.uuid}, + 'resources': mock_a.return_value}] + } + self.client.update_instance_allocation(cn, inst, 1) + mock_put.assert_called_once_with( + '/allocations/%s' % inst.uuid, + expected) + + @mock.patch('nova.scheduler.client.report.SchedulerReportClient.' + 'delete') + def test_update_instance_allocation_delete(self, mock_delete): + cn = objects.ComputeNode(uuid=uuids.cn) + inst = objects.Instance(uuid=uuids.inst) + self.client.update_instance_allocation(cn, inst, -1) + mock_delete.assert_called_once_with( + '/allocations/%s' % inst.uuid) |