diff options
author | Dmitry Tantsur <divius.inside@gmail.com> | 2019-03-27 17:30:48 +0100 |
---|---|---|
committer | Dmitry Tantsur <divius.inside@gmail.com> | 2019-05-21 20:09:21 +0200 |
commit | 8f6bf4f9dd3f3e27e0c0cf0fab06601a9904f2f7 (patch) | |
tree | 9cebf68a65f4fd1260b51cf994507a92f07b2993 /ironic/tests/unit | |
parent | 5edf4f3fdabedd1ae55ba94b65379c2e33cdfeae (diff) | |
download | ironic-8f6bf4f9dd3f3e27e0c0cf0fab06601a9904f2f7.tar.gz |
Allocation API: backfilling allocations
This feature addresses the case of moving the already deployed nodes
under the allocation API.
Change-Id: I29d0bd3663e0d1b27727a700c0f0e0fb6ceac1d9
Story: #2005014
Task: #29491
Diffstat (limited to 'ironic/tests/unit')
-rw-r--r-- | ironic/tests/unit/api/controllers/v1/test_allocation.py | 90 | ||||
-rw-r--r-- | ironic/tests/unit/api/utils.py | 8 | ||||
-rw-r--r-- | ironic/tests/unit/conductor/test_allocations.py | 262 |
3 files changed, 354 insertions, 6 deletions
diff --git a/ironic/tests/unit/api/controllers/v1/test_allocation.py b/ironic/tests/unit/api/controllers/v1/test_allocation.py index 20aa72f98..162ded7d2 100644 --- a/ironic/tests/unit/api/controllers/v1/test_allocation.py +++ b/ironic/tests/unit/api/controllers/v1/test_allocation.py @@ -561,7 +561,11 @@ class TestPatch(test_api_base.BaseApiTest): self.assertTrue(response.json['error_message']) -def _create_locally(_api, _ctx, allocation, _topic): +def _create_locally(_api, _ctx, allocation, topic): + if 'node_id' in allocation and allocation.node_id: + assert topic == 'node-topic', topic + else: + assert topic == 'some-topic', topic allocation.create() return allocation @@ -576,6 +580,10 @@ class TestPost(test_api_base.BaseApiTest): fixtures.MockPatchObject(rpcapi.ConductorAPI, 'get_random_topic') ).mock self.mock_get_topic.return_value = 'some-topic' + self.mock_get_topic_for_node = self.useFixture( + fixtures.MockPatchObject(rpcapi.ConductorAPI, 'get_topic_for') + ).mock + self.mock_get_topic_for_node.return_value = 'node-topic' @mock.patch.object(notification_utils, '_emit_api_notification') @mock.patch.object(timeutils, 'utcnow', autospec=True) @@ -591,6 +599,7 @@ class TestPost(test_api_base.BaseApiTest): self.assertIsNone(response.json['node_uuid']) self.assertEqual([], response.json['candidate_nodes']) self.assertEqual([], response.json['traits']) + self.assertNotIn('node', response.json) result = self.get_json('/allocations/%s' % adict['uuid'], headers=self.headers) self.assertEqual(adict['uuid'], result['uuid']) @@ -598,6 +607,7 @@ class TestPost(test_api_base.BaseApiTest): self.assertIsNone(result['node_uuid']) self.assertEqual([], result['candidate_nodes']) self.assertEqual([], result['traits']) + self.assertNotIn('node', result) return_created_at = timeutils.parse_isotime( result['created_at']).replace(tzinfo=None) self.assertEqual(test_time, return_created_at) @@ -700,7 +710,7 @@ class TestPost(test_api_base.BaseApiTest): headers=self.headers) self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) - self.assertTrue(response.json['error_message']) + self.assertIn('resource_class', response.json['error_message']) def test_create_allocation_resource_class_too_long(self): adict = apiutils.allocation_post_data() @@ -785,15 +795,87 @@ class TestPost(test_api_base.BaseApiTest): self.assertEqual('application/json', response.content_type) self.assertEqual(http_client.METHOD_NOT_ALLOWED, response.status_int) - def test_create_with_node_uuid_not_allowed(self): + def test_create_node_uuid_not_allowed(self): + node = obj_utils.create_test_node(self.context) adict = apiutils.allocation_post_data() - adict['node_uuid'] = uuidutils.generate_uuid() + adict['node_uuid'] = node.uuid response = self.post_json('/allocations', adict, expect_errors=True, headers=self.headers) self.assertEqual(http_client.BAD_REQUEST, response.status_int) self.assertEqual('application/json', response.content_type) self.assertTrue(response.json['error_message']) + def test_backfill(self): + node = obj_utils.create_test_node(self.context) + adict = apiutils.allocation_post_data(node=node.uuid) + response = self.post_json('/allocations', adict, + headers=self.headers) + self.assertEqual(http_client.CREATED, response.status_int) + self.assertNotIn('node', response.json) + result = self.get_json('/allocations/%s' % adict['uuid'], + headers=self.headers) + self.assertEqual(adict['uuid'], result['uuid']) + self.assertEqual(node.uuid, result['node_uuid']) + self.assertNotIn('node', result) + + def test_backfill_with_name(self): + node = obj_utils.create_test_node(self.context, name='backfill-me') + adict = apiutils.allocation_post_data(node=node.name) + response = self.post_json('/allocations', adict, + headers=self.headers) + self.assertEqual(http_client.CREATED, response.status_int) + self.assertNotIn('node', response.json) + result = self.get_json('/allocations/%s' % adict['uuid'], + headers=self.headers) + self.assertEqual(adict['uuid'], result['uuid']) + self.assertEqual(node.uuid, result['node_uuid']) + self.assertNotIn('node', result) + + def test_backfill_without_resource_class(self): + node = obj_utils.create_test_node(self.context, + resource_class='bm-super') + adict = {'node': node.uuid} + response = self.post_json('/allocations', adict, + headers=self.headers) + self.assertEqual(http_client.CREATED, response.status_int) + result = self.get_json('/allocations/%s' % response.json['uuid'], + headers=self.headers) + self.assertEqual(node.uuid, result['node_uuid']) + self.assertEqual('bm-super', result['resource_class']) + + def test_backfill_copy_instance_uuid(self): + uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, + instance_uuid=uuid, + resource_class='bm-super') + adict = {'node': node.uuid} + response = self.post_json('/allocations', adict, + headers=self.headers) + self.assertEqual(http_client.CREATED, response.status_int) + result = self.get_json('/allocations/%s' % response.json['uuid'], + headers=self.headers) + self.assertEqual(uuid, result['uuid']) + self.assertEqual(node.uuid, result['node_uuid']) + self.assertEqual('bm-super', result['resource_class']) + + def test_backfill_node_not_found(self): + adict = apiutils.allocation_post_data(node=uuidutils.generate_uuid()) + response = self.post_json('/allocations', adict, expect_errors=True, + headers=self.headers) + self.assertEqual(http_client.BAD_REQUEST, response.status_int) + self.assertEqual('application/json', response.content_type) + self.assertTrue(response.json['error_message']) + + def test_backfill_not_allowed(self): + node = obj_utils.create_test_node(self.context) + headers = {api_base.Version.string: '1.57'} + adict = apiutils.allocation_post_data(node=node.uuid) + response = self.post_json('/allocations', adict, expect_errors=True, + headers=headers) + self.assertEqual(http_client.BAD_REQUEST, response.status_int) + self.assertEqual('application/json', response.content_type) + self.assertTrue(response.json['error_message']) + @mock.patch.object(rpcapi.ConductorAPI, 'destroy_allocation') class TestDelete(test_api_base.BaseApiTest): diff --git a/ironic/tests/unit/api/utils.py b/ironic/tests/unit/api/utils.py index 7addd2e8d..30444d00a 100644 --- a/ironic/tests/unit/api/utils.py +++ b/ironic/tests/unit/api/utils.py @@ -189,12 +189,16 @@ def post_get_test_portgroup(**kw): _ALLOCATION_POST_FIELDS = {'resource_class', 'uuid', 'traits', - 'candidate_nodes', 'name', 'extra'} + 'candidate_nodes', 'name', 'extra', + 'node'} -def allocation_post_data(**kw): +def allocation_post_data(node=None, **kw): """Return an Allocation object without internal attributes.""" allocation = db_utils.get_test_allocation(**kw) + if node: + # This is not a database field, so it has to be handled explicitly + allocation['node'] = node return {key: value for key, value in allocation.items() if key in _ALLOCATION_POST_FIELDS} diff --git a/ironic/tests/unit/conductor/test_allocations.py b/ironic/tests/unit/conductor/test_allocations.py index d490e99e2..dbdb01e86 100644 --- a/ironic/tests/unit/conductor/test_allocations.py +++ b/ironic/tests/unit/conductor/test_allocations.py @@ -59,6 +59,30 @@ class AllocationTestCase(mgr_utils.ServiceSetUpMixin, db_base.DbTestCase): allocations.do_allocate, self.context, mock.ANY) + @mock.patch.object(manager.ConductorManager, '_spawn_worker', mock.Mock()) + @mock.patch.object(allocations, 'backfill_allocation', autospec=True) + def test_create_allocation_with_node_id(self, mock_backfill): + node = obj_utils.create_test_node(self.context) + allocation = obj_utils.get_test_allocation(self.context, + node_id=node.id) + + self._start_service() + res = self.service.create_allocation(self.context, allocation) + mock_backfill.assert_called_once_with(self.context, + allocation, + node.id) + + self.assertEqual('allocating', res['state']) + self.assertIsNotNone(res['uuid']) + self.assertEqual(self.service.conductor.id, res['conductor_affinity']) + # create_allocation purges node_id, and since we stub out + # backfill_allocation, it does not get populated. + self.assertIsNone(res['node_id']) + res = objects.Allocation.get_by_uuid(self.context, allocation['uuid']) + self.assertEqual('allocating', res['state']) + self.assertIsNotNone(res['uuid']) + self.assertEqual(self.service.conductor.id, res['conductor_affinity']) + def test_destroy_allocation_without_node(self): allocation = obj_utils.create_test_allocation(self.context) self.service.destroy_allocation(self.context, allocation) @@ -422,3 +446,241 @@ class DoAllocateTestCase(db_base.DbTestCase): # All nodes are filtered out on the database level. self.assertFalse(mock_acquire.called) + + +class BackfillAllocationTestCase(db_base.DbTestCase): + def test_with_associated_node(self): + uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, + instance_uuid=uuid, + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation(self.context, + uuid=uuid, + resource_class='x-large') + + allocations.backfill_allocation(self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertIsNone(allocation['last_error']) + self.assertEqual('active', allocation['state']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertEqual(allocation['uuid'], node['instance_uuid']) + self.assertEqual(allocation['id'], node['allocation_id']) + + def test_with_unassociated_node(self): + node = obj_utils.create_test_node(self.context, + instance_uuid=None, + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation(self.context, + resource_class='x-large') + + allocations.backfill_allocation(self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertIsNone(allocation['last_error']) + self.assertEqual('active', allocation['state']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertEqual(allocation['uuid'], node['instance_uuid']) + self.assertEqual(allocation['id'], node['allocation_id']) + + def test_with_candidate_nodes(self): + node = obj_utils.create_test_node(self.context, + instance_uuid=None, + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation( + self.context, candidate_nodes=[node.uuid], + resource_class='x-large') + + allocations.backfill_allocation(self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertIsNone(allocation['last_error']) + self.assertEqual('active', allocation['state']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertEqual(allocation['uuid'], node['instance_uuid']) + self.assertEqual(allocation['id'], node['allocation_id']) + + def test_without_resource_class(self): + uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, + instance_uuid=uuid, + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation(self.context, + uuid=uuid, + resource_class=None) + + allocations.backfill_allocation(self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertIsNone(allocation['last_error']) + self.assertEqual('active', allocation['state']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertEqual(allocation['uuid'], node['instance_uuid']) + self.assertEqual(allocation['id'], node['allocation_id']) + + def test_node_associated_with_another_instance(self): + other_uuid = uuidutils.generate_uuid() + node = obj_utils.create_test_node(self.context, + instance_uuid=other_uuid, + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation(self.context, + resource_class='x-large') + + self.assertRaises(exception.NodeAssociated, + allocations.backfill_allocation, + self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('associated', allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertEqual(other_uuid, node['instance_uuid']) + self.assertIsNone(node['allocation_id']) + + def test_non_existing_node(self): + allocation = obj_utils.create_test_allocation(self.context, + resource_class='x-large') + + self.assertRaises(exception.NodeNotFound, + allocations.backfill_allocation, + self.context, allocation, 42) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('Node 42 could not be found', allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + def test_uuid_associated_with_another_instance(self): + uuid = uuidutils.generate_uuid() + obj_utils.create_test_node(self.context, + uuid=uuidutils.generate_uuid(), + instance_uuid=uuid, + resource_class='x-large', + provision_state='active') + node = obj_utils.create_test_node(self.context, + uuid=uuidutils.generate_uuid(), + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation(self.context, + uuid=uuid, + resource_class='x-large') + + self.assertRaises(exception.InstanceAssociated, + allocations.backfill_allocation, + self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('associated', allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertIsNone(node['instance_uuid']) + self.assertIsNone(node['allocation_id']) + + def test_resource_class_mismatch(self): + node = obj_utils.create_test_node(self.context, + resource_class='x-small', + provision_state='active') + allocation = obj_utils.create_test_allocation(self.context, + resource_class='x-large') + + self.assertRaises(exception.AllocationFailed, + allocations.backfill_allocation, + self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('resource class', allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertIsNone(node['instance_uuid']) + self.assertIsNone(node['allocation_id']) + + def test_traits_mismatch(self): + node = obj_utils.create_test_node(self.context, + resource_class='x-large', + provision_state='active') + db_utils.create_test_node_traits(['tr1', 'tr2'], node_id=node.id) + allocation = obj_utils.create_test_allocation(self.context, + resource_class='x-large', + traits=['tr1', 'tr3']) + + self.assertRaises(exception.AllocationFailed, + allocations.backfill_allocation, + self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('traits', allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertIsNone(node['instance_uuid']) + self.assertIsNone(node['allocation_id']) + + def test_state_not_active(self): + node = obj_utils.create_test_node(self.context, + resource_class='x-large', + provision_state='available') + allocation = obj_utils.create_test_allocation(self.context, + resource_class='x-large') + + self.assertRaises(exception.AllocationFailed, + allocations.backfill_allocation, + self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('must be in the "active" state', + allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertIsNone(node['instance_uuid']) + self.assertIsNone(node['allocation_id']) + + def test_candidate_nodes_mismatch(self): + node = obj_utils.create_test_node(self.context, + resource_class='x-large', + provision_state='active') + allocation = obj_utils.create_test_allocation( + self.context, + candidate_nodes=[uuidutils.generate_uuid()], + resource_class='x-large') + + self.assertRaises(exception.AllocationFailed, + allocations.backfill_allocation, + self.context, allocation, node.id) + + allocation = objects.Allocation.get_by_uuid(self.context, + allocation['uuid']) + self.assertEqual('error', allocation['state']) + self.assertIn('Candidate nodes', allocation['last_error']) + self.assertIsNone(allocation['node_id']) + + node = objects.Node.get_by_uuid(self.context, node['uuid']) + self.assertIsNone(node['instance_uuid']) + self.assertIsNone(node['allocation_id']) |