summaryrefslogtreecommitdiff
path: root/ironic/tests/unit
diff options
context:
space:
mode:
authorDmitry Tantsur <divius.inside@gmail.com>2019-03-27 17:30:48 +0100
committerDmitry Tantsur <divius.inside@gmail.com>2019-05-21 20:09:21 +0200
commit8f6bf4f9dd3f3e27e0c0cf0fab06601a9904f2f7 (patch)
tree9cebf68a65f4fd1260b51cf994507a92f07b2993 /ironic/tests/unit
parent5edf4f3fdabedd1ae55ba94b65379c2e33cdfeae (diff)
downloadironic-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.py90
-rw-r--r--ironic/tests/unit/api/utils.py8
-rw-r--r--ironic/tests/unit/conductor/test_allocations.py262
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'])