Add engine scheduler unit test

Change-Id: Ib210fff443b34e223141bc7490ddcd4e2e49f48f
This commit is contained in:
yuntongjin 2016-09-27 09:27:14 +08:00
parent 99424b71db
commit c7b473e81f
9 changed files with 560 additions and 0 deletions

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
import os
from oslo_config import cfg
@ -95,3 +96,25 @@ class TestCase(base.BaseTestCase):
return os.path.join(root, project_file)
else:
return root
def mock_object(self, obj, attr_name, *args, **kwargs):
"""Use python mock to mock an object attribute
Mocks the specified objects attribute with the given value.
Automatically performs 'addCleanup' for the mock.
"""
patcher = mock.patch.object(obj, attr_name, *args, **kwargs)
result = patcher.start()
self.addCleanup(patcher.stop)
return result
def override_config(self, name, override, group=None):
"""Cleanly override CONF variables."""
CONF.set_override(name, override, group)
self.addCleanup(CONF.clear_override, name, group)
def flags(self, **kw):
"""Override CONF variables for a test."""
for k, v in kw.items():
self.override_config(k, v)

View File

@ -0,0 +1,62 @@
# Copyright (c) 2016 OpenStack Foundation
# 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.
#
"""
Fakes For Scheduler tests.
"""
from oslo_versionedobjects import base as object_base
from nimble.engine.scheduler import filter_scheduler
from nimble.engine.scheduler import node_manager
from nimble.objects import base
from nimble.objects import fields as object_fields
class FakeFilterScheduler(filter_scheduler.FilterScheduler):
def __init__(self, *args, **kwargs):
super(FakeFilterScheduler, self).__init__(*args, **kwargs)
self.node_manager = node_manager.NodeManager()
@base.NimbleObjectRegistry.register
class FakeNode(base.NimbleObject, object_base.VersionedObjectDictCompat):
fields = {
'id': object_fields.IntegerField(),
'uuid': object_fields.UUIDField(nullable=True),
'properties': object_fields.FlexibleDictField(nullable=True),
}
fakenode1 = FakeNode(id=1, uuid='1a617131-cdbc-45dc-afff-f21f17ae054e',
properties={'capabilities': '',
'availability_zone': 'az1',
'instance_type': 'type1'})
fakenode2 = FakeNode(id=2, uuid='2a617131-cdbc-45dc-afff-f21f17ae054e',
properties={'capabilities': '',
'availability_zone': 'az2',
'instance_type': 'type2'})
fakenode3 = FakeNode(id=3, uuid='3a617131-cdbc-45dc-afff-f21f17ae054e',
properties={'capabilities': '',
'availability_zone': 'az3',
'instance_type': 'type3'})
class FakeNodeState(node_manager.NodeState):
def __init__(self, node, attribute_dict):
super(FakeNodeState, self).__init__(node)
for (key, val) in attribute_dict.items():
setattr(self, key, val)

View File

