# Copyright (c) 2013 Hewlett-Packard Development Company, L.P. # 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. """Tests for the compute extra resources framework.""" from oslo_config import cfg from stevedore import extension from stevedore import named from nova.compute import resources from nova.compute.resources import base from nova import context from nova.objects import flavor as flavor_obj from nova import test CONF = cfg.CONF class FakeResourceHandler(resources.ResourceHandler): def __init__(self, extensions): self._mgr = \ named.NamedExtensionManager.make_test_instance(extensions) class FakeResource(base.Resource): def __init__(self): self.total_res = 0 self.used_res = 0 def _get_requested(self, usage): if 'extra_specs' not in usage: return if self.resource_name not in usage['extra_specs']: return req = usage['extra_specs'][self.resource_name] return int(req) def _get_limit(self, limits): if self.resource_name not in limits: return limit = limits[self.resource_name] return int(limit) def reset(self, resources, driver): self.total_res = 0 self.used_res = 0 def test(self, usage, limits): requested = self._get_requested(usage) if not requested: return limit = self._get_limit(limits) if not limit: return free = limit - self.used_res if requested <= free: return else: return ('Free %(free)d < requested %(requested)d ' % {'free': free, 'requested': requested}) def add_instance(self, usage): requested = self._get_requested(usage) if requested: self.used_res += requested def remove_instance(self, usage): requested = self._get_requested(usage) if requested: self.used_res -= requested def write(self, resources): pass def report_free(self): return "Free %s" % (self.total_res - self.used_res) class ResourceA(FakeResource): def reset(self, resources, driver): # ResourceA uses a configuration option self.total_res = int(CONF.resA) self.used_res = 0 self.resource_name = 'resource:resA' def write(self, resources): resources['resA'] = self.total_res resources['used_resA'] = self.used_res class ResourceB(FakeResource): def reset(self, resources, driver): # ResourceB uses resource details passed in parameter resources self.total_res = resources['resB'] self.used_res = 0 self.resource_name = 'resource:resB' def write(self, resources): resources['resB'] = self.total_res resources['used_resB'] = self.used_res def fake_flavor_obj(**updates): flavor = flavor_obj.Flavor() flavor.id = 1 flavor.name = 'fakeflavor' flavor.memory_mb = 8000 flavor.vcpus = 3 flavor.root_gb = 11 flavor.ephemeral_gb = 4 flavor.swap = 0 flavor.rxtx_factor = 1.0 flavor.vcpu_weight = 1 if updates: flavor.update(updates) return flavor class BaseTestCase(test.NoDBTestCase): def _initialize_used_res_counter(self): # Initialize the value for the used resource for ext in self.r_handler._mgr.extensions: ext.obj.used_res = 0 def setUp(self): super(BaseTestCase, self).setUp() # initialize flavors and stub get_by_id to # get flavors from here self._flavors = {} self.ctxt = context.get_admin_context() # Create a flavor without extra_specs defined _flavor_id = 1 _flavor = fake_flavor_obj(id=_flavor_id) self._flavors[_flavor_id] = _flavor # Create a flavor with extra_specs defined _flavor_id = 2 requested_resA = 5 requested_resB = 7 requested_resC = 7 _extra_specs = {'resource:resA': requested_resA, 'resource:resB': requested_resB, 'resource:resC': requested_resC} _flavor = fake_flavor_obj(id=_flavor_id, extra_specs=_extra_specs) self._flavors[_flavor_id] = _flavor # create fake resource extensions and resource handler _extensions = [ extension.Extension('resA', None, ResourceA, ResourceA()), extension.Extension('resB', None, ResourceB, ResourceB()), ] self.r_handler = FakeResourceHandler(_extensions) # Resources details can be passed to each plugin or can be specified as # configuration options driver_resources = {'resB': 5} CONF.resA = '10' # initialise the resources self.r_handler.reset_resources(driver_resources, None) def test_update_from_instance_with_extra_specs(self): # Flavor with extra_specs _flavor_id = 2 sign = 1 self.r_handler.update_from_instance(self._flavors[_flavor_id], sign) expected_resA = self._flavors[_flavor_id].extra_specs['resource:resA'] expected_resB = self._flavors[_flavor_id].extra_specs['resource:resB'] self.assertEqual(int(expected_resA), self.r_handler._mgr['resA'].obj.used_res) self.assertEqual(int(expected_resB), self.r_handler._mgr['resB'].obj.used_res) def test_update_from_instance_without_extra_specs(self): # Flavor id without extra spec _flavor_id = 1 self._initialize_used_res_counter() self.r_handler.resource_list = [] sign = 1 self.r_handler.update_from_instance(self._flavors[_flavor_id], sign) self.assertEqual(0, self.r_handler._mgr['resA'].obj.used_res) self.assertEqual(0, self.r_handler._mgr['resB'].obj.used_res) def test_write_resources(self): self._initialize_used_res_counter() extra_resources = {} expected = {'resA': 10, 'used_resA': 0, 'resB': 5, 'used_resB': 0} self.r_handler.write_resources(extra_resources) self.assertEqual(expected, extra_resources) def test_test_resources_without_extra_specs(self): limits = {} # Flavor id without extra_specs flavor = self._flavors[1] result = self.r_handler.test_resources(flavor, limits) self.assertEqual([None, None], result) def test_test_resources_with_limits_for_different_resource(self): limits = {'resource:resC': 20} # Flavor id with extra_specs flavor = self._flavors[2] result = self.r_handler.test_resources(flavor, limits) self.assertEqual([None, None], result) def test_passing_test_resources(self): limits = {'resource:resA': 10, 'resource:resB': 20} # Flavor id with extra_specs flavor = self._flavors[2] self._initialize_used_res_counter() result = self.r_handler.test_resources(flavor, limits) self.assertEqual([None, None], result) def test_failing_test_resources_for_single_resource(self): limits = {'resource:resA': 4, 'resource:resB': 20} # Flavor id with extra_specs flavor = self._flavors[2] self._initialize_used_res_counter() result = self.r_handler.test_resources(flavor, limits) expected = ['Free 4 < requested 5 ', None] self.assertEqual(sorted(expected, key=str), sorted(result, key=str)) def test_empty_resource_handler(self): """An empty resource handler has no resource extensions, should have no effect, and should raise no exceptions. """ empty_r_handler = FakeResourceHandler([]) resources = {} empty_r_handler.reset_resources(resources, None) flavor = self._flavors[1] sign = 1 empty_r_handler.update_from_instance(flavor, sign) limits = {} test_result = empty_r_handler.test_resources(flavor, limits) self.assertEqual([], test_result) sign = -1 empty_r_handler.update_from_instance(flavor, sign) extra_resources = {} expected_extra_resources = extra_resources empty_r_handler.write_resources(extra_resources) self.assertEqual(expected_extra_resources, extra_resources) empty_r_handler.report_free_resources()