208 lines
8.1 KiB
Python
208 lines
8.1 KiB
Python
# Copyright 2018 Cloudbase Solutions Srl
|
|
# All Rights Reserved.
|
|
#
|
|
# 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.
|
|
|
|
from unittest import mock
|
|
|
|
import ddt
|
|
from nova import context
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.tests.unit import fake_requests
|
|
from oslo_serialization import jsonutils
|
|
|
|
from compute_hyperv.nova.utils import placement as placement
|
|
from compute_hyperv.tests import fake_instance
|
|
from compute_hyperv.tests.unit import test_base
|
|
|
|
|
|
@ddt.ddt
|
|
class PlacementUtilsTestCase(test_base.HyperVBaseTestCase):
|
|
_autospec_classes = [
|
|
placement.report.SchedulerReportClient
|
|
]
|
|
|
|
_FAKE_PROVIDER = 'fdb5c6d0-e0e9-4411-b952-fb05d6133718'
|
|
_FAKE_RESOURCES = {'VCPU': 1, 'MEMORY_MB': 512, 'DISK_GB': 1}
|
|
_FAKE_ALLOCATIONS = {
|
|
_FAKE_PROVIDER: {'resources': _FAKE_RESOURCES}
|
|
}
|
|
|
|
def setUp(self):
|
|
super(PlacementUtilsTestCase, self).setUp()
|
|
self.context = context.get_admin_context()
|
|
self.instance = fake_instance.fake_instance_obj(self.context)
|
|
|
|
self.placement = placement.PlacementUtils()
|
|
self.client = self.placement.reportclient
|
|
|
|
@mock.patch.object(objects.ComputeNode, 'get_by_host_and_nodename')
|
|
@mock.patch.object(placement.PlacementUtils, 'move_allocations')
|
|
def test_move_compute_node_allocations(self, mock_move_alloc,
|
|
mock_get_comp_node):
|
|
mock_get_comp_node.side_effect = [
|
|
mock.Mock(uuid=uuid) for uuid in [mock.sentinel.old_host_uuid,
|
|
mock.sentinel.new_host_uuid]]
|
|
|
|
self.placement.move_compute_node_allocations(
|
|
self.context, self.instance, mock.sentinel.old_host,
|
|
mock.sentinel.new_host,
|
|
merge_existing=mock.sentinel.merge_existing)
|
|
|
|
mock_move_alloc.assert_called_once_with(
|
|
self.context, self.instance.uuid,
|
|
mock.sentinel.old_host_uuid,
|
|
mock.sentinel.new_host_uuid,
|
|
merge_existing=mock.sentinel.merge_existing)
|
|
mock_get_comp_node.assert_has_calls(
|
|
mock.call(self.context, host, host) for host in
|
|
[mock.sentinel.old_host, mock.sentinel.new_host])
|
|
|
|
@ddt.data({}, # provider did not change
|
|
{'old_rp': 'fake_rp'}) # provider not included in allocations
|
|
@ddt.unpack
|
|
@mock.patch.object(placement.PlacementUtils, '_get_allocs_for_consumer')
|
|
@mock.patch.object(placement.PlacementUtils, '_put_allocs')
|
|
def test_move_allocations_noop(self, mock_put, mock_get_allocs,
|
|
old_rp=_FAKE_PROVIDER,
|
|
new_rp=_FAKE_PROVIDER):
|
|
mock_get_allocs.return_value = {'allocations': self._FAKE_ALLOCATIONS}
|
|
|
|
self.placement.move_allocations(
|
|
self.context, mock.sentinel.consumer, old_rp, new_rp)
|
|
|
|
mock_get_allocs.assert_called_once_with(
|
|
self.context, mock.sentinel.consumer,
|
|
version=placement.CONSUMER_GENERATION_VERSION)
|
|
mock_put.assert_not_called()
|
|
|
|
@ddt.data(True, False)
|
|
@mock.patch.object(placement.PlacementUtils, '_get_allocs_for_consumer')
|
|
@mock.patch.object(placement.PlacementUtils, '_put_allocs')
|
|
def test_merge_allocations(self, merge_existing,
|
|
mock_put, mock_get_allocs):
|
|
old_rp = self._FAKE_PROVIDER
|
|
new_rp = 'new_rp'
|
|
allocs = self._FAKE_ALLOCATIONS.copy()
|
|
allocs[new_rp] = {'resources': self._FAKE_RESOURCES.copy()}
|
|
|
|
mock_get_allocs.return_value = {'allocations': allocs}
|
|
|
|
if merge_existing:
|
|
exp_resources = {'VCPU': 2, 'MEMORY_MB': 1024, 'DISK_GB': 2}
|
|
else:
|
|
exp_resources = self._FAKE_RESOURCES
|
|
exp_allocs = {new_rp: {'resources': exp_resources}}
|
|
|
|
self.placement.move_allocations(
|
|
self.context, mock.sentinel.consumer, old_rp, new_rp,
|
|
merge_existing=merge_existing)
|
|
|
|
mock_put.assert_called_once_with(
|
|
self.context, mock.sentinel.consumer,
|
|
{'allocations': exp_allocs},
|
|
version=placement.CONSUMER_GENERATION_VERSION)
|
|
|
|
@ddt.data({}, # no errors
|
|
{'status_code': 409,
|
|
'errors': [{'code': 'placement.concurrent_update'}],
|
|
'expected_exc': placement.report.Retry},
|
|
{'status_code': 500,
|
|
'expected_exc': exception.AllocationUpdateFailed})
|
|
@ddt.unpack
|
|
def test_put_allocs(self, status_code=204, expected_exc=None, errors=None):
|
|
response = fake_requests.FakeResponse(
|
|
status_code,
|
|
content=jsonutils.dumps({'errors': errors}))
|
|
self.client.put.return_value = response
|
|
|
|
args = (self.context, mock.sentinel.consumer, mock.sentinel.allocs,
|
|
mock.sentinel.version)
|
|
if expected_exc:
|
|
self.assertRaises(expected_exc, self.placement._put_allocs, *args)
|
|
else:
|
|
self.placement._put_allocs(*args)
|
|
|
|
self.client.put.assert_called_once_with(
|
|
'/allocations/%s' % mock.sentinel.consumer,
|
|
mock.sentinel.allocs,
|
|
version=mock.sentinel.version,
|
|
global_request_id=self.context.global_id)
|
|
|
|
def test_get_allocs(self):
|
|
ret_val = self.placement._get_allocs_for_consumer(
|
|
self.context, mock.sentinel.consumer, mock.sentinel.version)
|
|
exp_val = self.client.get.return_value.json.return_value
|
|
self.assertEqual(exp_val, ret_val)
|
|
|
|
self.client.get.assert_called_once_with(
|
|
'/allocations/%s' % mock.sentinel.consumer,
|
|
version=mock.sentinel.version,
|
|
global_request_id=self.context.global_id)
|
|
|
|
def test_get_allocs_missing(self):
|
|
self.client.get.return_value = fake_requests.FakeResponse(500)
|
|
self.assertRaises(
|
|
exception.ConsumerAllocationRetrievalFailed,
|
|
self.placement._get_allocs_for_consumer,
|
|
self.context, mock.sentinel.consumer, mock.sentinel.version)
|
|
|
|
def test_merge_resources(self):
|
|
resources = {
|
|
'VCPU': 1, 'MEMORY_MB': 1024,
|
|
}
|
|
new_resources = {
|
|
'VCPU': 2, 'MEMORY_MB': 2048, 'CUSTOM_FOO': 1,
|
|
}
|
|
doubled = {
|
|
'VCPU': 3, 'MEMORY_MB': 3072, 'CUSTOM_FOO': 1,
|
|
}
|
|
saved_orig = dict(resources)
|
|
self.placement.merge_resources(resources, new_resources)
|
|
# Check to see that we've doubled our resources
|
|
self.assertEqual(doubled, resources)
|
|
# and then removed those doubled resources
|
|
self.placement.merge_resources(resources, saved_orig, -1)
|
|
self.assertEqual(new_resources, resources)
|
|
|
|
def test_merge_resources_zero(self):
|
|
# Test 0 value resources are ignored.
|
|
resources = {
|
|
'VCPU': 1, 'MEMORY_MB': 1024,
|
|
}
|
|
new_resources = {
|
|
'VCPU': 2, 'MEMORY_MB': 2048, 'DISK_GB': 0,
|
|
}
|
|
# The result should not include the zero valued resource.
|
|
doubled = {
|
|
'VCPU': 3, 'MEMORY_MB': 3072,
|
|
}
|
|
self.placement.merge_resources(resources, new_resources)
|
|
self.assertEqual(doubled, resources)
|
|
|
|
def test_merge_resources_original_zeroes(self):
|
|
# Confirm that merging that result in a zero in the original
|
|
# excludes the zeroed resource class.
|
|
resources = {
|
|
'VCPU': 3, 'MEMORY_MB': 1023, 'DISK_GB': 1,
|
|
}
|
|
new_resources = {
|
|
'VCPU': 1, 'MEMORY_MB': 512, 'DISK_GB': 1,
|
|
}
|
|
merged = {
|
|
'VCPU': 2, 'MEMORY_MB': 511,
|
|
}
|
|
self.placement.merge_resources(resources, new_resources, -1)
|
|
self.assertEqual(merged, resources)
|