From 7a73fd7f9f6c1bc4cbe30e2f1184ad87e2def6f8 Mon Sep 17 00:00:00 2001 From: Vincent Llorens Date: Fri, 10 Jun 2016 17:31:49 +0200 Subject: [PATCH] add more unit tests to managers In this commit we add several unit tests that covers some managers. We chose to write unit tests for managers that are closest to Synergy core functionalities in a first stage. Also, we test only method specific to each manager as the base methods of Manager class have been tested in Synergy. Managers with unit tests: - FairShareManager - QueueManager - QuotaManager (and DynamicQuota) - SchedulerManager Change-Id: I831649e48b36bbaf94f6790fbf1a8954636fac32 --- .../tests/test_dynamic_quota.py | 26 -- .../tests/unit/__init__.py | 0 .../tests/{ => unit}/base.py | 0 .../tests/unit/test_dynamic_quota.py | 116 ++++++ .../tests/unit/test_fairshare_manager.py | 137 +++++++ .../tests/unit/test_queue_manager.py | 335 ++++++++++++++++++ .../tests/unit/test_scheduler_manager.py | 105 ++++++ test-requirements.txt | 2 + 8 files changed, 695 insertions(+), 26 deletions(-) delete mode 100644 synergy_scheduler_manager/tests/test_dynamic_quota.py create mode 100644 synergy_scheduler_manager/tests/unit/__init__.py rename synergy_scheduler_manager/tests/{ => unit}/base.py (100%) create mode 100644 synergy_scheduler_manager/tests/unit/test_dynamic_quota.py create mode 100644 synergy_scheduler_manager/tests/unit/test_fairshare_manager.py create mode 100644 synergy_scheduler_manager/tests/unit/test_queue_manager.py create mode 100644 synergy_scheduler_manager/tests/unit/test_scheduler_manager.py diff --git a/synergy_scheduler_manager/tests/test_dynamic_quota.py b/synergy_scheduler_manager/tests/test_dynamic_quota.py deleted file mode 100644 index 9f25dbd..0000000 --- a/synergy_scheduler_manager/tests/test_dynamic_quota.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 synergy_scheduler_manager.quota_manager import DynamicQuota -from synergy_scheduler_manager.tests import base - - -class TestDynamicQuota(base.TestCase): - - def setUp(self): - super(TestDynamicQuota, self).setUp() - self.dyn_quota = DynamicQuota() - - def test_add_project(self): - project_id = 1 - self.dyn_quota.addProject(project_id, "test_project") - self.assertIn(project_id, self.dyn_quota.getProjects()) diff --git a/synergy_scheduler_manager/tests/unit/__init__.py b/synergy_scheduler_manager/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synergy_scheduler_manager/tests/base.py b/synergy_scheduler_manager/tests/unit/base.py similarity index 100% rename from synergy_scheduler_manager/tests/base.py rename to synergy_scheduler_manager/tests/unit/base.py diff --git a/synergy_scheduler_manager/tests/unit/test_dynamic_quota.py b/synergy_scheduler_manager/tests/unit/test_dynamic_quota.py new file mode 100644 index 0000000..7fb8457 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_dynamic_quota.py @@ -0,0 +1,116 @@ +# 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 synergy_scheduler_manager.quota_manager import DynamicQuota +from synergy_scheduler_manager.tests.unit import base + + +class TestDynamicQuota(base.TestCase): + + def setUp(self): + super(TestDynamicQuota, self).setUp() + self.dyn_quota = DynamicQuota() + + def test_get_add_project_no_usage(self): + self.dyn_quota.addProject(prj_id=1, prj_name="test_project") + + project = self.dyn_quota.getProject(1) + self.assertEqual("test_project", project["name"]) + self.assertEqual(0, project["cores"]) + self.assertEqual(0, project["ram"]) + self.assertEqual({"active": [], "pending": []}, project["instances"]) + self.assertEqual(0, project["TTL"]) + + def test_get_add_project_with_usage(self): + fake_usage = {"cores": 5, "ram": 12, "instances": ["a", "b"]} + self.dyn_quota.addProject(prj_id=1, prj_name="test", usage=fake_usage) + + project = self.dyn_quota.getProject(1) + self.assertEqual("test", project["name"]) + self.assertEqual(5, project["cores"]) + self.assertEqual(12, project["ram"]) + self.assertEqual({"active": ["a", "b"], "pending": []}, + project["instances"]) + self.assertEqual(0, project["TTL"]) + self.assertEqual(12, self.dyn_quota.ram["in_use"]) + self.assertEqual(5, self.dyn_quota.cores["in_use"]) + + def test_get_size(self): + size = self.dyn_quota.getSize() + self.assertEqual(0, size["cores"]) + self.assertEqual(0, size["ram"]) + + def test_set_size(self): + self.dyn_quota.setSize(cores=10, ram=20) + self.assertEqual(10, self.dyn_quota.cores["limit"]) + self.assertEqual(20, self.dyn_quota.ram["limit"]) + + def test_get_projects(self): + self.assertEqual(self.dyn_quota.projects, self.dyn_quota.getProjects()) + + def test_remove_project(self): + self.dyn_quota.addProject(prj_id=1, prj_name="test") + + self.assertIn(1, self.dyn_quota.projects) + + self.dyn_quota.removeProject(1) + self.assertNotIn(1, self.dyn_quota.projects) + + def test_allocate_single_instance(self): + self.dyn_quota.setSize(cores=20, ram=100) + self.dyn_quota.addProject(prj_id=1, prj_name="test") + + self.dyn_quota.allocate("a", prj_id=1, cores=5, ram=10) + + project = self.dyn_quota.getProject(1) + self.assertIn("a", project["instances"]["active"]) + self.assertEqual(5, project["cores"]) + self.assertEqual(10, project["ram"]) + self.assertEqual(5, self.dyn_quota.cores["in_use"]) + self.assertEqual(10, self.dyn_quota.ram["in_use"]) + + def test_allocate_multiple_instances(self): + self.dyn_quota.setSize(cores=30, ram=100) + self.dyn_quota.addProject(prj_id=1, prj_name="test") + + self.dyn_quota.allocate("a", prj_id=1, cores=5, ram=10) + self.dyn_quota.allocate("b", prj_id=1, cores=7, ram=20) + self.dyn_quota.allocate("c", prj_id=1, cores=10, ram=20) + + project = self.dyn_quota.getProject(1) + self.assertIn("a", project["instances"]["active"]) + self.assertIn("b", project["instances"]["active"]) + self.assertIn("c", project["instances"]["active"]) + self.assertEqual(22, project["cores"]) + self.assertEqual(50, project["ram"]) + self.assertEqual(22, self.dyn_quota.cores["in_use"]) + self.assertEqual(50, self.dyn_quota.ram["in_use"]) + + def test_allocate_multiple_projects(self): + self.dyn_quota.setSize(cores=20, ram=100) + self.dyn_quota.addProject(prj_id=1, prj_name="project_A") + self.dyn_quota.addProject(prj_id=2, prj_name="project_B") + + # TODO(vincent): can we allocate the same instance to 2 projects? + self.dyn_quota.allocate("a", prj_id=1, cores=3, ram=10) + self.dyn_quota.allocate("a", prj_id=2, cores=5, ram=15) + + project_a = self.dyn_quota.getProject(1) + project_b = self.dyn_quota.getProject(2) + self.assertIn("a", project_a["instances"]["active"]) + self.assertIn("a", project_b["instances"]["active"]) + self.assertEqual(3, project_a["cores"]) + self.assertEqual(10, project_a["ram"]) + self.assertEqual(5, project_b["cores"]) + self.assertEqual(15, project_b["ram"]) + self.assertEqual(8, self.dyn_quota.cores["in_use"]) + self.assertEqual(25, self.dyn_quota.ram["in_use"]) diff --git a/synergy_scheduler_manager/tests/unit/test_fairshare_manager.py b/synergy_scheduler_manager/tests/unit/test_fairshare_manager.py new file mode 100644 index 0000000..01be132 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_fairshare_manager.py @@ -0,0 +1,137 @@ +# 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 datetime import datetime + +from mock import MagicMock +from mock import patch + +from synergy_scheduler_manager.fairshare_manager import FairShareManager +from synergy_scheduler_manager.keystone_manager import KeystoneManager +from synergy_scheduler_manager.queue_manager import QueueManager +from synergy_scheduler_manager.quota_manager import QuotaManager +from synergy_scheduler_manager.tests.unit import base + + +class TestFairshareManager(base.TestCase): + + def setUp(self): + super(TestFairshareManager, self).setUp() + self.fsmanager = FairShareManager() + + # NOTE(vincent): we cannot import NovaManager in our tests. + # NovaManager depends on the "nova" package (not novaclient), but it is + # not available on PyPI so the test runner will fail to install it. + nova_manager_mock = MagicMock() + + self.fsmanager.managers = { + 'NovaManager': nova_manager_mock(), + 'QueueManager': QueueManager(), + 'QuotaManager': QuotaManager(), + 'KeystoneManager': KeystoneManager()} + + # Mock the configuration since it is initiliazed by synergy-service. + with patch('synergy_scheduler_manager.fairshare_manager.CONF'): + self.fsmanager.setup() + + def test_add_project(self): + self.fsmanager.addProject(prj_id=1, prj_name="test_project", share=5) + + self.assertEqual(1, self.fsmanager.projects[1]["id"]) + self.assertEqual("test_project", self.fsmanager.projects[1]["name"]) + self.assertEqual("dynamic", self.fsmanager.projects[1]["type"]) + self.assertEqual({}, self.fsmanager.projects[1]["users"]) + self.assertEqual({}, self.fsmanager.projects[1]["usage"]) + self.assertEqual(5, self.fsmanager.projects[1]["share"]) + + def test_add_project_no_share(self): + self.fsmanager.addProject(prj_id=1, prj_name="test_project") + + self.assertEqual(1, self.fsmanager.projects[1]["id"]) + self.assertEqual("test_project", self.fsmanager.projects[1]["name"]) + self.assertEqual("dynamic", self.fsmanager.projects[1]["type"]) + self.assertEqual({}, self.fsmanager.projects[1]["users"]) + self.assertEqual({}, self.fsmanager.projects[1]["usage"]) + self.assertEqual(self.fsmanager.default_share, + self.fsmanager.projects[1]["share"]) + + def test_get_project(self): + self.fsmanager.addProject(prj_id=1, prj_name="test_project") + + expected_project = { + "id": 1, + "name": "test_project", + "type": "dynamic", + "users": {}, + "usage": {}, + "share": self.fsmanager.default_share} + self.assertEqual(expected_project, self.fsmanager.getProject(1)) + + def test_get_projects(self): + self.fsmanager.addProject(prj_id=1, prj_name="test1") + self.fsmanager.addProject(prj_id=2, prj_name="test2") + + expected_projects = { + 1: {"id": 1, + "name": "test1", + "type": "dynamic", + "users": {}, + "usage": {}, + "share": self.fsmanager.default_share}, + 2: {"id": 2, + "name": "test2", + "type": "dynamic", + "users": {}, + "usage": {}, + "share": self.fsmanager.default_share}} + self.assertEqual(expected_projects, self.fsmanager.getProjects()) + + def test_remove_project(self): + self.fsmanager.addProject(prj_id=1, prj_name="test") + + self.assertIn(1, self.fsmanager.projects) + self.fsmanager.removeProject(1) + self.assertNotIn(1, self.fsmanager.projects) + + def test_calculate_priority_one_user(self): + self.fsmanager.addProject(prj_id=1, prj_name="test") + + # Define values used for computing the priority + age_weight = self.fsmanager.age_weight = 1.0 + vcpus_weight = self.fsmanager.vcpus_weight = 2.0 + memory_weight = self.fsmanager.memory_weight = 3.0 + datetime_start = datetime(year=2000, month=1, day=1, hour=0, minute=0) + datetime_stop = datetime(year=2000, month=1, day=1, hour=2, minute=0) + minutes = (datetime_stop - datetime_start).seconds / 60 + fairshare_cores = 10 + fairshare_ram = 50 + + # Add a user to the project + self.fsmanager.projects[1]["users"] = { + 1: {"fairshare_cores": fairshare_cores, + "fairshare_ram": fairshare_ram}} + + # Compute the expected priority given the previously defined values + expected_priority = int(age_weight * minutes + + vcpus_weight * fairshare_cores + + memory_weight * fairshare_ram) + + with patch("synergy_scheduler_manager.fairshare_manager.datetime") \ + as datetime_mock: + datetime_mock.utcnow.side_effect = (datetime_start, datetime_stop) + priority = self.fsmanager.calculatePriority(user_id=1, prj_id=1) + + self.assertEqual(expected_priority, priority) + + def test_calculate_fairshare(self): + # TODO(vincent) + pass diff --git a/synergy_scheduler_manager/tests/unit/test_queue_manager.py b/synergy_scheduler_manager/tests/unit/test_queue_manager.py new file mode 100644 index 0000000..e7936df --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_queue_manager.py @@ -0,0 +1,335 @@ +# 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 heapq + +from mock import call +from mock import create_autospec +from mock import patch +from sqlalchemy.engine.base import Engine + +from synergy_scheduler_manager.fairshare_manager import FairShareManager +from synergy_scheduler_manager.queue_manager import PriorityQueue +from synergy_scheduler_manager.queue_manager import Queue +from synergy_scheduler_manager.queue_manager import QueueItem +from synergy_scheduler_manager.tests.unit import base + + +class TestQueueItem(base.TestCase): + + def test_get_set_id(self): + qitem = QueueItem(id=1, + user_id=None, + prj_id=None, + priority=None, + retry_count=None, + creation_time=None, + last_update=None, + data=None) + + self.assertEqual(1, qitem.getId()) + + qitem.setId(10) + self.assertEqual(10, qitem.getId()) + + def test_get_set_userid(self): + qitem = QueueItem(id=None, + user_id=1, + prj_id=None, + priority=None, + retry_count=None, + creation_time=None, + last_update=None, + data=None) + + self.assertEqual(1, qitem.getUserId()) + + qitem.setUserId(10) + self.assertEqual(10, qitem.getUserId()) + + def test_get_set_projectid(self): + qitem = QueueItem(id=None, + user_id=None, + prj_id=1, + priority=None, + retry_count=None, + creation_time=None, + last_update=None, + data=None) + + self.assertEqual(1, qitem.getProjectId()) + + qitem.setProjectId(10) + self.assertEqual(10, qitem.getProjectId()) + + def test_get_set_priority(self): + qitem = QueueItem(id=None, + user_id=None, + prj_id=None, + priority=1, + retry_count=None, + creation_time=None, + last_update=None, + data=None) + + self.assertEqual(1, qitem.getPriority()) + + qitem.setPriority(10) + self.assertEqual(10, qitem.getPriority()) + + def test_retry_count(self): + qitem = QueueItem(id=None, + user_id=None, + prj_id=None, + priority=None, + retry_count=1, + creation_time=None, + last_update=None, + data=None) + + self.assertEqual(1, qitem.getRetryCount()) + + qitem.setRetryCount(10) + self.assertEqual(10, qitem.getRetryCount()) + + qitem.incRetryCount() + self.assertEqual(11, qitem.getRetryCount()) + + def test_get_set_creation_time(self): + qitem = QueueItem(id=None, + user_id=None, + prj_id=None, + priority=None, + retry_count=None, + creation_time="now", + last_update=None, + data=None) + + self.assertEqual("now", qitem.getCreationTime()) + + qitem.setCreationTime("later") + self.assertEqual("later", qitem.getCreationTime()) + + def test_get_set_last_update(self): + qitem = QueueItem(id=None, + user_id=None, + prj_id=None, + priority=None, + retry_count=None, + creation_time=None, + last_update="now", + data=None) + + self.assertEqual("now", qitem.getLastUpdate()) + + qitem.setLastUpdate("later") + self.assertEqual("later", qitem.getLastUpdate()) + + def test_get_set_data(self): + qitem = QueueItem(id=None, + user_id=None, + prj_id=None, + priority=None, + retry_count=None, + creation_time=None, + last_update=None, + data=1) + + self.assertEqual(1, qitem.getData()) + + qitem.setData(2) + self.assertEqual(2, qitem.getData()) + + +class TestPriorityQueue(base.TestCase): + + def test_put(self): + pq = PriorityQueue() + pq.put(0, "a") + pq.put(5, "b") + pq.put(10, "c") + + self.assertIn((0, 0, "a"), pq.queue) + self.assertIn((-5, 1, "b"), pq.queue) + self.assertIn((-10, 2, "c"), pq.queue) + + self.assertEqual(3, pq._index) + + self.assertEqual((-10, 2, "c"), heapq.heappop(pq.queue)) + self.assertEqual((-5, 1, "b"), heapq.heappop(pq.queue)) + self.assertEqual((0, 0, "a"), heapq.heappop(pq.queue)) + + def test_get(self): + pq = PriorityQueue() + pq.put(0, "a") + pq.put(5, "b") + + self.assertEqual("b", pq.get()) + self.assertEqual("a", pq.get()) + + def test_size(self): + pq = PriorityQueue() + pq.put(0, "a") + pq.put(5, "b") + pq.put(10, "c") + + self.assertEqual(3, pq.size()) + + +class TestQueue(base.TestCase): + + def setUp(self): + super(TestQueue, self).setUp() + + # Create a Queue that mocks database interaction + self.db_engine_mock = create_autospec(Engine) + self.q = Queue(name="test", db_engine=self.db_engine_mock) + + def test_insert_item(self): + self.q.insertItem(user_id=1, prj_id=2, priority=10, data="mydata") + + # Check the db call of the item insert + insert_call = call.connect().execute( + 'insert into `test` (user_id, prj_id, priority, data) ' + 'values(%s, %s, %s, %s)', [1, 2, 10, '"mydata"']) + self.assertIn(insert_call, self.db_engine_mock.mock_calls) + + # Check the item existence and values in the in-memory queue + priority, index, item = heapq.heappop(self.q.pqueue.queue) + self.assertEqual(-10, priority) + self.assertEqual(0, index) + self.assertEqual(1, item.user_id) + self.assertEqual(2, item.prj_id) + self.assertEqual(10, item.priority) + self.assertEqual(0, item.retry_count) + self.assertIsNone(item.data) # TODO(vincent): should it be "mydata"? + + def test_get_size(self): + execute_mock = self.db_engine_mock.connect().execute + execute_call = call('select count(*) from `test`') + + fetchone_mock = execute_mock().fetchone + fetchone_mock.return_value = [3] + + # Check that getSize() uses the correct sqlalchemy method + self.assertEqual(3, self.q.getSize()) + + # Check that getSize() uses the correct SQL statement + self.assertEqual(execute_call, execute_mock.call_args) + + def test_reinsert_item(self): + # TODO(vincent): what is the purpose of this method? + # It will lead to duplicates. + pass + + def test_get_item(self): + # Insert the item and mock its DB insertion + execute_mock = self.db_engine_mock.connect().execute + execute_mock().lastrowid = 123 + self.q.insertItem(user_id=1, prj_id=2, priority=10, data="mydata") + + # Mock the DB select by returning the same things we inserted before + select_mock = self.db_engine_mock.connect().execute + select_call = call("select user_id, prj_id, priority, retry_count, " + "creation_time, last_update, data from `test` " + "where id=%s", [123]) + fetchone_mock = select_mock().fetchone + fetchone_mock.return_value = [1, 2, 10, 0, "now", "now", '"mydata"'] + + item = self.q.getItem() + self.assertEqual(select_call, select_mock.call_args) + self.assertEqual(123, item.id) + self.assertEqual(1, item.user_id) + self.assertEqual(2, item.prj_id) + self.assertEqual(10, item.priority) + self.assertEqual(0, item.retry_count) + self.assertEqual("now", item.creation_time) + self.assertEqual("now", item.last_update) + self.assertEqual("mydata", item.data) + + def test_delete_item(self): + # Mock QueueItem to be deleted + qitem = create_autospec(QueueItem) + qitem.getId.return_value = 123 + + # Mock the DB delete + execute_mock = self.db_engine_mock.connect().execute + execute_call = call("delete from `test` where id=%s", [123]) + + self.q.deleteItem(qitem) + self.assertEqual(execute_call, execute_mock.call_args) + + def test_update_item(self): + # Mock QueueItem to be updated + qitem = create_autospec(QueueItem) + qitem.getPriority.return_value = 10 + qitem.getRetryCount.return_value = 20 + qitem.getLastUpdate.return_value = "right_now" + qitem.getId.return_value = 123 + + # Mock the DB update + execute_mock = self.db_engine_mock.connect().execute + execute_call = call("update `test` set priority=%s, retry_count=%s, " + "last_update=%s where id=%s", + [10, 20, "right_now", 123]) + + # Check the DB call and that the new QueueItem is in the queue + self.q.updateItem(qitem) + self.assertEqual(execute_call, execute_mock.call_args) + self.assertIn((-10, 0, qitem), self.q.pqueue.queue) + + def test_update_priority(self): + qitem1 = QueueItem( + id=1, + user_id=None, + prj_id=None, + priority=0, + retry_count=None, + creation_time="0AC", + last_update="before") + qitem2 = QueueItem( + id=2, + user_id=None, + prj_id=None, + priority=10, + retry_count=None, + creation_time="0AC", + last_update="before") + + # TODO(vincent): priority on an item & priority in the queue, + # shouldn't it be the same thing? + self.q.pqueue.put(0, qitem1) + self.q.pqueue.put(10, qitem2) + + # Mock fairshare_mgr to fake computing the priority + self.q.fairshare_manager = create_autospec(FairShareManager) + self.q.fairshare_manager.execute.side_effect = [200, 100] # new prio. + + # Mock DB update call + execute_mock = self.db_engine_mock.connect().execute + execute_call1 = call("update `test` set priority=%s, last_update=%s " + "where id=%s", [200, "now", 2]) + execute_call2 = call("update `test` set priority=%s, last_update=%s " + "where id=%s", [100, "now", 1]) + + # Mock datetime.now() call so it is predictable + with patch("synergy_scheduler_manager.queue_manager.datetime") as mock: + mock.now.return_value = "now" + self.q.updatePriority() + + # Check that that fsmanager.execute was correctly called + self.assertIn(execute_call1, execute_mock.call_args_list) + self.assertIn(execute_call2, execute_mock.call_args_list) + + # Check new QueueItem with updated priority are in the pqueue + self.assertEqual(200, qitem2.priority) + self.assertEqual(100, qitem1.priority) diff --git a/synergy_scheduler_manager/tests/unit/test_scheduler_manager.py b/synergy_scheduler_manager/tests/unit/test_scheduler_manager.py new file mode 100644 index 0000000..9928688 --- /dev/null +++ b/synergy_scheduler_manager/tests/unit/test_scheduler_manager.py @@ -0,0 +1,105 @@ +# 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 mock import call +from mock import create_autospec +from mock import MagicMock +from sqlalchemy.engine.base import Engine + +from synergy_scheduler_manager.queue_manager import Queue +from synergy_scheduler_manager.queue_manager import QueueItem +from synergy_scheduler_manager.quota_manager import DynamicQuota +from synergy_scheduler_manager.scheduler_manager import Notifications +from synergy_scheduler_manager.scheduler_manager import Worker +from synergy_scheduler_manager.tests.unit import base + + +class TestNotifications(base.TestCase): + + def test_info_dynamic_quota(self): + """Test that info() makes the correct call to DynamicQuota""" + dynquota_mock = create_autospec(DynamicQuota) + ns = Notifications(dynquota_mock) + + payload = { + "state": "deleted", + "instance_id": 1, + "tenant_id": 2, + "memory_mb": 3, + "vcpus": 4} + ns.info(ctxt=None, + publisher_id=None, + event_type="compute.instance.delete.end", + payload=payload, + metadata=None) + + self.assertEqual(call(1, 2, 4, 3), dynquota_mock.release.call_args) + + +class TestWorker(base.TestCase): + + def setUp(self): + super(TestWorker, self).setUp() + self.nova_manager_mock = MagicMock() + db_engine_mock = create_autospec(Engine) + self.worker = Worker( + name="test", + queue=Queue("testq", db_engine_mock), + quota=DynamicQuota(), + nova_manager=self.nova_manager_mock) + + def test_destroy(self): + """An empty worker can be destroyed without raising an exception.""" + self.worker.destroy() + + def test_run_build_server(self): + + def nova_exec_side_effect(command, *args, **kwargs): + """Mock nova.execute to do a successful build.""" + if command == "GET_SERVER": + res = {"OS-EXT-STS:vm_state": "building", + "OS-EXT-STS:task_state": "scheduling"} + elif command == "BUILD_SERVER": + res = None + else: + raise TypeError("Wrong arguments to nova exec mock") + return res + + # Mock queue.isClosed to do a 1-pass run of the worker + is_closed_mock = create_autospec(self.worker.queue.isClosed) + self.worker.queue.isClosed = is_closed_mock + self.worker.queue.isClosed.side_effect = (False, True) + + # Mock QueueItem in the queue + qitem_mock = create_autospec(QueueItem) + get_item_mock = create_autospec(self.worker.queue.getItem) + get_item_mock.return_value = qitem_mock + self.worker.queue.getItem = get_item_mock + + # Mock nova "GET_SERVER" and "BUILD_SERVER" calls + nova_exec = self.nova_manager_mock.execute + nova_exec.side_effect = nova_exec_side_effect + + # Mock quota allocation + quota_allocate_mock = create_autospec(self.worker.quota.allocate) + quota_allocate_mock.return_value = True + self.worker.quota.allocate = quota_allocate_mock + + # Delete item from the queue + delete_item_mock = create_autospec(self.worker.queue.deleteItem) + self.worker.queue.deleteItem = delete_item_mock + + # Check that we ask nova to BUILD_SERVER and the qitem is deleted + self.worker.run() + build_server_call = nova_exec.call_args_list[1] # second call + self.assertEqual(("BUILD_SERVER",), build_server_call[0]) # check args + self.assertEqual(call(qitem_mock), delete_item_mock.call_args) diff --git a/test-requirements.txt b/test-requirements.txt index 21a7e3b..1719592 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,3 +12,5 @@ oslotest>=1.10.0 # Apache-2.0 testrepository>=0.0.18 testscenarios>=0.4 testtools>=1.4.0 +mock==2.0.0 +sqlalchemy>=1.0.0,<1.1.0