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