1550 lines
63 KiB
Python
1550 lines
63 KiB
Python
# 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 ddt
|
|
import mock
|
|
import os_resource_classes as orc
|
|
from oslo_utils.fixture import uuidsentinel as uuids
|
|
import six
|
|
|
|
from nova import context as nova_context
|
|
from nova import exception
|
|
from nova import objects
|
|
from nova.scheduler.client import report
|
|
from nova.scheduler import utils
|
|
from nova import test
|
|
from nova.tests.unit import fake_instance
|
|
from nova.tests.unit.scheduler import fakes
|
|
|
|
|
|
class FakeResourceRequest(object):
|
|
"""A fake of ``nova.scheduler.utils.ResourceRequest``.
|
|
|
|
Allows us to assert that various properties of a real ResourceRequest
|
|
object are set as we'd like them to be.
|
|
"""
|
|
|
|
def __init__(self):
|
|
self._rg_by_id = {}
|
|
self._group_policy = None
|
|
self._limit = 1000
|
|
|
|
|
|
class TestUtilsBase(test.NoDBTestCase):
|
|
def setUp(self):
|
|
super(TestUtilsBase, self).setUp()
|
|
self.context = nova_context.get_admin_context()
|
|
self.mock_host_manager = mock.Mock()
|
|
|
|
def assertResourceRequestsEqual(self, expected, observed):
|
|
self.assertEqual(expected._limit, observed._limit)
|
|
self.assertEqual(expected._group_policy, observed._group_policy)
|
|
ex_by_id = expected._rg_by_id
|
|
ob_by_id = observed._rg_by_id
|
|
self.assertEqual(set(ex_by_id), set(ob_by_id))
|
|
for ident in ex_by_id:
|
|
self.assertEqual(vars(ex_by_id[ident]), vars(ob_by_id[ident]))
|
|
|
|
|
|
@ddt.ddt
|
|
class TestUtils(TestUtilsBase):
|
|
@staticmethod
|
|
def _get_image_with_traits():
|
|
image_prop = {
|
|
'properties': {
|
|
'trait:CUSTOM_IMAGE_TRAIT1': 'required',
|
|
'trait:CUSTOM_IMAGE_TRAIT2': 'required',
|
|
},
|
|
'id': 'c8b1790e-a07d-4971-b137-44f2432936cd'
|
|
}
|
|
image = objects.ImageMeta.from_dict(image_prop)
|
|
return image
|
|
|
|
def _test_resources_from_request_spec(self, expected, flavor, image=None):
|
|
if image is None:
|
|
image = objects.ImageMeta(properties=objects.ImageMetaProps())
|
|
fake_spec = objects.RequestSpec(flavor=flavor, image=image)
|
|
resources = utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
self.assertResourceRequestsEqual(expected, resources)
|
|
return resources
|
|
|
|
def test_resources_from_request_spec_flavor_only(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0)
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_resources_from_request_spec_flavor_and_image_traits(self):
|
|
image = self._get_image_with_traits()
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={
|
|
'trait:CUSTOM_FLAVOR_TRAIT': 'required',
|
|
'trait:CUSTOM_IMAGE_TRAIT2': 'required'})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
required_traits={
|
|
# trait:CUSTOM_IMAGE_TRAIT2 is defined in both extra_specs and
|
|
# image metadata. We get a union of both.
|
|
'CUSTOM_IMAGE_TRAIT1',
|
|
'CUSTOM_IMAGE_TRAIT2',
|
|
'CUSTOM_FLAVOR_TRAIT',
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor,
|
|
image)
|
|
|
|
def test_resources_from_request_spec_flavor_forbidden_trait(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={
|
|
'trait:CUSTOM_FLAVOR_TRAIT': 'forbidden'})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
forbidden_traits={
|
|
'CUSTOM_FLAVOR_TRAIT',
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_resources_from_request_spec_with_no_disk(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=0,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_get_resources_from_request_spec_custom_resource_class(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={"resources:CUSTOM_TEST_CLASS": 1})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
"VCPU": 1,
|
|
"MEMORY_MB": 1024,
|
|
"DISK_GB": 15,
|
|
"CUSTOM_TEST_CLASS": 1,
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_get_resources_from_request_spec_override_flavor_amounts(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={
|
|
"resources:VCPU": 99,
|
|
"resources:MEMORY_MB": 99,
|
|
"resources:DISK_GB": 99})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
"VCPU": 99,
|
|
"MEMORY_MB": 99,
|
|
"DISK_GB": 99,
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_get_resources_from_request_spec_remove_flavor_amounts(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={
|
|
"resources:VCPU": 0,
|
|
"resources:DISK_GB": 0})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
"MEMORY_MB": 1024,
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_get_resources_from_request_spec_vgpu(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=0,
|
|
swap=0,
|
|
extra_specs={
|
|
"resources:VGPU": 1,
|
|
"resources:VGPU_DISPLAY_HEAD": 1})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
"VCPU": 1,
|
|
"MEMORY_MB": 1024,
|
|
"DISK_GB": 10,
|
|
"VGPU": 1,
|
|
"VGPU_DISPLAY_HEAD": 1,
|
|
}
|
|
)
|
|
self._test_resources_from_request_spec(expected_resources, flavor)
|
|
|
|
def test_get_resources_from_request_spec_bad_std_resource_class(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={
|
|
"resources:DOESNT_EXIST": 0})
|
|
fake_spec = objects.RequestSpec(flavor=flavor)
|
|
with mock.patch("nova.scheduler.utils.LOG.warning") as mock_log:
|
|
utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
mock_log.assert_called_once()
|
|
args = mock_log.call_args[0]
|
|
self.assertEqual(args[0], "Received an invalid ResourceClass "
|
|
"'%(key)s' in extra_specs.")
|
|
self.assertEqual(args[1], {"key": "DOESNT_EXIST"})
|
|
|
|
def test_get_resources_from_request_spec_granular(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=0, swap=0,
|
|
extra_specs={'resources1:VGPU': '1',
|
|
'resources1:VGPU_DISPLAY_HEAD': '2',
|
|
# Replace
|
|
'resources3:VCPU': '2',
|
|
# Stay separate (don't sum)
|
|
'resources42:SRIOV_NET_VF': '1',
|
|
'resources24:SRIOV_NET_VF': '2',
|
|
# Ignore
|
|
'some:bogus': 'value',
|
|
# Custom in the unnumbered group (merge with DISK_GB)
|
|
'resources:CUSTOM_THING': '123',
|
|
# Traits make it through
|
|
'trait3:CUSTOM_SILVER': 'required',
|
|
'trait3:CUSTOM_GOLD': 'required',
|
|
# Delete standard
|
|
'resources86:MEMORY_MB': '0',
|
|
# Standard and custom zeroes don't make it through
|
|
'resources:IPV4_ADDRESS': '0',
|
|
'resources:CUSTOM_FOO': '0',
|
|
# Bogus values don't make it through
|
|
'resources1:MEMORY_MB': 'bogus',
|
|
'group_policy': 'none'})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._group_policy = 'none'
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'DISK_GB': 10,
|
|
'CUSTOM_THING': 123,
|
|
}
|
|
)
|
|
expected_resources._rg_by_id['1'] = objects.RequestGroup(
|
|
resources={
|
|
'VGPU': 1,
|
|
'VGPU_DISPLAY_HEAD': 2,
|
|
}
|
|
)
|
|
expected_resources._rg_by_id['3'] = objects.RequestGroup(
|
|
resources={
|
|
'VCPU': 2,
|
|
},
|
|
required_traits={
|
|
'CUSTOM_GOLD',
|
|
'CUSTOM_SILVER',
|
|
}
|
|
)
|
|
expected_resources._rg_by_id['24'] = objects.RequestGroup(
|
|
resources={
|
|
'SRIOV_NET_VF': 2,
|
|
},
|
|
)
|
|
expected_resources._rg_by_id['42'] = objects.RequestGroup(
|
|
resources={
|
|
'SRIOV_NET_VF': 1,
|
|
}
|
|
)
|
|
|
|
rr = self._test_resources_from_request_spec(expected_resources, flavor)
|
|
expected_querystring = (
|
|
'group_policy=none&'
|
|
'limit=1000&'
|
|
'required3=CUSTOM_GOLD%2CCUSTOM_SILVER&'
|
|
'resources=CUSTOM_THING%3A123%2CDISK_GB%3A10&'
|
|
'resources1=VGPU%3A1%2CVGPU_DISPLAY_HEAD%3A2&'
|
|
'resources24=SRIOV_NET_VF%3A2&'
|
|
'resources3=VCPU%3A2&'
|
|
'resources42=SRIOV_NET_VF%3A1'
|
|
)
|
|
self.assertEqual(expected_querystring, rr.to_querystring())
|
|
|
|
def test_all_required_traits(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={
|
|
'trait:HW_CPU_X86_SSE': 'required',
|
|
'trait:HW_CPU_X86_AVX': 'required',
|
|
'trait:HW_CPU_X86_AVX2': 'forbidden'})
|
|
expected_resources = FakeResourceRequest()
|
|
expected_resources._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
required_traits={
|
|
'HW_CPU_X86_SSE',
|
|
'HW_CPU_X86_AVX'
|
|
},
|
|
forbidden_traits={
|
|
'HW_CPU_X86_AVX2'
|
|
}
|
|
)
|
|
resource = self._test_resources_from_request_spec(expected_resources,
|
|
flavor)
|
|
expected_result = {'HW_CPU_X86_SSE', 'HW_CPU_X86_AVX'}
|
|
self.assertEqual(expected_result,
|
|
resource.all_required_traits)
|
|
|
|
def test_resources_from_request_spec_aggregates(self):
|
|
destination = objects.Destination()
|
|
flavor = objects.Flavor(vcpus=1, memory_mb=1024,
|
|
root_gb=1, ephemeral_gb=0,
|
|
swap=0)
|
|
reqspec = objects.RequestSpec(flavor=flavor,
|
|
requested_destination=destination)
|
|
|
|
destination.require_aggregates(['foo', 'bar'])
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual([['foo', 'bar']],
|
|
req.get_request_group(None).aggregates)
|
|
|
|
destination.require_aggregates(['baz'])
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual([['foo', 'bar'], ['baz']],
|
|
req.get_request_group(None).aggregates)
|
|
|
|
def test_resources_from_request_spec_no_aggregates(self):
|
|
flavor = objects.Flavor(vcpus=1, memory_mb=1024,
|
|
root_gb=1, ephemeral_gb=0,
|
|
swap=0)
|
|
reqspec = objects.RequestSpec(flavor=flavor)
|
|
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual([], req.get_request_group(None).aggregates)
|
|
|
|
reqspec.requested_destination = None
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual([], req.get_request_group(None).aggregates)
|
|
|
|
reqspec.requested_destination = objects.Destination()
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual([], req.get_request_group(None).aggregates)
|
|
|
|
reqspec.requested_destination.aggregates = None
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual([], req.get_request_group(None).aggregates)
|
|
|
|
def test_resources_from_request_spec_forbidden_aggregates(self):
|
|
flavor = objects.Flavor(vcpus=1, memory_mb=1024,
|
|
root_gb=1, ephemeral_gb=0,
|
|
swap=0)
|
|
reqspec = objects.RequestSpec(
|
|
flavor=flavor,
|
|
requested_destination=objects.Destination(
|
|
forbidden_aggregates=set(['foo', 'bar'])))
|
|
|
|
req = utils.resources_from_request_spec(self.context, reqspec,
|
|
self.mock_host_manager)
|
|
self.assertEqual(set(['foo', 'bar']),
|
|
req.get_request_group(None).forbidden_aggregates)
|
|
|
|
def test_resources_from_request_spec_no_forbidden_aggregates(self):
|
|
flavor = objects.Flavor(vcpus=1, memory_mb=1024,
|
|
root_gb=1, ephemeral_gb=0,
|
|
swap=0)
|
|
reqspec = objects.RequestSpec(flavor=flavor)
|
|
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual(set([]), req.get_request_group(None).
|
|
forbidden_aggregates)
|
|
|
|
reqspec.requested_destination = None
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual(set([]), req.get_request_group(None).
|
|
forbidden_aggregates)
|
|
|
|
reqspec.requested_destination = objects.Destination()
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual(set([]), req.get_request_group(None).
|
|
forbidden_aggregates)
|
|
|
|
reqspec.requested_destination.forbidden_aggregates = None
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual(set([]), req.get_request_group(None).
|
|
forbidden_aggregates)
|
|
|
|
def test_process_extra_specs_granular_called(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={"resources:CUSTOM_TEST_CLASS": 1})
|
|
fake_spec = objects.RequestSpec(flavor=flavor)
|
|
# just call this to make sure things don't explode
|
|
utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
|
|
def test_process_extra_specs_granular_not_called(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0)
|
|
fake_spec = objects.RequestSpec(flavor=flavor)
|
|
# just call this to make sure things don't explode
|
|
utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
|
|
def test_process_missing_extra_specs_value(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs={"resources:CUSTOM_TEST_CLASS": ""})
|
|
fake_spec = objects.RequestSpec(flavor=flavor)
|
|
# just call this to make sure things don't explode
|
|
utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
|
|
def test_process_no_force_hosts_or_force_nodes(self):
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=15,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
)
|
|
rr = self._test_resources_from_request_spec(expected, flavor)
|
|
expected_querystring = (
|
|
'limit=1000&'
|
|
'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1'
|
|
)
|
|
self.assertEqual(expected_querystring, rr.to_querystring())
|
|
|
|
def test_process_use_force_nodes(self):
|
|
fake_nodes = objects.ComputeNodeList(objects=[
|
|
objects.ComputeNode(host='fake-host',
|
|
uuid='12345678-1234-1234-1234-123456789012',
|
|
hypervisor_hostname='test')])
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
return_value = fake_nodes
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=15,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
fake_spec = objects.RequestSpec(flavor=flavor, force_nodes=['test'])
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
in_tree='12345678-1234-1234-1234-123456789012',
|
|
)
|
|
resources = utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
self.assertResourceRequestsEqual(expected, resources)
|
|
expected_querystring = (
|
|
'in_tree=12345678-1234-1234-1234-123456789012&'
|
|
'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
|
|
self.assertEqual(expected_querystring, resources.to_querystring())
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
assert_called_once_with(self.context, None, 'test', cell=None)
|
|
|
|
def test_process_use_force_hosts(self):
|
|
fake_nodes = objects.ComputeNodeList(objects=[
|
|
objects.ComputeNode(host='test',
|
|
uuid='12345678-1234-1234-1234-123456789012')
|
|
])
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
return_value = fake_nodes
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=15,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
fake_spec = objects.RequestSpec(flavor=flavor, force_hosts=['test'])
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
in_tree='12345678-1234-1234-1234-123456789012',
|
|
)
|
|
resources = utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
self.assertResourceRequestsEqual(expected, resources)
|
|
expected_querystring = (
|
|
'in_tree=12345678-1234-1234-1234-123456789012&'
|
|
'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
|
|
self.assertEqual(expected_querystring, resources.to_querystring())
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
assert_called_once_with(self.context, 'test', None, cell=None)
|
|
|
|
def test_process_use_force_hosts_multinodes_found(self):
|
|
fake_nodes = objects.ComputeNodeList(objects=[
|
|
objects.ComputeNode(host='test',
|
|
uuid='12345678-1234-1234-1234-123456789012'),
|
|
objects.ComputeNode(host='test',
|
|
uuid='87654321-4321-4321-4321-210987654321'),
|
|
])
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
return_value = fake_nodes
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=15,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
fake_spec = objects.RequestSpec(flavor=flavor, force_hosts=['test'])
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
)
|
|
# Validate that the limit is unset
|
|
expected._limit = None
|
|
|
|
resources = utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
self.assertResourceRequestsEqual(expected, resources)
|
|
# Validate that the limit is unset
|
|
expected_querystring = (
|
|
'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
|
|
self.assertEqual(expected_querystring, resources.to_querystring())
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
assert_called_once_with(self.context, 'test', None, cell=None)
|
|
|
|
def test_process_use_requested_destination(self):
|
|
fake_cell = objects.CellMapping(uuid=uuids.cell1, name='foo')
|
|
destination = objects.Destination(
|
|
host='fake-host', node='fake-node', cell=fake_cell)
|
|
fake_nodes = objects.ComputeNodeList(objects=[
|
|
objects.ComputeNode(host='fake-host',
|
|
uuid='12345678-1234-1234-1234-123456789012',
|
|
hypervisor_hostname='fake-node')
|
|
])
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
return_value = fake_nodes
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=15,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
fake_spec = objects.RequestSpec(
|
|
flavor=flavor, requested_destination=destination)
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
in_tree='12345678-1234-1234-1234-123456789012',
|
|
)
|
|
resources = utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
self.assertResourceRequestsEqual(expected, resources)
|
|
expected_querystring = (
|
|
'in_tree=12345678-1234-1234-1234-123456789012&'
|
|
'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
|
|
self.assertEqual(expected_querystring, resources.to_querystring())
|
|
self.mock_host_manager.get_compute_nodes_by_host_or_node.\
|
|
assert_called_once_with(
|
|
self.context, 'fake-host', 'fake-node', cell=fake_cell)
|
|
|
|
def test_resources_from_request_spec_having_requested_resources(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0)
|
|
rg1 = objects.RequestGroup()
|
|
rg2 = objects.RequestGroup()
|
|
reqspec = objects.RequestSpec(flavor=flavor,
|
|
requested_resources=[rg1, rg2])
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual({'MEMORY_MB': 1024, 'DISK_GB': 15, 'VCPU': 1},
|
|
req.get_request_group(None).resources)
|
|
self.assertIs(rg1, req.get_request_group(1))
|
|
self.assertIs(rg2, req.get_request_group(2))
|
|
|
|
def test_resources_from_request_spec_requested_resources_unfilled(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0)
|
|
reqspec = objects.RequestSpec(flavor=flavor)
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual({'MEMORY_MB': 1024, 'DISK_GB': 15, 'VCPU': 1},
|
|
req.get_request_group(None).resources)
|
|
self.assertEqual(1, len(list(req.resource_groups())))
|
|
|
|
reqspec = objects.RequestSpec(flavor=flavor, requested_resources=[])
|
|
req = utils.resources_from_request_spec(
|
|
self.context, reqspec, self.mock_host_manager)
|
|
self.assertEqual({'MEMORY_MB': 1024, 'DISK_GB': 15, 'VCPU': 1},
|
|
req.get_request_group(None).resources)
|
|
self.assertEqual(1, len(list(req.resource_groups())))
|
|
|
|
@ddt.data(
|
|
# Test single hint that we are checking for.
|
|
{'group': [uuids.fake]},
|
|
# Test hint we care about and some other random hint.
|
|
{'same_host': [uuids.fake], 'fake-hint': ['fake-value']},
|
|
# Test multiple hints we are checking for.
|
|
{'same_host': [uuids.server1], 'different_host': [uuids.server2]})
|
|
def test_resources_from_request_spec_no_limit_based_on_hint(self, hints):
|
|
"""Tests that there is no limit applied to the
|
|
GET /allocation_candidates query string if a given scheduler hint
|
|
is in the request spec.
|
|
"""
|
|
flavor = objects.Flavor(vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=15,
|
|
ephemeral_gb=0,
|
|
swap=0)
|
|
fake_spec = objects.RequestSpec(
|
|
flavor=flavor, scheduler_hints=hints)
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
)
|
|
expected._limit = None
|
|
resources = utils.resources_from_request_spec(
|
|
self.context, fake_spec, self.mock_host_manager)
|
|
self.assertResourceRequestsEqual(expected, resources)
|
|
expected_querystring = (
|
|
'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1'
|
|
)
|
|
self.assertEqual(expected_querystring, resources.to_querystring())
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance',
|
|
return_value=False)
|
|
def test_resources_from_flavor_no_bfv(self, mock_is_bfv):
|
|
flavor = objects.Flavor(vcpus=1, memory_mb=1024, root_gb=10,
|
|
ephemeral_gb=5, swap=1024,
|
|
extra_specs={})
|
|
instance = objects.Instance()
|
|
expected = {
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 16,
|
|
}
|
|
actual = utils.resources_from_flavor(instance, flavor)
|
|
self.assertEqual(expected, actual)
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance',
|
|
return_value=True)
|
|
def test_resources_from_flavor_bfv(self, mock_is_bfv):
|
|
flavor = objects.Flavor(vcpus=1, memory_mb=1024, root_gb=10,
|
|
ephemeral_gb=5, swap=1024,
|
|
extra_specs={})
|
|
instance = objects.Instance()
|
|
expected = {
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 6, # No root disk...
|
|
}
|
|
actual = utils.resources_from_flavor(instance, flavor)
|
|
self.assertEqual(expected, actual)
|
|
|
|
@mock.patch('nova.compute.utils.is_volume_backed_instance',
|
|
new=mock.Mock(return_value=False))
|
|
def test_resources_from_flavor_with_override(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=1024,
|
|
extra_specs={
|
|
# Replace
|
|
'resources:VCPU': '2',
|
|
# Sum up
|
|
'resources42:SRIOV_NET_VF': '1',
|
|
'resources24:SRIOV_NET_VF': '2',
|
|
# Ignore
|
|
'some:bogus': 'value',
|
|
# Custom
|
|
'resources:CUSTOM_THING': '123',
|
|
# Ignore
|
|
'trait:CUSTOM_GOLD': 'required',
|
|
# Delete standard
|
|
'resources86:MEMORY_MB': 0,
|
|
# Standard and custom zeroes don't make it through
|
|
'resources:IPV4_ADDRESS': 0,
|
|
'resources:CUSTOM_FOO': 0,
|
|
'group_policy': 'none'})
|
|
instance = objects.Instance()
|
|
expected = {
|
|
'VCPU': 2,
|
|
'DISK_GB': 16,
|
|
'CUSTOM_THING': 123,
|
|
'SRIOV_NET_VF': 3,
|
|
}
|
|
actual = utils.resources_from_flavor(instance, flavor)
|
|
self.assertEqual(expected, actual)
|
|
|
|
def test_resource_request_init(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
|
|
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
)
|
|
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
|
|
rr = utils.ResourceRequest(rs)
|
|
self.assertResourceRequestsEqual(expected, rr)
|
|
|
|
def test_resource_request_init_with_extra_specs(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
|
extra_specs={
|
|
'resources:VCPU': '2',
|
|
'resources:MEMORY_MB': '2048',
|
|
'trait:HW_CPU_X86_AVX': 'required',
|
|
# Key skipped because no colons
|
|
'nocolons': '42',
|
|
'trait:CUSTOM_MAGIC': 'required',
|
|
'trait:CUSTOM_BRONZE': 'forbidden',
|
|
# Resource skipped because invalid resource class name
|
|
'resources86:CUTSOM_MISSPELLED': '86',
|
|
'resources1:SRIOV_NET_VF': '1',
|
|
# Resource skipped because non-int-able value
|
|
'resources86:CUSTOM_FOO': 'seven',
|
|
# Resource skipped because negative value
|
|
'resources86:CUSTOM_NEGATIVE': '-7',
|
|
'resources1:IPV4_ADDRESS': '1',
|
|
# Trait skipped because unsupported value
|
|
'trait86:CUSTOM_GOLD': 'preferred',
|
|
'trait1:CUSTOM_PHYSNET_NET1': 'required',
|
|
'trait1:CUSTOM_PHYSNET_NET2': 'forbidden',
|
|
'resources2:SRIOV_NET_VF': '1',
|
|
'resources2:IPV4_ADDRESS': '2',
|
|
'trait2:CUSTOM_PHYSNET_NET2': 'required',
|
|
'trait2:HW_NIC_ACCEL_SSL': 'required',
|
|
# Groupings that don't quite match the patterns are ignored
|
|
'resources_5:SRIOV_NET_VF': '7',
|
|
'traitFoo:HW_NIC_ACCEL_SSL': 'required',
|
|
# Solo resource, no corresponding traits
|
|
'resources3:DISK_GB': '5',
|
|
'group_policy': 'isolate',
|
|
})
|
|
|
|
expected = FakeResourceRequest()
|
|
expected._group_policy = 'isolate'
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 2,
|
|
'MEMORY_MB': 2048,
|
|
},
|
|
required_traits={
|
|
'HW_CPU_X86_AVX',
|
|
'CUSTOM_MAGIC',
|
|
},
|
|
forbidden_traits={
|
|
'CUSTOM_BRONZE',
|
|
},
|
|
)
|
|
expected._rg_by_id['1'] = objects.RequestGroup(
|
|
resources={
|
|
'SRIOV_NET_VF': 1,
|
|
'IPV4_ADDRESS': 1,
|
|
},
|
|
required_traits={
|
|
'CUSTOM_PHYSNET_NET1',
|
|
},
|
|
forbidden_traits={
|
|
'CUSTOM_PHYSNET_NET2',
|
|
},
|
|
)
|
|
expected._rg_by_id['2'] = objects.RequestGroup(
|
|
resources={
|
|
'SRIOV_NET_VF': 1,
|
|
'IPV4_ADDRESS': 2,
|
|
},
|
|
required_traits={
|
|
'CUSTOM_PHYSNET_NET2',
|
|
'HW_NIC_ACCEL_SSL',
|
|
}
|
|
)
|
|
expected._rg_by_id['3'] = objects.RequestGroup(
|
|
resources={
|
|
'DISK_GB': 5,
|
|
}
|
|
)
|
|
|
|
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
|
|
rr = utils.ResourceRequest(rs)
|
|
self.assertResourceRequestsEqual(expected, rr)
|
|
expected_querystring = (
|
|
'group_policy=isolate&'
|
|
'limit=1000&'
|
|
'required=CUSTOM_MAGIC%2CHW_CPU_X86_AVX%2C%21CUSTOM_BRONZE&'
|
|
'required1=CUSTOM_PHYSNET_NET1%2C%21CUSTOM_PHYSNET_NET2&'
|
|
'required2=CUSTOM_PHYSNET_NET2%2CHW_NIC_ACCEL_SSL&'
|
|
'resources=MEMORY_MB%3A2048%2CVCPU%3A2&'
|
|
'resources1=IPV4_ADDRESS%3A1%2CSRIOV_NET_VF%3A1&'
|
|
'resources2=IPV4_ADDRESS%3A2%2CSRIOV_NET_VF%3A1&'
|
|
'resources3=DISK_GB%3A5'
|
|
)
|
|
self.assertEqual(expected_querystring, rr.to_querystring())
|
|
|
|
def test_resource_request_init_with_image_props(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
|
|
image = objects.ImageMeta.from_dict({
|
|
'properties': {
|
|
'trait:CUSTOM_TRUSTED': 'required',
|
|
},
|
|
'id': 'c8b1790e-a07d-4971-b137-44f2432936cd'
|
|
})
|
|
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
},
|
|
required_traits={
|
|
'CUSTOM_TRUSTED',
|
|
}
|
|
)
|
|
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
|
|
rr = utils.ResourceRequest(rs)
|
|
self.assertResourceRequestsEqual(expected, rr)
|
|
|
|
def test_resource_request_init_is_bfv(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=1555)
|
|
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources={
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
# this should only include the ephemeral and swap disk, and the
|
|
# latter should be converted from MB to GB and rounded up
|
|
'DISK_GB': 7,
|
|
},
|
|
)
|
|
rs = objects.RequestSpec(flavor=flavor, is_bfv=True)
|
|
rr = utils.ResourceRequest(rs)
|
|
self.assertResourceRequestsEqual(expected, rr)
|
|
|
|
def test_resource_request_add_group_inserts_the_group(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
|
|
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
|
|
req = utils.ResourceRequest(rs)
|
|
rg1 = objects.RequestGroup()
|
|
req.add_request_group(rg1)
|
|
rg2 = objects.RequestGroup()
|
|
req.add_request_group(rg2)
|
|
self.assertIs(rg1, req.get_request_group(1))
|
|
self.assertIs(rg2, req.get_request_group(2))
|
|
|
|
def test_resource_request_add_group_inserts_the_group_implicit_group(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
|
|
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
|
|
req = utils.ResourceRequest(rs)
|
|
|
|
# this implicitly creates the specified group
|
|
unnumbered_rg = req.get_request_group(42)
|
|
|
|
rg1 = objects.RequestGroup()
|
|
req.add_request_group(rg1)
|
|
rg2 = objects.RequestGroup()
|
|
req.add_request_group(rg2)
|
|
|
|
self.assertIs(rg1, req.get_request_group(43))
|
|
self.assertIs(rg2, req.get_request_group(44))
|
|
self.assertIs(unnumbered_rg, req.get_request_group(42))
|
|
|
|
def test_claim_resources_on_destination_no_source_allocations(self):
|
|
"""Tests the negative scenario where the instance does not have
|
|
allocations in Placement on the source compute node so no claim is
|
|
attempted on the destination compute node.
|
|
"""
|
|
reportclient = report.SchedulerReportClient()
|
|
instance = fake_instance.fake_instance_obj(self.context)
|
|
source_node = objects.ComputeNode(
|
|
uuid=uuids.source_node, host=instance.host)
|
|
dest_node = objects.ComputeNode(uuid=uuids.dest_node, host='dest-host')
|
|
|
|
@mock.patch.object(reportclient,
|
|
'get_allocs_for_consumer',
|
|
return_value={})
|
|
@mock.patch.object(reportclient,
|
|
'claim_resources',
|
|
new_callable=mock.NonCallableMock)
|
|
def test(mock_claim, mock_get_allocs):
|
|
ex = self.assertRaises(
|
|
exception.ConsumerAllocationRetrievalFailed,
|
|
utils.claim_resources_on_destination,
|
|
self.context, reportclient, instance, source_node, dest_node)
|
|
mock_get_allocs.assert_called_once_with(
|
|
self.context, instance.uuid)
|
|
self.assertIn(
|
|
'Expected to find allocations for source node resource '
|
|
'provider %s' % source_node.uuid, six.text_type(ex))
|
|
|
|
test()
|
|
|
|
def test_claim_resources_on_destination_claim_fails(self):
|
|
"""Tests the negative scenario where the resource allocation claim
|
|
on the destination compute node fails, resulting in an error.
|
|
"""
|
|
reportclient = report.SchedulerReportClient()
|
|
instance = fake_instance.fake_instance_obj(self.context)
|
|
source_node = objects.ComputeNode(
|
|
uuid=uuids.source_node, host=instance.host)
|
|
dest_node = objects.ComputeNode(uuid=uuids.dest_node, host='dest-host')
|
|
source_res_allocs = {
|
|
'allocations': {
|
|
uuids.source_node: {
|
|
'resources': {
|
|
'VCPU': instance.vcpus,
|
|
'MEMORY_MB': instance.memory_mb,
|
|
# This would really include ephemeral and swap too but
|
|
# we're lazy.
|
|
'DISK_GB': instance.root_gb
|
|
}
|
|
}
|
|
},
|
|
'consumer_generation': 1,
|
|
'project_id': uuids.project_id,
|
|
'user_id': uuids.user_id
|
|
}
|
|
dest_alloc_request = {
|
|
'allocations': {
|
|
uuids.dest_node: {
|
|
'resources': {
|
|
'VCPU': instance.vcpus,
|
|
'MEMORY_MB': instance.memory_mb,
|
|
'DISK_GB': instance.root_gb
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
@mock.patch.object(reportclient,
|
|
'get_allocs_for_consumer',
|
|
return_value=source_res_allocs)
|
|
@mock.patch.object(reportclient,
|
|
'claim_resources', return_value=False)
|
|
def test(mock_claim, mock_get_allocs):
|
|
# NOTE(danms): Don't pass source_node_allocations here to test
|
|
# that they are fetched if needed.
|
|
self.assertRaises(exception.NoValidHost,
|
|
utils.claim_resources_on_destination,
|
|
self.context, reportclient, instance,
|
|
source_node, dest_node)
|
|
mock_get_allocs.assert_called_once_with(
|
|
self.context, instance.uuid)
|
|
mock_claim.assert_called_once_with(
|
|
self.context, instance.uuid, dest_alloc_request,
|
|
instance.project_id, instance.user_id,
|
|
allocation_request_version='1.28', consumer_generation=1)
|
|
|
|
test()
|
|
|
|
def test_claim_resources_on_destination(self):
|
|
"""Happy path test where everything is successful."""
|
|
reportclient = report.SchedulerReportClient()
|
|
instance = fake_instance.fake_instance_obj(self.context)
|
|
source_node = objects.ComputeNode(
|
|
uuid=uuids.source_node, host=instance.host)
|
|
dest_node = objects.ComputeNode(uuid=uuids.dest_node, host='dest-host')
|
|
source_res_allocs = {
|
|
uuids.source_node: {
|
|
'resources': {
|
|
'VCPU': instance.vcpus,
|
|
'MEMORY_MB': instance.memory_mb,
|
|
# This would really include ephemeral and swap too but
|
|
# we're lazy.
|
|
'DISK_GB': instance.root_gb
|
|
}
|
|
}
|
|
}
|
|
dest_alloc_request = {
|
|
'allocations': {
|
|
uuids.dest_node: {
|
|
'resources': {
|
|
'VCPU': instance.vcpus,
|
|
'MEMORY_MB': instance.memory_mb,
|
|
'DISK_GB': instance.root_gb
|
|
}
|
|
}
|
|
},
|
|
}
|
|
|
|
@mock.patch.object(reportclient,
|
|
'get_allocs_for_consumer')
|
|
@mock.patch.object(reportclient,
|
|
'claim_resources', return_value=True)
|
|
def test(mock_claim, mock_get_allocs):
|
|
utils.claim_resources_on_destination(
|
|
self.context, reportclient, instance, source_node, dest_node,
|
|
source_res_allocs, consumer_generation=None)
|
|
self.assertFalse(mock_get_allocs.called)
|
|
mock_claim.assert_called_once_with(
|
|
self.context, instance.uuid, dest_alloc_request,
|
|
instance.project_id, instance.user_id,
|
|
allocation_request_version='1.28', consumer_generation=None)
|
|
|
|
test()
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient')
|
|
@mock.patch('nova.scheduler.utils.request_is_rebuild')
|
|
def test_claim_resources(self, mock_is_rebuild, mock_client):
|
|
"""Tests that when claim_resources() is called, that we appropriately
|
|
call the placement client to claim resources for the instance.
|
|
"""
|
|
mock_is_rebuild.return_value = False
|
|
ctx = nova_context.RequestContext(user_id=uuids.user_id)
|
|
spec_obj = objects.RequestSpec(project_id=uuids.project_id)
|
|
instance_uuid = uuids.instance
|
|
alloc_req = mock.sentinel.alloc_req
|
|
mock_client.claim_resources.return_value = True
|
|
|
|
res = utils.claim_resources(ctx, mock_client, spec_obj, instance_uuid,
|
|
alloc_req)
|
|
|
|
mock_client.claim_resources.assert_called_once_with(
|
|
ctx, uuids.instance, mock.sentinel.alloc_req, uuids.project_id,
|
|
uuids.user_id, allocation_request_version=None,
|
|
consumer_generation=None)
|
|
self.assertTrue(res)
|
|
|
|
# Now do it again but with RequestSpec.user_id set.
|
|
spec_obj.user_id = uuids.spec_user_id
|
|
mock_client.reset_mock()
|
|
utils.claim_resources(ctx, mock_client, spec_obj, instance_uuid,
|
|
alloc_req)
|
|
mock_client.claim_resources.assert_called_once_with(
|
|
ctx, uuids.instance, mock.sentinel.alloc_req, uuids.project_id,
|
|
uuids.spec_user_id, allocation_request_version=None,
|
|
consumer_generation=None)
|
|
|
|
@mock.patch('nova.scheduler.client.report.SchedulerReportClient')
|
|
@mock.patch('nova.scheduler.utils.request_is_rebuild')
|
|
def test_claim_resources_for_policy_check(self, mock_is_rebuild,
|
|
mock_client):
|
|
mock_is_rebuild.return_value = True
|
|
ctx = mock.Mock(user_id=uuids.user_id)
|
|
res = utils.claim_resources(ctx, None, mock.sentinel.spec_obj,
|
|
mock.sentinel.instance_uuid, [])
|
|
self.assertTrue(res)
|
|
mock_is_rebuild.assert_called_once_with(mock.sentinel.spec_obj)
|
|
self.assertFalse(mock_client.claim_resources.called)
|
|
|
|
def test_get_weight_multiplier(self):
|
|
host_attr = {'vcpus_total': 4, 'vcpus_used': 6,
|
|
'cpu_allocation_ratio': 1.0}
|
|
host1 = fakes.FakeHostState('fake-host', 'node', host_attr)
|
|
|
|
host1.aggregates = [
|
|
objects.Aggregate(
|
|
id=1,
|
|
name='foo',
|
|
hosts=['fake-host'],
|
|
metadata={'cpu_weight_multiplier': 'invalid'},
|
|
)]
|
|
# Get value from default given value if the agg meta is invalid.
|
|
self.assertEqual(
|
|
1.0,
|
|
utils.get_weight_multiplier(host1, 'cpu_weight_multiplier', 1.0)
|
|
)
|
|
|
|
host1.aggregates = [
|
|
objects.Aggregate(
|
|
id=1,
|
|
name='foo',
|
|
hosts=['fake-host'],
|
|
metadata={'cpu_weight_multiplier': '1.9'},
|
|
)]
|
|
# Get value from aggregate metadata
|
|
self.assertEqual(
|
|
1.9,
|
|
utils.get_weight_multiplier(host1, 'cpu_weight_multiplier', 1.0)
|
|
)
|
|
|
|
host1.aggregates = [
|
|
objects.Aggregate(
|
|
id=1,
|
|
name='foo',
|
|
hosts=['fake-host'],
|
|
metadata={'cpu_weight_multiplier': '1.9'}),
|
|
objects.Aggregate(
|
|
id=2,
|
|
name='foo',
|
|
hosts=['fake-host'],
|
|
metadata={'cpu_weight_multiplier': '1.8'}),
|
|
]
|
|
# Get min value from aggregate metadata
|
|
self.assertEqual(
|
|
1.8,
|
|
utils.get_weight_multiplier(host1, 'cpu_weight_multiplier', 1.0)
|
|
)
|
|
|
|
|
|
class TestEncryptedMemoryTranslation(TestUtilsBase):
|
|
flavor_name = 'm1.test'
|
|
image_name = 'cirros'
|
|
|
|
def _get_request_spec(self, extra_specs, image):
|
|
flavor = objects.Flavor(name=self.flavor_name,
|
|
vcpus=1,
|
|
memory_mb=1024,
|
|
root_gb=10,
|
|
ephemeral_gb=5,
|
|
swap=0,
|
|
extra_specs=extra_specs)
|
|
|
|
# NOTE(aspiers): RequestSpec.flavor is not nullable, but
|
|
# RequestSpec.image is.
|
|
reqspec = objects.RequestSpec(flavor=flavor)
|
|
|
|
if image:
|
|
reqspec.image = image
|
|
|
|
return reqspec
|
|
|
|
def _get_resource_request(self, extra_specs, image):
|
|
reqspec = self._get_request_spec(extra_specs, image)
|
|
return utils.ResourceRequest(reqspec)
|
|
|
|
def _get_expected_resource_request(self, mem_encryption_context):
|
|
expected_resources = {
|
|
'VCPU': 1,
|
|
'MEMORY_MB': 1024,
|
|
'DISK_GB': 15,
|
|
}
|
|
if mem_encryption_context:
|
|
expected_resources[orc.MEM_ENCRYPTION_CONTEXT] = 1
|
|
|
|
expected = FakeResourceRequest()
|
|
expected._rg_by_id[None] = objects.RequestGroup(
|
|
use_same_provider=False,
|
|
resources=expected_resources)
|
|
return expected
|
|
|
|
def _test_encrypted_memory_support_not_required(self, extra_specs,
|
|
image=None):
|
|
resreq = self._get_resource_request(extra_specs, image)
|
|
expected = self._get_expected_resource_request(False)
|
|
|
|
self.assertResourceRequestsEqual(expected, resreq)
|
|
|
|
def test_encrypted_memory_support_empty_extra_specs(self):
|
|
self._test_encrypted_memory_support_not_required(extra_specs={})
|
|
|
|
def test_encrypted_memory_support_false_extra_spec(self):
|
|
for extra_spec in ('0', 'false', 'False'):
|
|
self._test_encrypted_memory_support_not_required(
|
|
extra_specs={'hw:mem_encryption': extra_spec})
|
|
|
|
def test_encrypted_memory_support_empty_image_props(self):
|
|
self._test_encrypted_memory_support_not_required(
|
|
extra_specs={},
|
|
image=objects.ImageMeta(properties=objects.ImageMetaProps()))
|
|
|
|
def test_encrypted_memory_support_false_image_prop(self):
|
|
for image_prop in ('0', 'false', 'False'):
|
|
self._test_encrypted_memory_support_not_required(
|
|
extra_specs={},
|
|
image=objects.ImageMeta(
|
|
properties=objects.ImageMetaProps(
|
|
hw_mem_encryption=image_prop))
|
|
)
|
|
|
|
def test_encrypted_memory_support_both_false(self):
|
|
for extra_spec in ('0', 'false', 'False'):
|
|
for image_prop in ('0', 'false', 'False'):
|
|
self._test_encrypted_memory_support_not_required(
|
|
extra_specs={'hw:mem_encryption': extra_spec},
|
|
image=objects.ImageMeta(
|
|
properties=objects.ImageMetaProps(
|
|
hw_mem_encryption=image_prop))
|
|
)
|
|
|
|
def _test_encrypted_memory_support_conflict(self, extra_spec,
|
|
image_prop_in,
|
|
image_prop_out):
|
|
# NOTE(aspiers): hw_mem_encryption image property is a
|
|
# FlexibleBooleanField, so the result should always be coerced
|
|
# to a boolean.
|
|
self.assertIsInstance(image_prop_out, bool)
|
|
|
|
image = objects.ImageMeta(
|
|
name=self.image_name,
|
|
properties=objects.ImageMetaProps(
|
|
hw_mem_encryption=image_prop_in)
|
|
)
|
|
|
|
reqspec = self._get_request_spec(
|
|
extra_specs={'hw:mem_encryption': extra_spec},
|
|
image=image)
|
|
|
|
# Sanity check that our test request spec has an extra_specs
|
|
# dict, which is needed in order for there to be a conflict.
|
|
self.assertIn('flavor', reqspec)
|
|
self.assertIn('extra_specs', reqspec.flavor)
|
|
|
|
error = (
|
|
"Flavor %(flavor_name)s has hw:mem_encryption extra spec "
|
|
"explicitly set to %(flavor_val)s, conflicting with "
|
|
"image %(image_name)s which has hw_mem_encryption property "
|
|
"explicitly set to %(image_val)s"
|
|
)
|
|
exc = self.assertRaises(
|
|
exception.FlavorImageConflict,
|
|
utils.ResourceRequest, reqspec
|
|
)
|
|
error_data = {
|
|
'flavor_name': self.flavor_name,
|
|
'flavor_val': extra_spec,
|
|
'image_name': self.image_name,
|
|
'image_val': image_prop_out,
|
|
}
|
|
self.assertEqual(error % error_data, str(exc))
|
|
|
|
def test_encrypted_memory_support_conflict1(self):
|
|
for extra_spec in ('0', 'false', 'False'):
|
|
for image_prop_in in ('1', 'true', 'True'):
|
|
self._test_encrypted_memory_support_conflict(
|
|
extra_spec, image_prop_in, True
|
|
)
|
|
|
|
def test_encrypted_memory_support_conflict2(self):
|
|
for extra_spec in ('1', 'true', 'True'):
|
|
for image_prop_in in ('0', 'false', 'False'):
|
|
self._test_encrypted_memory_support_conflict(
|
|
extra_spec, image_prop_in, False
|
|
)
|
|
|
|
@mock.patch.object(utils, 'LOG')
|
|
def _test_encrypted_memory_support_required(self, requesters, extra_specs,
|
|
mock_log, image=None):
|
|
resreq = self._get_resource_request(extra_specs, image)
|
|
expected = self._get_expected_resource_request(True)
|
|
|
|
self.assertResourceRequestsEqual(expected, resreq)
|
|
mock_log.debug.assert_has_calls([
|
|
mock.call('Added %s=1 to requested resources',
|
|
orc.MEM_ENCRYPTION_CONTEXT)
|
|
])
|
|
|
|
def test_encrypted_memory_support_extra_spec(self):
|
|
for extra_spec in ('1', 'true', 'True'):
|
|
self._test_encrypted_memory_support_required(
|
|
'hw:mem_encryption extra spec',
|
|
{'hw:mem_encryption': extra_spec},
|
|
image=objects.ImageMeta(
|
|
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
|
properties=objects.ImageMetaProps(
|
|
hw_machine_type='q35',
|
|
hw_firmware_type='uefi'))
|
|
)
|
|
|
|
def test_encrypted_memory_support_image_prop(self):
|
|
for image_prop in ('1', 'true', 'True'):
|
|
self._test_encrypted_memory_support_required(
|
|
'hw_mem_encryption image property',
|
|
{},
|
|
image=objects.ImageMeta(
|
|
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
|
name=self.image_name,
|
|
properties=objects.ImageMetaProps(
|
|
hw_machine_type='q35',
|
|
hw_firmware_type='uefi',
|
|
hw_mem_encryption=image_prop))
|
|
)
|
|
|
|
def test_encrypted_memory_support_both_required(self):
|
|
for extra_spec in ('1', 'true', 'True'):
|
|
for image_prop in ('1', 'true', 'True'):
|
|
self._test_encrypted_memory_support_required(
|
|
'hw:mem_encryption extra spec and '
|
|
'hw_mem_encryption image property',
|
|
{'hw:mem_encryption': extra_spec},
|
|
image=objects.ImageMeta(
|
|
id='005249be-3c2f-4351-9df7-29bb13c21b14',
|
|
name=self.image_name,
|
|
properties=objects.ImageMetaProps(
|
|
hw_machine_type='q35',
|
|
hw_firmware_type='uefi',
|
|
hw_mem_encryption=image_prop))
|
|
)
|
|
|
|
|
|
class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
|
|
"""These test cases assert what happens when the group policy is missing
|
|
from the flavor but more than one numbered request group is requested from
|
|
various sources. Note that while image can provide required traits for the
|
|
resource request those traits are always added to the unnumbered group so
|
|
image cannot be a source of additional numbered groups.
|
|
"""
|
|
|
|
def setUp(self):
|
|
super(TestResourcesFromRequestGroupDefaultPolicy, self).setUp()
|
|
self.context = nova_context.get_admin_context()
|
|
self.port_group1 = objects.RequestGroup.from_port_request(
|
|
self.context, uuids.port1,
|
|
port_resource_request={
|
|
"resources": {
|
|
"NET_BW_IGR_KILOBIT_PER_SEC": 1000,
|
|
"NET_BW_EGR_KILOBIT_PER_SEC": 1000},
|
|
"required": ["CUSTOM_PHYSNET_2",
|
|
"CUSTOM_VNIC_TYPE_NORMAL"]
|
|
})
|
|
self.port_group2 = objects.RequestGroup.from_port_request(
|
|
self.context, uuids.port2,
|
|
port_resource_request={
|
|
"resources": {
|
|
"NET_BW_IGR_KILOBIT_PER_SEC": 2000,
|
|
"NET_BW_EGR_KILOBIT_PER_SEC": 2000},
|
|
"required": ["CUSTOM_PHYSNET_3",
|
|
"CUSTOM_VNIC_TYPE_DIRECT"]
|
|
})
|
|
self.image = objects.ImageMeta(properties=objects.ImageMetaProps())
|
|
|
|
def test_one_group_from_flavor_dont_warn(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
|
extra_specs={
|
|
'resources1:CUSTOM_BAR': '2',
|
|
})
|
|
request_spec = objects.RequestSpec(
|
|
flavor=flavor, image=self.image, requested_resources=[])
|
|
|
|
rr = utils.resources_from_request_spec(
|
|
self.context, request_spec, host_manager=mock.Mock())
|
|
|
|
log = self.stdlog.logger.output
|
|
self.assertNotIn(
|
|
"There is more than one numbered request group in the allocation "
|
|
"candidate query but the flavor did not specify any group policy.",
|
|
log)
|
|
self.assertNotIn(
|
|
"To avoid the placement failure nova defaults the group policy to "
|
|
"'none'.",
|
|
log)
|
|
self.assertIsNone(rr.group_policy)
|
|
self.assertNotIn('group_policy=none', rr.to_querystring())
|
|
|
|
def test_one_group_from_port_dont_warn(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
|
extra_specs={})
|
|
request_spec = objects.RequestSpec(
|
|
flavor=flavor, image=self.image,
|
|
requested_resources=[self.port_group1])
|
|
|
|
rr = utils.resources_from_request_spec(
|
|
self.context, request_spec, host_manager=mock.Mock())
|
|
|
|
log = self.stdlog.logger.output
|
|
self.assertNotIn(
|
|
"There is more than one numbered request group in the allocation "
|
|
"candidate query but the flavor did not specify any group policy.",
|
|
log)
|
|
self.assertNotIn(
|
|
"To avoid the placement failure nova defaults the group policy to "
|
|
"'none'.",
|
|
log)
|
|
self.assertIsNone(rr.group_policy)
|
|
self.assertNotIn('group_policy=none', rr.to_querystring())
|
|
|
|
def test_two_groups_from_flavor_only_warns(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
|
extra_specs={
|
|
'resources1:CUSTOM_BAR': '2',
|
|
'resources2:CUSTOM_FOO': '1'
|
|
})
|
|
request_spec = objects.RequestSpec(
|
|
flavor=flavor, image=self.image, requested_resources=[])
|
|
|
|
rr = utils.resources_from_request_spec(
|
|
self.context, request_spec, host_manager=mock.Mock())
|
|
|
|
log = self.stdlog.logger.output
|
|
self.assertIn(
|
|
"There is more than one numbered request group in the allocation "
|
|
"candidate query but the flavor did not specify any group policy.",
|
|
log)
|
|
self.assertNotIn(
|
|
"To avoid the placement failure nova defaults the group policy to "
|
|
"'none'.",
|
|
log)
|
|
self.assertIsNone(rr.group_policy)
|
|
self.assertNotIn('group_policy', rr.to_querystring())
|
|
|
|
def test_one_group_from_flavor_one_from_port_policy_defaulted(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
|
extra_specs={
|
|
'resources1:CUSTOM_BAR': '2',
|
|
})
|
|
request_spec = objects.RequestSpec(
|
|
flavor=flavor, image=self.image,
|
|
requested_resources=[self.port_group1])
|
|
|
|
rr = utils.resources_from_request_spec(
|
|
self.context, request_spec, host_manager=mock.Mock())
|
|
|
|
log = self.stdlog.logger.output
|
|
self.assertIn(
|
|
"There is more than one numbered request group in the allocation "
|
|
"candidate query but the flavor did not specify any group policy.",
|
|
log)
|
|
self.assertIn(
|
|
"To avoid the placement failure nova defaults the group policy to "
|
|
"'none'.",
|
|
log)
|
|
self.assertEqual('none', rr.group_policy)
|
|
self.assertIn('group_policy=none', rr.to_querystring())
|
|
|
|
def test_two_groups_from_ports_policy_defaulted(self):
|
|
flavor = objects.Flavor(
|
|
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
|
|
extra_specs={})
|
|
request_spec = objects.RequestSpec(
|
|
flavor=flavor, image=self.image,
|
|
requested_resources=[self.port_group1, self.port_group2])
|
|
|
|
rr = utils.resources_from_request_spec(
|
|
self.context, request_spec, host_manager=mock.Mock())
|
|
|
|
log = self.stdlog.logger.output
|
|
self.assertIn(
|
|
"There is more than one numbered request group in the allocation "
|
|
"candidate query but the flavor did not specify any group policy.",
|
|
log)
|
|
self.assertIn(
|
|
"To avoid the placement failure nova defaults the group policy to "
|
|
"'none'.",
|
|
log)
|
|
self.assertEqual('none', rr.group_policy)
|
|
self.assertIn('group_policy=none', rr.to_querystring())
|