@ -0,0 +1,174 @@
# Copyright (c) 2016 OpenStack Foundation.
#
# 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 mock
from nimble.engine.scheduler import base_filter
from nimble.tests import base as test
class TestBaseFilter(test.TestCase):
def setUp(self):
super(TestBaseFilter, self).setUp()
self.filter = base_filter.BaseFilter()
def test_filter_one_is_called(self):
filters = [1, 2, 3, 4]
filter_properties = {'x': 'y'}
self.filter._filter_one = mock.Mock()
self.filter._filter_one.side_effect = [False, True, True, False]
calls = [mock.call(i, filter_properties) for i in filters]
result = list(self.filter.filter_all(filters, filter_properties))
self.assertEqual([2, 3], result)
self.filter._filter_one.assert_has_calls(calls)
class FakeExtension(object):
def __init__(self, plugin):
self.plugin = plugin
class BaseFakeFilter(base_filter.BaseFilter):
pass
class FakeFilter1(BaseFakeFilter):
"""Derives from BaseFakeFilter and has a fake entry point defined.
Entry point is returned by fake ExtensionManager.
Should be included in the output of all_classes.
"""
pass
class FakeFilter2(BaseFakeFilter):
"""Derives from BaseFakeFilter but has no entry point.
Should be not included in all_classes.
"""
pass
class FakeFilter3(base_filter.BaseFilter):
"""Does not derive from BaseFakeFilter.
Should not be included.
"""
pass
class FakeFilter4(BaseFakeFilter):
"""Derives from BaseFakeFilter and has an entry point.
Should be included.
"""
pass
class FakeFilter5(BaseFakeFilter):
"""Derives from BaseFakeFilter but has no entry point.
Should not be included.
"""
run_filter_once_per_request = True
pass
class FilterA(base_filter.BaseFilter):
def filter_all(self, list_objs, filter_properties):
# return all but the first object
return list_objs[1:]
class FilterB(base_filter.BaseFilter):
def filter_all(self, list_objs, filter_properties):
# return an empty list
return None
class FakeExtensionManager(list):
def __init__(self, namespace):
classes = [FakeFilter1, FakeFilter3, FakeFilter4]
exts = map(FakeExtension, classes)
super(FakeExtensionManager, self).__init__(exts)
self.namespace = namespace
class TestBaseFilterHandler(test.TestCase):
def setUp(self):
super(TestBaseFilterHandler, self).setUp()
self.mock_object(base_filter.base_handler.extension,
'ExtensionManager', FakeExtensionManager)
self.handler = base_filter.BaseFilterHandler(BaseFakeFilter,
'fake_filters')
def test_get_all_classes(self):
# In order for a FakeFilter to be returned by get_all_classes, it has
# to comply with these rules:
# * It must be derived from BaseFakeFilter
# AND
# * It must have a python entrypoint assigned (returned by
# FakeExtensionManager)
expected = [FakeFilter1, FakeFilter4]
result = self.handler.get_all_classes()
self.assertEqual(expected, result)
def _get_filtered_objects(self, filter_classes, index=0):
filter_objs_initial = [1, 2, 3, 4]
filter_properties = {'x': 'y'}
return self.handler.get_filtered_objects(filter_classes,
filter_objs_initial,
filter_properties,
index)
@mock.patch.object(FakeFilter4, 'filter_all')
@mock.patch.object(FakeFilter3, 'filter_all', return_value=None)
def test_get_filtered_objects_return_none(self, fake3_filter_all,
fake4_filter_all):
filter_classes = [FakeFilter1, FakeFilter2, FakeFilter3, FakeFilter4]
result = self._get_filtered_objects(filter_classes)
self.assertIsNone(result)
self.assertFalse(fake4_filter_all.called)
def test_get_filtered_objects(self):
filter_objs_expected = [1, 2, 3, 4]
filter_classes = [FakeFilter1, FakeFilter2, FakeFilter3, FakeFilter4]
result = self._get_filtered_objects(filter_classes)
self.assertEqual(filter_objs_expected, result)
def test_get_filtered_objects_with_filter_run_once(self):
filter_objs_expected = [1, 2, 3, 4]
filter_classes = [FakeFilter5]
with mock.patch.object(FakeFilter5, 'filter_all',
return_value=filter_objs_expected
) as fake5_filter_all:
result = self._get_filtered_objects(filter_classes)
self.assertEqual(filter_objs_expected, result)
self.assertEqual(1, fake5_filter_all.call_count)
result = self._get_filtered_objects(filter_classes, index=1)
self.assertEqual(filter_objs_expected, result)
self.assertEqual(1, fake5_filter_all.call_count)
result = self._get_filtered_objects(filter_classes, index=2)
self.assertEqual(filter_objs_expected, result)
self.assertEqual(1, fake5_filter_all.call_count)

View File

@ -0,0 +1,86 @@
# Copyright (c) 2016 OpenStack Foundation
# 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 NodeManager
"""
import mock
from nimble.common import exception
from nimble.engine.scheduler import filters
from nimble.engine.scheduler import node_manager
from nimble.engine.scheduler.node_manager import NodeState
from nimble.tests import base as test
from nimble.tests.unit.engine.scheduler import fakes
class FakeFilterClass1(filters.BaseNodeFilter):
def node_passes(self, node_state, filter_properties):
pass
class FakeFilterClass2(filters.BaseNodeFilter):
def node_passes(self, node_state, filter_properties):
pass
class NodeManagerTestCase(test.TestCase):
"""Test case for NodeManager class."""
def setUp(self):
super(NodeManagerTestCase, self).setUp()
self.node_manager = node_manager.NodeManager()
self.fake_nodes = [NodeState(fakes.fakenode1),
NodeState(fakes.fakenode2),
NodeState(fakes.fakenode3)]
def test_choose_node_filters_not_found(self):
self.override_config('scheduler_default_filters', 'FakeFilterClass3',
'scheduler')
self.node_manager.filter_classes = [FakeFilterClass1,
FakeFilterClass2]
self.assertRaises(exception.SchedulerNodeFilterNotFound,
self.node_manager._choose_node_filters, None)
def test_choose_node_filters(self):
self.override_config('scheduler_default_filters', 'FakeFilterClass2',
group='scheduler')
self.node_manager.filter_classes = [FakeFilterClass1,
FakeFilterClass2]
# Test returns 1 correct filter class
filter_classes = self.node_manager._choose_node_filters(None)
self.assertEqual(1, len(filter_classes))
self.assertEqual('FakeFilterClass2', filter_classes[0].__name__)
@mock.patch('nimble.engine.scheduler.node_manager.NodeManager.'
'_choose_node_filters')
def test_get_filtered_nodes(self, _mock_choose_node_filters):
filter_class = FakeFilterClass1
mock_func = mock.Mock()
mock_func.return_value = True
filter_class._filter_one = mock_func
_mock_choose_node_filters.return_value = [filter_class]
fake_properties = {'moo': 1, 'cow': 2}
expected = []
for fake_node in self.fake_nodes:
expected.append(mock.call(fake_node, fake_properties))
result = self.node_manager.get_filtered_nodes(self.fake_nodes,
fake_properties)
self.assertEqual(expected, mock_func.call_args_list)
self.assertEqual(set(self.fake_nodes), set(result))

View File

@ -0,0 +1,138 @@
# Copyright 2016 OpenStack Foundation
# 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 PickledScheduler.
"""
import datetime
from oslo_serialization import jsonutils
import six
from nimble.engine.scheduler import scheduler_options
from nimble.tests import base as test
class FakeSchedulerOptions(scheduler_options.SchedulerOptions):
def __init__(self, last_checked, now, file_old, file_now, data, filedata):
super(FakeSchedulerOptions, self).__init__()
# Change internals ...
self.last_modified = file_old
self.last_checked = last_checked
self.data = data
# For overrides ...
self._time_now = now
self._file_now = file_now
self._file_data = filedata
self.file_was_loaded = False
def _get_file_timestamp(self, filename):
return self._file_now
def _get_file_handle(self, filename):
self.file_was_loaded = True
return six.StringIO(self._file_data)
def _get_time_now(self):
return self._time_now
class SchedulerOptionsTestCase(test.TestCase):
def test_get_configuration_first_time_no_flag(self):
last_checked = None
now = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_old = None
file_now = datetime.datetime(2012, 1, 1, 1, 1, 1)
data = dict(a=1, b=2, c=3)
jdata = jsonutils.dumps(data)
fake = FakeSchedulerOptions(last_checked, now, file_old, file_now,
{}, jdata)
self.assertEqual({}, fake.get_configuration())
self.assertFalse(fake.file_was_loaded)
def test_get_configuration_first_time_empty_file(self):
last_checked = None
now = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_old = None
file_now = datetime.datetime(2012, 1, 1, 1, 1, 1)
jdata = ""
fake = FakeSchedulerOptions(last_checked, now, file_old, file_now,
{}, jdata)
self.assertEqual({}, fake.get_configuration('foo.json'))
self.assertTrue(fake.file_was_loaded)
def test_get_configuration_first_time_happy_day(self):
last_checked = None
now = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_old = None
file_now = datetime.datetime(2012, 1, 1, 1, 1, 1)
data = dict(a=1, b=2, c=3)
jdata = jsonutils.dumps(data)
fake = FakeSchedulerOptions(last_checked, now, file_old, file_now,
{}, jdata)
self.assertEqual(data, fake.get_configuration('foo.json'))
self.assertTrue(fake.file_was_loaded)
def test_get_configuration_second_time_no_change(self):
last_checked = datetime.datetime(2011, 1, 1, 1, 1, 1)
now = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_old = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_now = datetime.datetime(2012, 1, 1, 1, 1, 1)
data = dict(a=1, b=2, c=3)
jdata = jsonutils.dumps(data)
fake = FakeSchedulerOptions(last_checked, now, file_old, file_now,
data, jdata)
self.assertEqual(data, fake.get_configuration('foo.json'))
self.assertFalse(fake.file_was_loaded)
def test_get_configuration_second_time_too_fast(self):
last_checked = datetime.datetime(2011, 1, 1, 1, 1, 1)
now = datetime.datetime(2011, 1, 1, 1, 1, 2)
file_old = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_now = datetime.datetime(2013, 1, 1, 1, 1, 1)
old_data = dict(a=1, b=2, c=3)
data = dict(a=11, b=12, c=13)
jdata = jsonutils.dumps(data)
fake = FakeSchedulerOptions(last_checked, now, file_old, file_now,
old_data, jdata)
self.assertEqual(old_data, fake.get_configuration('foo.json'))
self.assertFalse(fake.file_was_loaded)
def test_get_configuration_second_time_change(self):
last_checked = datetime.datetime(2011, 1, 1, 1, 1, 1)
now = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_old = datetime.datetime(2012, 1, 1, 1, 1, 1)
file_now = datetime.datetime(2013, 1, 1, 1, 1, 1)
old_data = dict(a=1, b=2, c=3)
data = dict(a=11, b=12, c=13)
jdata = jsonutils.dumps(data)
fake = FakeSchedulerOptions(last_checked, now, file_old, file_now,
old_data, jdata)
self.assertEqual(data, fake.get_configuration('foo.json'))
self.assertTrue(fake.file_was_loaded)

View File

@ -0,0 +1,54 @@
# Copyright 2016 OpenStack Foundation.
# 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 Scheduler weights.
"""
from nimble.engine.scheduler import base_weight
from nimble.tests import base as test
class TestWeightHandler(test.TestCase):
def test_no_multiplier(self):
class FakeWeigher(base_weight.BaseWeigher):
def _weigh_object(self, *args, **kwargs):
pass
self.assertEqual(1.0,
FakeWeigher().weight_multiplier())
def test_no_weight_object(self):
class FakeWeigher(base_weight.BaseWeigher):
def weight_multiplier(self, *args, **kwargs):
pass
self.assertRaises(TypeError,
FakeWeigher)
def test_normalization(self):
# weight_list, expected_result, minval, maxval
map_ = (
((), (), None, None),
((0.0, 0.0), (0.0, 0.0), None, None),
((1.0, 1.0), (0.0, 0.0), None, None),
((20.0, 50.0), (0.0, 1.0), None, None),
((20.0, 50.0), (0.0, 0.375), None, 100.0),
((20.0, 50.0), (0.4, 1.0), 0.0, None),
((20.0, 50.0), (0.2, 0.5), 0.0, 100.0),
)
for seq, result, minval, maxval in map_:
ret = base_weight.normalize(seq, minval=minval, maxval=maxval)
self.assertEqual(result, tuple(ret))

View File

@ -0,0 +1,22 @@
# Copyright 2016 Intel, Inc.
#
# 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.
IMAGE_ID = 'e79161cd-5f9d-4007-8823-81a807a64332'
INSTANCE_ID = 'fa617131-cdbc-45dc-afff-f21f17ae054e'
PROJECT_ID = '89afd400-b646-4bbc-b12b-c0a4d63e5bd3'
PROJECT2_ID = '452ebfbc-55d9-402a-87af-65061916c24b'
PROJECT3_ID = 'f6c912d7-bf30-4b12-af81-a9e0b2f85f85'
USER_ID = 'c853ca26-e8ea-4797-8a52-ee124a013d0e'
USER2_ID = '95f7b7ed-bd7f-426e-b05f-f1ffeb4f09df'

View File

@ -7,6 +7,7 @@ hacking<0.12,>=0.11.0 # Apache-2.0
coverage>=3.6 # Apache-2.0
python-subunit>=0.0.18 # Apache-2.0/BSD
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
ddt>=1.0.1 # MIT
oslosphinx>=4.7.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
testrepository>=0.0.18 # Apache-2.0/BSD