diff options
Diffstat (limited to 'ironic/tests')
51 files changed, 1935 insertions, 937 deletions
diff --git a/ironic/tests/__init__.py b/ironic/tests/__init__.py index 7e8390833..918237d30 100644 --- a/ironic/tests/__init__.py +++ b/ironic/tests/__init__.py @@ -32,14 +32,3 @@ eventlet.monkey_patch(os=False) # The code below enables nosetests to work with i18n _() blocks import six.moves.builtins as __builtin__ setattr(__builtin__, '_', lambda x: x) - -# NOTE(viktors): Ironic unittests patches timeutils from oslo_utils. At the -# same time oslo.db uses oslo.utils not oslo_utils till 1.5.0 -# release, so timeutils in oslo.db code at and leave not -# patched, so time comparison fails in Ironic tests. To avoid -# this we have oslo_db use timeutils from oslo_utils in tests. -# TODO(viktors): Remove this workaround when Ironic will use oslo.db 1.5.0 -from oslo_db.sqlalchemy import models -from oslo_utils import timeutils - -models.timeutils = timeutils diff --git a/ironic/tests/api/test_acl.py b/ironic/tests/api/test_acl.py index 0b60387d8..8de194ffc 100644 --- a/ironic/tests/api/test_acl.py +++ b/ironic/tests/api/test_acl.py @@ -88,7 +88,10 @@ class TestACL(base.FunctionalTest): self.assertEqual(200, response.status_int) def test_public_api_with_path_extensions(self): - for route in ('/v1/', '/v1.json', '/v1.xml'): - response = self.get_json(route, + routes = {'/v1/': 200, + '/v1.json': 200, + '/v1.xml': 404} + for url in routes: + response = self.get_json(url, path_prefix='', expect_errors=True) - self.assertEqual(200, response.status_int) + self.assertEqual(routes[url], response.status_int) diff --git a/ironic/tests/api/utils.py b/ironic/tests/api/utils.py index ee029717f..76d229b0a 100644 --- a/ironic/tests/api/utils.py +++ b/ironic/tests/api/utils.py @@ -94,3 +94,13 @@ def chassis_post_data(**kw): chassis = utils.get_test_chassis(**kw) internal = chassis_controller.ChassisPatchType.internal_attrs() return remove_internal(chassis, internal) + + +def post_get_test_node(**kw): + # NOTE(lucasagomes): When creating a node via API (POST) + # we have to use chassis_uuid + node = node_post_data(**kw) + chassis = utils.get_test_chassis() + node['chassis_id'] = None + node['chassis_uuid'] = kw.get('chassis_uuid', chassis['uuid']) + return node diff --git a/ironic/tests/api/v1/test_chassis.py b/ironic/tests/api/v1/test_chassis.py index 71607b270..a45e79e5a 100644 --- a/ironic/tests/api/v1/test_chassis.py +++ b/ironic/tests/api/v1/test_chassis.py @@ -21,6 +21,7 @@ import mock from oslo_config import cfg from oslo_utils import timeutils from oslo_utils import uuidutils +import six from six.moves.urllib import parse as urlparse from wsme import types as wtypes @@ -77,16 +78,16 @@ class TestListChassis(api_base.FunctionalTest): ch_list = [] for id_ in range(5): chassis = obj_utils.create_test_chassis( - self.context, id=id_, uuid=uuidutils.generate_uuid()) + self.context, uuid=uuidutils.generate_uuid()) ch_list.append(chassis.uuid) data = self.get_json('/chassis') self.assertEqual(len(ch_list), len(data['chassis'])) uuids = [n['uuid'] for n in data['chassis']] - self.assertEqual(ch_list.sort(), uuids.sort()) + six.assertCountEqual(self, ch_list, uuids) def test_links(self): uuid = uuidutils.generate_uuid() - obj_utils.create_test_chassis(self.context, id=1, uuid=uuid) + obj_utils.create_test_chassis(self.context, uuid=uuid) data = self.get_json('/chassis/%s' % uuid) self.assertIn('links', data.keys()) self.assertEqual(2, len(data['links'])) @@ -97,7 +98,7 @@ class TestListChassis(api_base.FunctionalTest): def test_collection_links(self): for id in range(5): - obj_utils.create_test_chassis(self.context, id=id, + obj_utils.create_test_chassis(self.context, uuid=uuidutils.generate_uuid()) data = self.get_json('/chassis/?limit=3') self.assertEqual(3, len(data['chassis'])) @@ -108,7 +109,7 @@ class TestListChassis(api_base.FunctionalTest): def test_collection_links_default_limit(self): cfg.CONF.set_override('max_limit', 3, 'api') for id_ in range(5): - obj_utils.create_test_chassis(self.context, id=id_, + obj_utils.create_test_chassis(self.context, uuid=uuidutils.generate_uuid()) data = self.get_json('/chassis') self.assertEqual(3, len(data['chassis'])) @@ -186,8 +187,7 @@ class TestPatch(api_base.FunctionalTest): def test_replace_multi(self): extra = {"foo1": "bar1", "foo2": "bar2", "foo3": "bar3"} chassis = obj_utils.create_test_chassis(self.context, extra=extra, - uuid=uuidutils.generate_uuid(), - id=1) + uuid=uuidutils.generate_uuid()) new_value = 'new value' response = self.patch_json('/chassis/%s' % chassis.uuid, [{'path': '/extra/foo2', @@ -201,8 +201,7 @@ class TestPatch(api_base.FunctionalTest): def test_remove_singular(self): chassis = obj_utils.create_test_chassis(self.context, extra={'a': 'b'}, - uuid=uuidutils.generate_uuid(), - id=1) + uuid=uuidutils.generate_uuid()) response = self.patch_json('/chassis/%s' % chassis.uuid, [{'path': '/description', 'op': 'remove'}]) self.assertEqual('application/json', response.content_type) @@ -218,8 +217,7 @@ class TestPatch(api_base.FunctionalTest): extra = {"foo1": "bar1", "foo2": "bar2", "foo3": "bar3"} chassis = obj_utils.create_test_chassis(self.context, extra=extra, description="foobar", - uuid=uuidutils.generate_uuid(), - id=1) + uuid=uuidutils.generate_uuid()) # Removing one item from the collection response = self.patch_json('/chassis/%s' % chassis.uuid, @@ -343,7 +341,7 @@ class TestPost(api_base.FunctionalTest): def test_post_nodes_subresource(self): chassis = obj_utils.create_test_chassis(self.context) - ndict = apiutils.node_post_data(chassis_id=None) + ndict = apiutils.node_post_data() ndict['chassis_uuid'] = chassis.uuid response = self.post_json('/chassis/nodes', ndict, expect_errors=True) diff --git a/ironic/tests/api/v1/test_nodes.py b/ironic/tests/api/v1/test_nodes.py index 4f79e6aff..dbb95daaf 100644 --- a/ironic/tests/api/v1/test_nodes.py +++ b/ironic/tests/api/v1/test_nodes.py @@ -22,7 +22,6 @@ import mock from oslo_config import cfg from oslo_utils import timeutils from oslo_utils import uuidutils -import pecan from six.moves.urllib import parse as urlparse from testtools.matchers import HasLength from wsme import types as wtypes @@ -30,6 +29,7 @@ from wsme import types as wtypes from ironic.api.controllers import base as api_base from ironic.api.controllers import v1 as api_v1 from ironic.api.controllers.v1 import node as api_node +from ironic.api.controllers.v1 import utils as api_utils from ironic.common import boot_devices from ironic.common import exception from ironic.common import states @@ -38,89 +38,13 @@ from ironic import objects from ironic.tests.api import base as test_api_base from ironic.tests.api import utils as test_api_utils from ironic.tests import base -from ironic.tests.db import utils as dbutils from ironic.tests.objects import utils as obj_utils -# NOTE(lucasagomes): When creating a node via API (POST) -# we have to use chassis_uuid -def post_get_test_node(**kw): - node = test_api_utils.node_post_data(**kw) - chassis = dbutils.get_test_chassis() - node['chassis_id'] = None - node['chassis_uuid'] = kw.get('chassis_uuid', chassis['uuid']) - return node - - -class TestTopLevelFunctions(base.TestCase): - - def setUp(self): - super(TestTopLevelFunctions, self).setUp() - self.valid_name = 'my-host' - self.valid_uuid = uuidutils.generate_uuid() - self.invalid_name = 'Mr Plow' - self.invalid_uuid = '636-555-3226-' - self.node = post_get_test_node() - - def test_is_valid_name(self): - self.assertTrue(api_node.is_valid_name(self.valid_name)) - self.assertFalse(api_node.is_valid_name(self.invalid_name)) - self.assertFalse(api_node.is_valid_name(self.valid_uuid)) - self.assertFalse(api_node.is_valid_name(self.invalid_uuid)) - - @mock.patch.object(pecan, 'request') - @mock.patch.object(api_node, 'allow_logical_names') - @mock.patch.object(objects.Node, 'get_by_uuid') - @mock.patch.object(objects.Node, 'get_by_name') - def test__get_rpc_node_expect_uuid(self, mock_gbn, mock_gbu, mock_aln, - mock_pr): - mock_aln.return_value = True - self.node['uuid'] = self.valid_uuid - mock_gbu.return_value = self.node - self.assertEqual(self.node, api_node._get_rpc_node(self.valid_uuid)) - self.assertEqual(1, mock_gbu.call_count) - self.assertEqual(0, mock_gbn.call_count) - - @mock.patch.object(pecan, 'request') - @mock.patch.object(api_v1.node, 'allow_logical_names') - @mock.patch.object(objects.Node, 'get_by_uuid') - @mock.patch.object(objects.Node, 'get_by_name') - def test__get_rpc_node_expect_name(self, mock_gbn, mock_gbu, mock_aln, - mock_pr): - mock_aln.return_value = True - self.node['name'] = self.valid_name - mock_gbn.return_value = self.node - self.assertEqual(self.node, api_node._get_rpc_node(self.valid_name)) - self.assertEqual(0, mock_gbu.call_count) - self.assertEqual(1, mock_gbn.call_count) - - @mock.patch.object(pecan, 'request') - @mock.patch.object(api_v1.node, 'allow_logical_names') - @mock.patch.object(objects.Node, 'get_by_uuid') - @mock.patch.object(objects.Node, 'get_by_name') - def test__get_rpc_node_invalid_name(self, mock_gbn, mock_gbu, - mock_aln, mock_pr): - mock_aln.return_value = True - self.assertRaises(exception.InvalidUuidOrName, - api_node._get_rpc_node, - self.invalid_name) - - @mock.patch.object(pecan, 'request') - @mock.patch.object(api_v1.node, 'allow_logical_names') - @mock.patch.object(objects.Node, 'get_by_uuid') - @mock.patch.object(objects.Node, 'get_by_name') - def test__get_rpc_node_invalid_uuid(self, mock_gbn, mock_gbu, - mock_aln, mock_pr): - mock_aln.return_value = True - self.assertRaises(exception.InvalidUuidOrName, - api_node._get_rpc_node, - self.invalid_uuid) - - class TestNodeObject(base.TestCase): def test_node_init(self): - node_dict = test_api_utils.node_post_data(chassis_id=None) + node_dict = test_api_utils.node_post_data() del node_dict['instance_uuid'] node = api_node.Node(**node_dict) self.assertEqual(wtypes.Unset, node.instance_uuid) @@ -159,7 +83,8 @@ class TestListNodes(test_api_base.FunctionalTest): self.assertEqual([], data['nodes']) def test_one(self): - node = obj_utils.create_test_node(self.context) + node = obj_utils.create_test_node(self.context, + chassis_id=self.chassis.id) data = self.get_json('/nodes', headers={api_base.Version.string: str(api_v1.MAX_VER)}) self.assertIn('instance_uuid', data['nodes'][0]) @@ -184,7 +109,8 @@ class TestListNodes(test_api_base.FunctionalTest): self.assertNotIn('chassis_id', data['nodes'][0]) def test_get_one(self): - node = obj_utils.create_test_node(self.context) + node = obj_utils.create_test_node(self.context, + chassis_id=self.chassis.id) data = self.get_json('/nodes/%s' % node.uuid, headers={api_base.Version.string: str(api_v1.MAX_VER)}) self.assertEqual(node.uuid, data['uuid']) @@ -205,7 +131,8 @@ class TestListNodes(test_api_base.FunctionalTest): self.assertNotIn('chassis_id', data) def test_detail(self): - node = obj_utils.create_test_node(self.context) + node = obj_utils.create_test_node(self.context, + chassis_id=self.chassis.id) data = self.get_json('/nodes/detail', headers={api_base.Version.string: str(api_v1.MAX_VER)}) self.assertEqual(node.uuid, data['nodes'][0]["uuid"]) @@ -244,23 +171,7 @@ class TestListNodes(test_api_base.FunctionalTest): headers={api_base.Version.string: "1.2"}) self.assertEqual(states.AVAILABLE, data['provision_state']) - def test_hide_fields_in_newer_versions(self): - some_time = datetime.datetime(2015, 3, 18, 19, 20) - node = obj_utils.create_test_node(self.context, - inspection_started_at=some_time) - data = self.get_json('/nodes/%s' % node.uuid, - headers={api_base.Version.string: str(api_v1.MIN_VER)}) - self.assertNotIn('inspection_finished_at', data) - self.assertNotIn('inspection_started_at', data) - - data = self.get_json('/nodes/%s' % node.uuid, - headers={api_base.Version.string: "1.6"}) - started = timeutils.parse_isotime( - data['inspection_started_at']).replace(tzinfo=None) - self.assertEqual(some_time, started) - self.assertEqual(None, data['inspection_finished_at']) - - def test_hide_driver_internal_info(self): + def test_hide_fields_in_newer_versions_driver_internal(self): node = obj_utils.create_test_node(self.context, driver_internal_info={"foo": "bar"}) data = self.get_json('/nodes/%s' % node.uuid, @@ -271,7 +182,7 @@ class TestListNodes(test_api_base.FunctionalTest): headers={api_base.Version.string: "1.3"}) self.assertEqual({"foo": "bar"}, data['driver_internal_info']) - def test_unset_logical_names(self): + def test_hide_fields_in_newer_versions_name(self): node = obj_utils.create_test_node(self.context, name="fish") data = self.get_json('/nodes/%s' % node.uuid, @@ -282,6 +193,22 @@ class TestListNodes(test_api_base.FunctionalTest): headers={api_base.Version.string: "1.5"}) self.assertEqual('fish', data['name']) + def test_hide_fields_in_newer_versions_inspection(self): + some_time = datetime.datetime(2015, 3, 18, 19, 20) + node = obj_utils.create_test_node(self.context, + inspection_started_at=some_time) + data = self.get_json('/nodes/%s' % node.uuid, + headers={api_base.Version.string: str(api_v1.MIN_VER)}) + self.assertNotIn('inspection_finished_at', data) + self.assertNotIn('inspection_started_at', data) + + data = self.get_json('/nodes/%s' % node.uuid, + headers={api_base.Version.string: "1.6"}) + started = timeutils.parse_isotime( + data['inspection_started_at']).replace(tzinfo=None) + self.assertEqual(some_time, started) + self.assertEqual(None, data['inspection_finished_at']) + def test_many(self): nodes = [] for id in range(5): @@ -530,7 +457,8 @@ class TestListNodes(test_api_base.FunctionalTest): node = obj_utils.create_test_node( self.context, uuid=uuidutils.generate_uuid(), - instance_uuid=uuidutils.generate_uuid()) + instance_uuid=uuidutils.generate_uuid(), + chassis_id=self.chassis.id) instance_uuid = node.instance_uuid data = self.get_json('/nodes/detail?instance_uuid=%s' % instance_uuid) @@ -734,9 +662,11 @@ class TestPatch(test_api_base.FunctionalTest): def setUp(self): super(TestPatch, self).setUp() self.chassis = obj_utils.create_test_chassis(self.context) - self.node = obj_utils.create_test_node(self.context, name='node-57') + self.node = obj_utils.create_test_node(self.context, name='node-57', + chassis_id=self.chassis.id) self.node_no_name = obj_utils.create_test_node(self.context, - uuid='deadbeef-0000-1111-2222-333333333333') + uuid='deadbeef-0000-1111-2222-333333333333', + chassis_id=self.chassis.id) p = mock.patch.object(rpcapi.ConductorAPI, 'get_topic_for') self.mock_gtf = p.start() self.mock_gtf.return_value = 'test-topic' @@ -777,7 +707,7 @@ class TestPatch(test_api_base.FunctionalTest): 'value': 'aaaaaaaa-1111-bbbb-2222-cccccccccccc', 'op': 'replace'}], expect_errors=True) - self.assertEqual(400, response.status_code) + self.assertEqual(404, response.status_code) self.assertFalse(self.mock_update_node.called) def test_update_ok_by_name(self): @@ -945,6 +875,35 @@ class TestPatch(test_api_base.FunctionalTest): self.assertEqual(400, response.status_code) self.assertTrue(response.json['error_message']) + def test_remove_instance_uuid_cleaning(self): + node = obj_utils.create_test_node( + self.context, + uuid=uuidutils.generate_uuid(), + provision_state=states.CLEANING, + target_provision_state=states.AVAILABLE) + self.mock_update_node.return_value = node + response = self.patch_json('/nodes/%s' % node.uuid, + [{'op': 'remove', + 'path': '/instance_uuid'}]) + self.assertEqual('application/json', response.content_type) + self.assertEqual(200, response.status_code) + self.mock_update_node.assert_called_once_with( + mock.ANY, mock.ANY, 'test-topic') + + def test_add_state_in_cleaning(self): + node = obj_utils.create_test_node( + self.context, + uuid=uuidutils.generate_uuid(), + provision_state=states.CLEANING, + target_provision_state=states.AVAILABLE) + self.mock_update_node.return_value = node + response = self.patch_json('/nodes/%s' % node.uuid, + [{'path': '/extra/foo', 'value': 'bar', + 'op': 'add'}], expect_errors=True) + self.assertEqual('application/json', response.content_type) + self.assertEqual(409, response.status_code) + self.assertTrue(response.json['error_message']) + def test_remove_mandatory_field(self): response = self.patch_json('/nodes/%s' % self.node.uuid, [{'path': '/driver', 'op': 'remove'}], @@ -1134,6 +1093,21 @@ class TestPatch(test_api_base.FunctionalTest): self.assertEqual(409, response.status_code) self.assertTrue(response.json['error_message']) + @mock.patch.object(api_utils, 'get_rpc_node') + def test_patch_update_drive_console_enabled(self, mock_rpc_node): + self.node.console_enabled = True + mock_rpc_node.return_value = self.node + + response = self.patch_json('/nodes/%s' % self.node.uuid, + [{'path': '/driver', + 'value': 'foo', + 'op': 'add'}], + expect_errors=True) + mock_rpc_node.assert_called_once_with(self.node.uuid) + self.assertEqual('application/json', response.content_type) + self.assertEqual(409, response.status_code) + self.assertTrue(response.json['error_message']) + class TestPost(test_api_base.FunctionalTest): @@ -1147,7 +1121,7 @@ class TestPost(test_api_base.FunctionalTest): @mock.patch.object(timeutils, 'utcnow') def test_create_node(self, mock_utcnow): - ndict = post_get_test_node() + ndict = test_api_utils.post_get_test_node() test_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = test_time response = self.post_json('/nodes', ndict) @@ -1172,7 +1146,7 @@ class TestPost(test_api_base.FunctionalTest): # as Unset). with mock.patch.object(self.dbapi, 'create_node', wraps=self.dbapi.create_node) as cn_mock: - ndict = post_get_test_node(extra={'foo': 123}) + ndict = test_api_utils.post_get_test_node(extra={'foo': 123}) self.post_json('/nodes', ndict) result = self.get_json('/nodes/%s' % ndict['uuid']) self.assertEqual(ndict['extra'], result['extra']) @@ -1184,7 +1158,7 @@ class TestPost(test_api_base.FunctionalTest): kwargs = {attr_name: {'str': 'foo', 'int': 123, 'float': 0.1, 'bool': True, 'list': [1, 2], 'none': None, 'dict': {'cat': 'meow'}}} - ndict = post_get_test_node(**kwargs) + ndict = test_api_utils.post_get_test_node(**kwargs) self.post_json('/nodes', ndict) result = self.get_json('/nodes/%s' % ndict['uuid']) self.assertEqual(ndict[attr_name], result[attr_name]) @@ -1310,7 +1284,7 @@ class TestPost(test_api_base.FunctionalTest): self.assertEqual(403, response.status_int) def test_create_node_no_mandatory_field_driver(self): - ndict = post_get_test_node() + ndict = test_api_utils.post_get_test_node() del ndict['driver'] response = self.post_json('/nodes', ndict, expect_errors=True) self.assertEqual(400, response.status_int) @@ -1318,7 +1292,7 @@ class TestPost(test_api_base.FunctionalTest): self.assertTrue(response.json['error_message']) def test_create_node_invalid_driver(self): - ndict = post_get_test_node() + ndict = test_api_utils.post_get_test_node() self.mock_gtf.side_effect = exception.NoValidHost('Fake Error') response = self.post_json('/nodes', ndict, expect_errors=True) self.assertEqual(400, response.status_int) @@ -1326,7 +1300,7 @@ class TestPost(test_api_base.FunctionalTest): self.assertTrue(response.json['error_message']) def test_create_node_no_chassis_uuid(self): - ndict = post_get_test_node() + ndict = test_api_utils.post_get_test_node() del ndict['chassis_uuid'] response = self.post_json('/nodes', ndict) self.assertEqual('application/json', response.content_type) @@ -1338,7 +1312,8 @@ class TestPost(test_api_base.FunctionalTest): expected_location) def test_create_node_with_chassis_uuid(self): - ndict = post_get_test_node(chassis_uuid=self.chassis.uuid) + ndict = test_api_utils.post_get_test_node( + chassis_uuid=self.chassis.uuid) response = self.post_json('/nodes', ndict) self.assertEqual('application/json', response.content_type) self.assertEqual(201, response.status_int) @@ -1351,7 +1326,7 @@ class TestPost(test_api_base.FunctionalTest): expected_location) def test_create_node_chassis_uuid_not_found(self): - ndict = post_get_test_node( + ndict = test_api_utils.post_get_test_node( chassis_uuid='1a1a1a1a-2b2b-3c3c-4d4d-5e5e5e5e5e5e') response = self.post_json('/nodes', ndict, expect_errors=True) self.assertEqual('application/json', response.content_type) @@ -1359,7 +1334,7 @@ class TestPost(test_api_base.FunctionalTest): self.assertTrue(response.json['error_message']) def test_create_node_with_internal_field(self): - ndict = post_get_test_node() + ndict = test_api_utils.post_get_test_node() ndict['reservation'] = 'fake' response = self.post_json('/nodes', ndict, expect_errors=True) self.assertEqual('application/json', response.content_type) @@ -1392,7 +1367,6 @@ class TestDelete(test_api_base.FunctionalTest): def setUp(self): super(TestDelete, self).setUp() - self.chassis = obj_utils.create_test_chassis(self.context) p = mock.patch.object(rpcapi.ConductorAPI, 'get_topic_for') self.mock_gtf = p.start() self.mock_gtf.return_value = 'test-topic' @@ -1436,7 +1410,7 @@ class TestDelete(test_api_base.FunctionalTest): response = self.delete('/nodes/%s' % node.name, expect_errors=True) - self.assertEqual(400, response.status_int) + self.assertEqual(404, response.status_int) self.assertFalse(mock_gbn.called) @mock.patch.object(objects.Node, 'get_by_name') @@ -1508,7 +1482,6 @@ class TestPut(test_api_base.FunctionalTest): def setUp(self): super(TestPut, self).setUp() - self.chassis = obj_utils.create_test_chassis(self.context) self.node = obj_utils.create_test_node(self.context, provision_state=states.AVAILABLE, name='node-39') p = mock.patch.object(rpcapi.ConductorAPI, 'get_topic_for') @@ -1547,7 +1520,7 @@ class TestPut(test_api_base.FunctionalTest): response = self.put_json('/nodes/%s/states/power' % self.node.name, {'target': states.POWER_ON}, expect_errors=True) - self.assertEqual(400, response.status_code) + self.assertEqual(404, response.status_code) def test_power_state_by_name(self): response = self.put_json('/nodes/%s/states/power' % self.node.name, @@ -1570,6 +1543,13 @@ class TestPut(test_api_base.FunctionalTest): {'target': 'not-supported'}, expect_errors=True) self.assertEqual(400, ret.status_code) + def test_power_change_during_cleaning(self): + self.node.provision_state = states.CLEANING + self.node.save() + ret = self.put_json('/nodes/%s/states/power' % self.node.uuid, + {'target': states.POWER_OFF}, expect_errors=True) + self.assertEqual(400, ret.status_code) + def test_provision_invalid_state_request(self): ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid, {'target': 'not-supported'}, expect_errors=True) @@ -1592,7 +1572,7 @@ class TestPut(test_api_base.FunctionalTest): ret = self.put_json('/nodes/%s/states/provision' % self.node.name, {'target': states.ACTIVE}, expect_errors=True) - self.assertEqual(400, ret.status_code) + self.assertEqual(404, ret.status_code) def test_provision_by_name(self): ret = self.put_json('/nodes/%s/states/provision' % self.node.name, @@ -1775,7 +1755,7 @@ class TestPut(test_api_base.FunctionalTest): ret = self.put_json('/nodes/%s/states/console' % self.node.name, {'enabled': "true"}, expect_errors=True) - self.assertEqual(400, ret.status_code) + self.assertEqual(404, ret.status_code) @mock.patch.object(rpcapi.ConductorAPI, 'set_console_mode') def test_set_console_by_name(self, mock_scm): @@ -1833,17 +1813,14 @@ class TestPut(test_api_base.FunctionalTest): True, 'test-topic') def test_provision_node_in_maintenance_fail(self): - with mock.patch.object(rpcapi.ConductorAPI, 'do_node_deploy') as dnd: - self.node.maintenance = True - self.node.save() - dnd.side_effect = exception.NodeInMaintenance(op='provisioning', - node=self.node.uuid) + self.node.maintenance = True + self.node.save() - ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid, - {'target': states.ACTIVE}, - expect_errors=True) - self.assertEqual(400, ret.status_code) - self.assertTrue(ret.json['error_message']) + ret = self.put_json('/nodes/%s/states/provision' % self.node.uuid, + {'target': states.ACTIVE}, + expect_errors=True) + self.assertEqual(400, ret.status_code) + self.assertTrue(ret.json['error_message']) @mock.patch.object(rpcapi.ConductorAPI, 'set_boot_device') def test_set_boot_device(self, mock_sbd): diff --git a/ironic/tests/api/v1/test_ports.py b/ironic/tests/api/v1/test_ports.py index 8a2c1201b..fd5a16376 100644 --- a/ironic/tests/api/v1/test_ports.py +++ b/ironic/tests/api/v1/test_ports.py @@ -21,11 +21,14 @@ import mock from oslo_config import cfg from oslo_utils import timeutils from oslo_utils import uuidutils +import six from six.moves.urllib import parse as urlparse from testtools.matchers import HasLength from wsme import types as wtypes +from ironic.api.controllers import base as api_controller from ironic.api.controllers.v1 import port as api_port +from ironic.api.controllers.v1 import utils as api_utils from ironic.common import exception from ironic.conductor import rpcapi from ironic.tests.api import base as api_base @@ -109,7 +112,7 @@ class TestListPorts(api_base.FunctionalTest): self.assertEqual(len(ports), len(data['ports'])) uuids = [n['uuid'] for n in data['ports']] - self.assertEqual(ports.sort(), uuids.sort()) + six.assertCountEqual(self, ports, uuids) def test_links(self): uuid = uuidutils.generate_uuid() @@ -180,6 +183,78 @@ class TestListPorts(api_base.FunctionalTest): self.assertEqual('application/json', response.content_type) self.assertIn(invalid_address, response.json['error_message']) + @mock.patch.object(api_utils, 'get_rpc_node') + def test_get_all_by_node_name_ok(self, mock_get_rpc_node): + # GET /v1/ports specifying node_name - success + mock_get_rpc_node.return_value = self.node + for i in range(5): + if i < 3: + node_id = self.node.id + else: + node_id = 100000 + i + obj_utils.create_test_port(self.context, + node_id=node_id, + uuid=uuidutils.generate_uuid(), + address='52:54:00:cf:2d:3%s' % i) + data = self.get_json("/ports?node=%s" % 'test-node', + headers={api_controller.Version.string: '1.5'}) + self.assertEqual(3, len(data['ports'])) + + @mock.patch.object(api_utils, 'get_rpc_node') + def test_get_all_by_node_uuid_and_name(self, mock_get_rpc_node): + # GET /v1/ports specifying node and uuid - should only use node_uuid + mock_get_rpc_node.return_value = self.node + obj_utils.create_test_port(self.context, node_id=self.node.id) + self.get_json('/ports/detail?node_uuid=%s&node=%s' % + (self.node.uuid, 'node-name')) + mock_get_rpc_node.assert_called_once_with(self.node.uuid) + + @mock.patch.object(api_utils, 'get_rpc_node') + def test_get_all_by_node_name_not_supported(self, mock_get_rpc_node): + # GET /v1/ports specifying node_name - name not supported + mock_get_rpc_node.side_effect = exception.InvalidUuidOrName( + name=self.node.uuid) + for i in range(3): + obj_utils.create_test_port(self.context, + node_id=self.node.id, + uuid=uuidutils.generate_uuid(), + address='52:54:00:cf:2d:3%s' % i) + data = self.get_json("/ports?node=%s" % 'test-node', + expect_errors=True) + self.assertEqual(0, mock_get_rpc_node.call_count) + self.assertEqual(406, data.status_int) + + @mock.patch.object(api_utils, 'get_rpc_node') + def test_detail_by_node_name_ok(self, mock_get_rpc_node): + # GET /v1/ports/detail specifying node_name - success + mock_get_rpc_node.return_value = self.node + port = obj_utils.create_test_port(self.context, node_id=self.node.id) + data = self.get_json('/ports/detail?node=%s' % 'test-node', + headers={api_controller.Version.string: '1.5'}) + self.assertEqual(port.uuid, data['ports'][0]['uuid']) + self.assertEqual(self.node.uuid, data['ports'][0]['node_uuid']) + + @mock.patch.object(api_utils, 'get_rpc_node') + def test_detail_by_node_name_not_supported(self, mock_get_rpc_node): + # GET /v1/ports/detail specifying node_name - name not supported + mock_get_rpc_node.side_effect = exception.InvalidUuidOrName( + name=self.node.uuid) + obj_utils.create_test_port(self.context, node_id=self.node.id) + data = self.get_json('/ports/detail?node=%s' % 'test-node', + expect_errors=True) + self.assertEqual(0, mock_get_rpc_node.call_count) + self.assertEqual(406, data.status_int) + + @mock.patch.object(api_port.PortsController, '_get_ports_collection') + def test_detail_with_incorrect_api_usage(self, mock_gpc): + # GET /v1/ports/detail specifying node and node_uuid. In this case + # we expect the node_uuid interface to be used. + self.get_json('/ports/detail?node=%s&node_uuid=%s' % + ('test-node', self.node.uuid)) + mock_gpc.assert_called_once_with(self.node.uuid, mock.ANY, mock.ANY, + mock.ANY, mock.ANY, mock.ANY, + mock.ANY, mock.ANY) + @mock.patch.object(rpcapi.ConductorAPI, 'update_port') class TestPatch(api_base.FunctionalTest): diff --git a/ironic/tests/api/v1/test_root.py b/ironic/tests/api/v1/test_root.py index 7228663f1..35913e60b 100644 --- a/ironic/tests/api/v1/test_root.py +++ b/ironic/tests/api/v1/test_root.py @@ -12,6 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. +import mock from webob import exc as webob_exc from ironic.api.controllers import v1 as v1_api @@ -25,7 +26,8 @@ class TestV1Routing(api_base.FunctionalTest): def test_route_checks_version(self): self.get_json('/') - self._check_version.assert_called_once() + self._check_version.assert_called_once_with(mock.ANY, + mock.ANY) class TestCheckVersions(test_base.TestCase): diff --git a/ironic/tests/api/v1/test_utils.py b/ironic/tests/api/v1/test_utils.py index dda776b97..f55435884 100644 --- a/ironic/tests/api/v1/test_utils.py +++ b/ironic/tests/api/v1/test_utils.py @@ -13,13 +13,18 @@ # License for the specific language governing permissions and limitations # under the License. +import mock +from oslo_config import cfg +from oslo_utils import uuidutils +import pecan import wsme from ironic.api.controllers.v1 import utils +from ironic.common import exception +from ironic import objects +from ironic.tests.api import utils as test_api_utils from ironic.tests import base -from oslo_config import cfg - CONF = cfg.CONF @@ -47,3 +52,105 @@ class TestApiUtils(base.TestCase): self.assertRaises(wsme.exc.ClientSideError, utils.validate_sort_dir, 'fake-sort') + + +class TestNodeIdent(base.TestCase): + + def setUp(self): + super(TestNodeIdent, self).setUp() + self.valid_name = 'my-host' + self.valid_uuid = uuidutils.generate_uuid() + self.invalid_name = 'Mr Plow' + self.invalid_uuid = '636-555-3226-' + self.node = test_api_utils.post_get_test_node() + + @mock.patch.object(pecan, 'request') + def test_allow_node_logical_names_pre_name(self, mock_pecan_req): + mock_pecan_req.version.minor = 1 + self.assertFalse(utils.allow_node_logical_names()) + + @mock.patch.object(pecan, 'request') + def test_allow_node_logical_names_post_name(self, mock_pecan_req): + mock_pecan_req.version.minor = 5 + self.assertTrue(utils.allow_node_logical_names()) + + def test_is_valid_node_name(self): + self.assertTrue(utils.is_valid_node_name(self.valid_name)) + self.assertFalse(utils.is_valid_node_name(self.invalid_name)) + self.assertFalse(utils.is_valid_node_name(self.valid_uuid)) + self.assertFalse(utils.is_valid_node_name(self.invalid_uuid)) + + @mock.patch.object(pecan, 'request') + @mock.patch.object(utils, 'allow_node_logical_names') + @mock.patch.object(objects.Node, 'get_by_uuid') + @mock.patch.object(objects.Node, 'get_by_name') + def test_get_rpc_node_expect_uuid(self, mock_gbn, mock_gbu, mock_anln, + mock_pr): + mock_anln.return_value = True + self.node['uuid'] = self.valid_uuid + mock_gbu.return_value = self.node + self.assertEqual(self.node, utils.get_rpc_node(self.valid_uuid)) + self.assertEqual(1, mock_gbu.call_count) + self.assertEqual(0, mock_gbn.call_count) + + @mock.patch.object(pecan, 'request') + @mock.patch.object(utils, 'allow_node_logical_names') + @mock.patch.object(objects.Node, 'get_by_uuid') + @mock.patch.object(objects.Node, 'get_by_name') + def test_get_rpc_node_expect_name(self, mock_gbn, mock_gbu, mock_anln, + mock_pr): + mock_anln.return_value = True + self.node['name'] = self.valid_name + mock_gbn.return_value = self.node + self.assertEqual(self.node, utils.get_rpc_node(self.valid_name)) + self.assertEqual(0, mock_gbu.call_count) + self.assertEqual(1, mock_gbn.call_count) + + @mock.patch.object(pecan, 'request') + @mock.patch.object(utils, 'allow_node_logical_names') + @mock.patch.object(objects.Node, 'get_by_uuid') + @mock.patch.object(objects.Node, 'get_by_name') + def test_get_rpc_node_invalid_name(self, mock_gbn, mock_gbu, + mock_anln, mock_pr): + mock_anln.return_value = True + self.assertRaises(exception.InvalidUuidOrName, + utils.get_rpc_node, + self.invalid_name) + + @mock.patch.object(pecan, 'request') + @mock.patch.object(utils, 'allow_node_logical_names') + @mock.patch.object(objects.Node, 'get_by_uuid') + @mock.patch.object(objects.Node, 'get_by_name') + def test_get_rpc_node_invalid_uuid(self, mock_gbn, mock_gbu, + mock_anln, mock_pr): + mock_anln.return_value = True + self.assertRaises(exception.InvalidUuidOrName, + utils.get_rpc_node, + self.invalid_uuid) + + @mock.patch.object(pecan, 'request') + @mock.patch.object(utils, 'allow_node_logical_names') + @mock.patch.object(objects.Node, 'get_by_uuid') + @mock.patch.object(objects.Node, 'get_by_name') + def test_get_rpc_node_by_uuid_no_logical_name(self, mock_gbn, mock_gbu, + mock_anln, mock_pr): + # allow_node_logical_name() should have no effect + mock_anln.return_value = False + self.node['uuid'] = self.valid_uuid + mock_gbu.return_value = self.node + self.assertEqual(self.node, utils.get_rpc_node(self.valid_uuid)) + self.assertEqual(1, mock_gbu.call_count) + self.assertEqual(0, mock_gbn.call_count) + + @mock.patch.object(pecan, 'request') + @mock.patch.object(utils, 'allow_node_logical_names') + @mock.patch.object(objects.Node, 'get_by_uuid') + @mock.patch.object(objects.Node, 'get_by_name') + def test_get_rpc_node_by_name_no_logical_name(self, mock_gbn, mock_gbu, + mock_anln, mock_pr): + mock_anln.return_value = False + self.node['name'] = self.valid_name + mock_gbn.return_value = self.node + self.assertRaises(exception.NodeNotFound, + utils.get_rpc_node, + self.valid_name) diff --git a/ironic/tests/conductor/test_manager.py b/ironic/tests/conductor/test_manager.py index e2dd06568..47a6e77f7 100644 --- a/ironic/tests/conductor/test_manager.py +++ b/ironic/tests/conductor/test_manager.py @@ -1696,7 +1696,7 @@ class DoNodeCleanTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase): node.refresh() # Assert that the node was moved to available without cleaning - mock_validate.assert_not_called() + self.assertFalse(mock_validate.called) self.assertEqual(states.AVAILABLE, node.provision_state) self.assertEqual(states.NOSTATE, node.target_provision_state) self.assertEqual({}, node.clean_step) @@ -1725,9 +1725,9 @@ class DoNodeCleanTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase): self.service._worker_pool.waitall() node.refresh() - mock_validate.assert_called_once() + mock_validate.assert_called_once_with(task) mock_next_step.assert_called_once_with(mock.ANY, [], {}) - mock_steps.assert_called_once() + mock_steps.assert_called_once_with(task) # Check that state didn't change self.assertEqual(states.CLEANING, node.provision_state) @@ -1806,7 +1806,7 @@ class DoNodeCleanTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase): # Cleaning should be complete without calling additional steps self.assertEqual(states.AVAILABLE, node.provision_state) self.assertEqual({}, node.clean_step) - mock_execute.assert_not_called() + self.assertFalse(mock_execute.called) @mock.patch('ironic.drivers.modules.fake.FakePower.execute_clean_step') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step') @@ -1869,7 +1869,7 @@ class DoNodeCleanTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase): self.assertEqual({}, node.clean_step) self.assertIsNotNone(node.last_error) self.assertTrue(node.maintenance) - mock_execute.assert_not_called() + self.assertFalse(mock_execute.called) @mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step') def test__do_next_clean_step_fail(self, mock_execute): @@ -1897,7 +1897,6 @@ class DoNodeCleanTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase): self.assertEqual({}, node.clean_step) self.assertIsNotNone(node.last_error) self.assertTrue(node.maintenance) - mock_execute.assert_not_called() mock_execute.assert_called_once_with(mock.ANY, self.clean_steps[0]) @mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step') @@ -1923,7 +1922,7 @@ class DoNodeCleanTestCase(_ServiceSetUpMixin, tests_db_base.DbTestCase): # Cleaning should be complete without calling additional steps self.assertEqual(states.AVAILABLE, node.provision_state) self.assertEqual({}, node.clean_step) - mock_execute.assert_not_called() + self.assertFalse(mock_execute.called) @mock.patch('ironic.drivers.modules.fake.FakePower.execute_clean_step') @mock.patch('ironic.drivers.modules.fake.FakeDeploy.execute_clean_step') @@ -2841,7 +2840,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) get_node_mock.assert_called_once_with(self.context, self.node.id) - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) self.assertFalse(sync_mock.called) def test_node_in_deploywait_on_acquire(self, get_nodeinfo_mock, @@ -2853,7 +2852,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): task = self._create_task( node_attrs=dict(provision_state=states.DEPLOYWAIT, target_provision_state=states.ACTIVE, - id=self.node.id)) + uuid=self.node.uuid)) acquire_mock.side_effect = self._get_acquire_side_effect(task) self.service._sync_power_states(self.context) @@ -2863,7 +2862,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) get_node_mock.assert_called_once_with(self.context, self.node.id) - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) self.assertFalse(sync_mock.called) def test_node_in_maintenance_on_acquire(self, get_nodeinfo_mock, @@ -2873,7 +2872,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): get_node_mock.return_value = self.node mapped_mock.return_value = True task = self._create_task( - node_attrs=dict(maintenance=True, id=self.node.id)) + node_attrs=dict(maintenance=True, uuid=self.node.uuid)) acquire_mock.side_effect = self._get_acquire_side_effect(task) self.service._sync_power_states(self.context) @@ -2883,7 +2882,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) get_node_mock.assert_called_once_with(self.context, self.node.id) - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) self.assertFalse(sync_mock.called) def test_node_disappears_on_acquire(self, get_nodeinfo_mock, @@ -2902,7 +2901,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) get_node_mock.assert_called_once_with(self.context, self.node.id) - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) self.assertFalse(sync_mock.called) def test_single_node(self, get_nodeinfo_mock, get_node_mock, @@ -2910,7 +2909,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): get_nodeinfo_mock.return_value = self._get_nodeinfo_list_response() get_node_mock.return_value = self.node mapped_mock.return_value = True - task = self._create_task(node_attrs=dict(id=self.node.id)) + task = self._create_task(node_attrs=dict(uuid=self.node.uuid)) acquire_mock.side_effect = self._get_acquire_side_effect(task) self.service._sync_power_states(self.context) @@ -2920,7 +2919,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) get_node_mock.assert_called_once_with(self.context, self.node.id) - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) sync_mock.assert_called_once_with(task, mock.ANY) def test__sync_power_state_multiple_nodes(self, get_nodeinfo_mock, @@ -2957,16 +2956,16 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_map[n.uuid] = False if i == 2 else True get_node_map[n.uuid] = n - tasks = [self._create_task(node_attrs=dict(id=1)), + tasks = [self._create_task(node_attrs=dict(uuid=nodes[0].uuid)), exception.NodeLocked(node=7, host='fake'), exception.NodeNotFound(node=8, host='fake'), self._create_task( - node_attrs=dict(id=9, + node_attrs=dict(uuid=nodes[8].uuid, provision_state=states.DEPLOYWAIT, target_provision_state=states.ACTIVE)), self._create_task( - node_attrs=dict(id=10, maintenance=True)), - self._create_task(node_attrs=dict(id=11))] + node_attrs=dict(uuid=nodes[9].uuid, maintenance=True)), + self._create_task(node_attrs=dict(uuid=nodes[10].uuid))] def _get_node_side_effect(ctxt, node_id): if node_id == 6: @@ -2994,7 +2993,7 @@ class ManagerSyncPowerStatesTestCase(_CommonMixIn, tests_db_base.DbTestCase): for x in nodes[:1] + nodes[2:]] self.assertEqual(get_node_calls, get_node_mock.call_args_list) - acquire_calls = [mock.call(self.context, x.id) + acquire_calls = [mock.call(self.context, x.uuid) for x in nodes[:1] + nodes[6:]] self.assertEqual(acquire_calls, acquire_mock.call_args_list) sync_calls = [mock.call(tasks[0], mock.ANY), @@ -3339,20 +3338,19 @@ class ManagerTestProperties(tests_db_base.DbTestCase): def test_driver_properties_fake_ilo(self): expected = ['ilo_address', 'ilo_username', 'ilo_password', - 'client_port', 'client_timeout', 'inspect_ports', - 'ilo_change_password'] + 'client_port', 'client_timeout', 'ilo_change_password'] self._check_driver_properties("fake_ilo", expected) def test_driver_properties_ilo_iscsi(self): expected = ['ilo_address', 'ilo_username', 'ilo_password', 'client_port', 'client_timeout', 'ilo_deploy_iso', - 'console_port', 'inspect_ports', 'ilo_change_password'] + 'console_port', 'ilo_change_password'] self._check_driver_properties("iscsi_ilo", expected) def test_driver_properties_agent_ilo(self): expected = ['ilo_address', 'ilo_username', 'ilo_password', 'client_port', 'client_timeout', 'ilo_deploy_iso', - 'console_port', 'inspect_ports', 'ilo_change_password'] + 'console_port', 'ilo_change_password'] self._check_driver_properties("agent_ilo", expected) def test_driver_properties_fail(self): @@ -3437,7 +3435,7 @@ class ManagerSyncLocalStateTestCase(_CommonMixIn, tests_db_base.DbTestCase): self._assert_get_nodeinfo_args(get_nodeinfo_mock) mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) get_authtoken_mock.assert_called_once_with() - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) # assert spawn_after has been called self.task.spawn_after.assert_called_once_with( self.service._spawn_worker, @@ -3471,7 +3469,7 @@ class ManagerSyncLocalStateTestCase(_CommonMixIn, tests_db_base.DbTestCase): # assert acquire() gets called 2 times only instead of 3. When # NoFreeConductorWorker is raised the loop should be broken - expected = [mock.call(self.context, self.node.id)] * 2 + expected = [mock.call(self.context, self.node.uuid)] * 2 self.assertEqual(expected, acquire_mock.call_args_list) # Only one auth token needed for all runs @@ -3504,7 +3502,7 @@ class ManagerSyncLocalStateTestCase(_CommonMixIn, tests_db_base.DbTestCase): self.assertEqual(expected, mapped_mock.call_args_list) # assert acquire() gets called 3 times - expected = [mock.call(self.context, self.node.id)] * 3 + expected = [mock.call(self.context, self.node.uuid)] * 3 self.assertEqual(expected, acquire_mock.call_args_list) # Only one auth token needed for all runs @@ -3539,7 +3537,7 @@ class ManagerSyncLocalStateTestCase(_CommonMixIn, tests_db_base.DbTestCase): mapped_mock.assert_called_once_with(self.node.uuid, self.node.driver) # assert acquire() gets called only once because of the worker limit - acquire_mock.assert_called_once_with(self.context, self.node.id) + acquire_mock.assert_called_once_with(self.context, self.node.uuid) # Only one auth token needed for all runs get_authtoken_mock.assert_called_once_with() diff --git a/ironic/tests/db/sqlalchemy/test_migrations.py b/ironic/tests/db/sqlalchemy/test_migrations.py index b6ba0e095..37b539e02 100644 --- a/ironic/tests/db/sqlalchemy/test_migrations.py +++ b/ironic/tests/db/sqlalchemy/test_migrations.py @@ -15,15 +15,9 @@ # under the License. """ -Tests for database migrations. This test case reads the configuration -file test_migrations.conf for database connection settings -to use in the tests. For each connection found in the config file, -the test case runs a series of test cases to ensure that migrations work -properly. - -There are also "opportunistic" tests for both mysql and postgresql in here, -which allows testing against all 3 databases (sqlite in memory, mysql, pg) in -a properly configured unit test environment. +Tests for database migrations. There are "opportunistic" tests for both mysql +and postgresql in here, which allows testing against these databases in a +properly configured unit test environment. For the opportunistic testing you need to set up a db named 'openstack_citest' with user 'openstack_citest' and password 'openstack_citest' on localhost. @@ -314,12 +308,8 @@ class MigrationCheckersMixin(object): 'instance_uuid': instance_uuid} nodes.insert().values(data).execute() data['uuid'] = uuidutils.generate_uuid() - # TODO(viktors): Remove check on sqlalchemy.exc.IntegrityError, when - # Ironic will use oslo_db 0.4.0 or higher. - # See bug #1214341 for details. - self.assertRaises( - (sqlalchemy.exc.IntegrityError, db_exc.DBDuplicateEntry), - nodes.insert().execute, data) + self.assertRaises(db_exc.DBDuplicateEntry, + nodes.insert().execute, data) def _check_242cc6a923b3(self, engine, data): nodes = db_utils.get_table(engine, 'nodes') @@ -374,6 +364,15 @@ class MigrationCheckersMixin(object): self.assertIsInstance(nodes.c.clean_step.type, sqlalchemy.types.String) + def _check_2fb93ffd2af1(self, engine, data): + nodes = db_utils.get_table(engine, 'nodes') + bigstring = 'a' * 255 + uuid = uuidutils.generate_uuid() + data = {'uuid': uuid, 'name': bigstring} + nodes.insert().execute(data) + node = nodes.select(nodes.c.uuid == uuid).execute().first() + self.assertEqual(bigstring, node['name']) + def test_upgrade_and_version(self): with patch_with_engine(self.engine): self.migration_api.upgrade('head') diff --git a/ironic/tests/db/test_chassis.py b/ironic/tests/db/test_chassis.py index de81e756e..480d4e6cd 100644 --- a/ironic/tests/db/test_chassis.py +++ b/ironic/tests/db/test_chassis.py @@ -25,40 +25,36 @@ from ironic.tests.db import utils class DbChassisTestCase(base.DbTestCase): - def _create_test_chassis(self, **kwargs): - ch = utils.get_test_chassis(**kwargs) - self.dbapi.create_chassis(ch) - return ch + def setUp(self): + super(DbChassisTestCase, self).setUp() + self.chassis = utils.create_test_chassis() def test_get_chassis_list(self): - uuids = [] + uuids = [self.chassis.uuid] for i in range(1, 6): - n = utils.get_test_chassis(id=i, uuid=uuidutils.generate_uuid()) - self.dbapi.create_chassis(n) - uuids.append(six.text_type(n['uuid'])) + ch = utils.create_test_chassis(uuid=uuidutils.generate_uuid()) + uuids.append(six.text_type(ch.uuid)) res = self.dbapi.get_chassis_list() res_uuids = [r.uuid for r in res] - self.assertEqual(uuids.sort(), res_uuids.sort()) + six.assertCountEqual(self, uuids, res_uuids) def test_get_chassis_by_id(self): - ch = self._create_test_chassis() - chassis = self.dbapi.get_chassis_by_id(ch['id']) + chassis = self.dbapi.get_chassis_by_id(self.chassis.id) - self.assertEqual(ch['uuid'], chassis.uuid) + self.assertEqual(self.chassis.uuid, chassis.uuid) def test_get_chassis_by_uuid(self): - ch = self._create_test_chassis() - chassis = self.dbapi.get_chassis_by_uuid(ch['uuid']) + chassis = self.dbapi.get_chassis_by_uuid(self.chassis.uuid) - self.assertEqual(ch['id'], chassis.id) + self.assertEqual(self.chassis.id, chassis.id) def test_get_chassis_that_does_not_exist(self): self.assertRaises(exception.ChassisNotFound, self.dbapi.get_chassis_by_id, 666) def test_update_chassis(self): - ch = self._create_test_chassis() - res = self.dbapi.update_chassis(ch['id'], {'description': 'hello'}) + res = self.dbapi.update_chassis(self.chassis.id, + {'description': 'hello'}) self.assertEqual('hello', res.description) @@ -67,32 +63,27 @@ class DbChassisTestCase(base.DbTestCase): self.dbapi.update_chassis, 666, {'description': ''}) def test_update_chassis_uuid(self): - ch = self._create_test_chassis() self.assertRaises(exception.InvalidParameterValue, - self.dbapi.update_chassis, ch['id'], + self.dbapi.update_chassis, self.chassis.id, {'uuid': 'hello'}) def test_destroy_chassis(self): - ch = self._create_test_chassis() - self.dbapi.destroy_chassis(ch['id']) + self.dbapi.destroy_chassis(self.chassis.id) self.assertRaises(exception.ChassisNotFound, - self.dbapi.get_chassis_by_id, ch['id']) + self.dbapi.get_chassis_by_id, self.chassis.id) def test_destroy_chassis_that_does_not_exist(self): self.assertRaises(exception.ChassisNotFound, self.dbapi.destroy_chassis, 666) def test_destroy_chassis_with_nodes(self): - ch = self._create_test_chassis() - utils.create_test_node(chassis_id=ch['id']) + utils.create_test_node(chassis_id=self.chassis.id) self.assertRaises(exception.ChassisNotEmpty, - self.dbapi.destroy_chassis, ch['id']) + self.dbapi.destroy_chassis, self.chassis.id) def test_create_chassis_already_exists(self): - uuid = uuidutils.generate_uuid() - self._create_test_chassis(id=1, uuid=uuid) self.assertRaises(exception.ChassisAlreadyExists, - self._create_test_chassis, - id=2, uuid=uuid) + utils.create_test_chassis, + uuid=self.chassis.uuid) diff --git a/ironic/tests/db/test_conductor.py b/ironic/tests/db/test_conductor.py index d93aad120..1ff182615 100644 --- a/ironic/tests/db/test_conductor.py +++ b/ironic/tests/db/test_conductor.py @@ -64,7 +64,7 @@ class DbConductorTestCase(base.DbTestCase): self.dbapi.unregister_conductor, c.hostname) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_touch_conductor(self, mock_utcnow): test_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = test_time @@ -110,7 +110,7 @@ class DbConductorTestCase(base.DbTestCase): self.assertEqual('hostname2', node2.reservation) self.assertIsNone(node3.reservation) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_driver_dict_one_host_no_driver(self, mock_utcnow): h = 'fake-host' expected = {} @@ -120,7 +120,7 @@ class DbConductorTestCase(base.DbTestCase): result = self.dbapi.get_active_driver_dict() self.assertEqual(expected, result) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_driver_dict_one_host_one_driver(self, mock_utcnow): h = 'fake-host' d = 'fake-driver' @@ -131,7 +131,7 @@ class DbConductorTestCase(base.DbTestCase): result = self.dbapi.get_active_driver_dict() self.assertEqual(expected, result) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_driver_dict_one_host_many_drivers(self, mock_utcnow): h = 'fake-host' d1 = 'driver-one' @@ -143,7 +143,7 @@ class DbConductorTestCase(base.DbTestCase): result = self.dbapi.get_active_driver_dict() self.assertEqual(expected, result) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_driver_dict_many_hosts_one_driver(self, mock_utcnow): h1 = 'host-one' h2 = 'host-two' @@ -156,7 +156,7 @@ class DbConductorTestCase(base.DbTestCase): result = self.dbapi.get_active_driver_dict() self.assertEqual(expected, result) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_driver_dict_many_hosts_and_drivers(self, mock_utcnow): h1 = 'host-one' h2 = 'host-two' @@ -172,7 +172,7 @@ class DbConductorTestCase(base.DbTestCase): result = self.dbapi.get_active_driver_dict() self.assertEqual(expected, result) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_active_driver_dict_with_old_conductor(self, mock_utcnow): past = datetime.datetime(2000, 1, 1, 0, 0) present = past + datetime.timedelta(minutes=2) diff --git a/ironic/tests/db/test_nodes.py b/ironic/tests/db/test_nodes.py index 35c542e5e..be44943b4 100644 --- a/ironic/tests/db/test_nodes.py +++ b/ironic/tests/db/test_nodes.py @@ -33,9 +33,6 @@ class DbNodeTestCase(base.DbTestCase): def test_create_node(self): utils.create_test_node() - def test_create_node_nullable_chassis_id(self): - utils.create_test_node(chassis_id=None) - def test_create_node_already_exists(self): utils.create_test_node() self.assertRaises(exception.NodeAlreadyExists, @@ -139,7 +136,7 @@ class DbNodeTestCase(base.DbTestCase): res = self.dbapi.get_node_list(filters={'maintenance': False}) self.assertEqual([node1.id], [r.id for r in res]) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_nodeinfo_list_provision(self, mock_utcnow): past = datetime.datetime(2000, 1, 1, 0, 0) next = past + datetime.timedelta(minutes=8) @@ -164,7 +161,7 @@ class DbNodeTestCase(base.DbTestCase): states.DEPLOYWAIT}) self.assertEqual([node2.id], [r[0] for r in res]) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_get_nodeinfo_list_inspection(self, mock_utcnow): past = datetime.datetime(2000, 1, 1, 0, 0) next = past + datetime.timedelta(minutes=8) @@ -197,13 +194,11 @@ class DbNodeTestCase(base.DbTestCase): uuids.append(six.text_type(node['uuid'])) res = self.dbapi.get_node_list() res_uuids = [r.uuid for r in res] - self.assertEqual(uuids.sort(), res_uuids.sort()) + six.assertCountEqual(self, uuids, res_uuids) def test_get_node_list_with_filters(self): - ch1 = utils.get_test_chassis(id=1, uuid=uuidutils.generate_uuid()) - ch2 = utils.get_test_chassis(id=2, uuid=uuidutils.generate_uuid()) - self.dbapi.create_chassis(ch1) - self.dbapi.create_chassis(ch2) + ch1 = utils.create_test_chassis(uuid=uuidutils.generate_uuid()) + ch2 = utils.create_test_chassis(uuid=uuidutils.generate_uuid()) node1 = utils.create_test_node(driver='driver-one', instance_uuid=uuidutils.generate_uuid(), @@ -359,7 +354,7 @@ class DbNodeTestCase(base.DbTestCase): node2.id, {'instance_uuid': new_i_uuid}) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_update_node_provision(self, mock_utcnow): mocked_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = mocked_time @@ -383,7 +378,7 @@ class DbNodeTestCase(base.DbTestCase): self.assertIsNone(res['provision_updated_at']) self.assertIsNone(res['inspection_started_at']) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_update_node_inspection_started_at(self, mock_utcnow): mocked_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = mocked_time @@ -395,7 +390,7 @@ class DbNodeTestCase(base.DbTestCase): timeutils.normalize_time(result)) self.assertIsNone(res['inspection_finished_at']) - @mock.patch.object(timeutils, 'utcnow') + @mock.patch.object(timeutils, 'utcnow', autospec=True) def test_update_node_inspection_finished_at(self, mock_utcnow): mocked_time = datetime.datetime(2000, 1, 1, 0, 0) mock_utcnow.return_value = mocked_time diff --git a/ironic/tests/db/test_ports.py b/ironic/tests/db/test_ports.py index afb1284e4..947de11a1 100644 --- a/ironic/tests/db/test_ports.py +++ b/ironic/tests/db/test_ports.py @@ -50,9 +50,11 @@ class DbPortTestCase(base.DbTestCase): port = db_utils.create_test_port(uuid=uuidutils.generate_uuid(), address='52:54:00:cf:2d:4%s' % i) uuids.append(six.text_type(port.uuid)) + # Also add the uuid for the port created in setUp() + uuids.append(six.text_type(self.port.uuid)) res = self.dbapi.get_port_list() res_uuids = [r.uuid for r in res] - self.assertEqual(uuids.sort(), res_uuids.sort()) + six.assertCountEqual(self, uuids, res_uuids) def test_get_ports_by_node_id(self): res = self.dbapi.get_ports_by_node_id(self.node.id) diff --git a/ironic/tests/db/utils.py b/ironic/tests/db/utils.py index 677187e80..dbb8e8d95 100644 --- a/ironic/tests/db/utils.py +++ b/ironic/tests/db/utils.py @@ -149,6 +149,7 @@ def get_test_agent_driver_info(): def get_test_agent_driver_internal_info(): return { 'agent_url': 'http://127.0.0.1/foo', + 'is_whole_disk_image': True, } @@ -187,7 +188,7 @@ def get_test_node(**kw): 'id': kw.get('id', 123), 'name': kw.get('name', None), 'uuid': kw.get('uuid', '1be26c0b-03f2-4d2e-ae87-c02d7f33c123'), - 'chassis_id': kw.get('chassis_id', 42), + 'chassis_id': kw.get('chassis_id', None), 'conductor_affinity': kw.get('conductor_affinity', None), 'power_state': kw.get('power_state', states.NOSTATE), 'target_power_state': kw.get('target_power_state', states.NOSTATE), @@ -272,6 +273,23 @@ def get_test_chassis(**kw): } +def create_test_chassis(**kw): + """Create test chassis entry in DB and return Chassis DB object. + + Function to be used to create test Chassis objects in the database. + + :param kw: kwargs with overriding values for chassis's attributes. + :returns: Test Chassis DB object. + + """ + chassis = get_test_chassis(**kw) + # Let DB generate ID if it isn't specified explicitly + if 'id' not in kw: + del chassis['id'] + dbapi = db_api.get_instance() + return dbapi.create_chassis(chassis) + + def get_test_conductor(**kw): return { 'id': kw.get('id', 6), diff --git a/ironic/tests/dhcp/test_factory.py b/ironic/tests/dhcp/test_factory.py index 37ce4c040..850479f8f 100644 --- a/ironic/tests/dhcp/test_factory.py +++ b/ironic/tests/dhcp/test_factory.py @@ -13,10 +13,13 @@ # License for the specific language governing permissions and limitations # under the License. +import inspect + import mock from ironic.common import dhcp_factory from ironic.common import exception +from ironic.dhcp import base as base_class from ironic.dhcp import neutron from ironic.dhcp import none from ironic.tests import base @@ -68,3 +71,35 @@ class TestDHCPFactory(base.TestCase): group='dhcp') self.assertRaises(exception.DHCPNotFound, dhcp_factory.DHCPFactory) + + +class CompareBasetoModules(base.TestCase): + + def test_drivers_match_dhcp_base(self): + def _get_public_apis(inst): + methods = {} + for (name, value) in inspect.getmembers(inst, inspect.ismethod): + if name.startswith("_"): + continue + methods[name] = value + return methods + + def _compare_classes(baseclass, driverclass): + + basemethods = _get_public_apis(baseclass) + implmethods = _get_public_apis(driverclass) + + for name in basemethods: + baseargs = inspect.getargspec(basemethods[name]) + implargs = inspect.getargspec(implmethods[name]) + self.assertEqual( + baseargs, + implargs, + "%s args of %s don't match base %s" % ( + name, + driverclass, + baseclass) + ) + + _compare_classes(base_class.BaseDHCP, none.NoneDHCPApi) + _compare_classes(base_class.BaseDHCP, neutron.NeutronDHCPApi) diff --git a/ironic/tests/dhcp/test_neutron.py b/ironic/tests/dhcp/test_neutron.py index 4cb4402f6..11a716fa9 100644 --- a/ironic/tests/dhcp/test_neutron.py +++ b/ironic/tests/dhcp/test_neutron.py @@ -377,23 +377,25 @@ class TestNeutron(db_base.DbTestCase): 'network_id': '00000000-0000-0000-0000-000000000000', 'admin_state_up': True, 'mac_address': self.ports[0].address}}) - @mock.patch('ironic.conductor.manager.cleaning_error_handler') + @mock.patch.object(neutron.NeutronDHCPApi, '_rollback_cleaning_ports') @mock.patch.object(client.Client, 'create_port') - def test_create_cleaning_ports_fail(self, create_mock, error_mock): - # Check that if creating a port fails, the node goes to cleanfail + def test_create_cleaning_ports_fail(self, create_mock, rollback_mock): + # Check that if creating a port fails, the ports are cleaned up create_mock.side_effect = neutron_client_exc.ConnectionFailed api = dhcp_factory.DHCPFactory().provider with task_manager.acquire(self.context, self.node.uuid) as task: - api.create_cleaning_ports(task) - error_mock.assert_called_once_with(task, mock.ANY) + self.assertRaises(exception.NodeCleaningFailure, + api.create_cleaning_ports, + task) create_mock.assert_called_once_with({'port': { 'network_id': '00000000-0000-0000-0000-000000000000', 'admin_state_up': True, 'mac_address': self.ports[0].address}}) + rollback_mock.assert_called_once_with(task) - @mock.patch('ironic.conductor.manager.cleaning_error_handler') @mock.patch.object(client.Client, 'create_port') - def test_create_cleaning_ports_bad_config(self, create_mock, error_mock): + def test_create_cleaning_ports_bad_config(self, create_mock): + # Check an error is raised if the cleaning network is not set self.config(cleaning_network_uuid=None, group='neutron') api = dhcp_factory.DHCPFactory().provider @@ -417,32 +419,31 @@ class TestNeutron(db_base.DbTestCase): network_id='00000000-0000-0000-0000-000000000000') delete_mock.assert_called_once_with(self.neutron_port['id']) - @mock.patch('ironic.conductor.manager.cleaning_error_handler') @mock.patch.object(client.Client, 'list_ports') - def test_delete_cleaning_ports_list_fail(self, list_mock, error_mock): + def test_delete_cleaning_ports_list_fail(self, list_mock): # Check that if listing ports fails, the node goes to cleanfail list_mock.side_effect = neutron_client_exc.ConnectionFailed api = dhcp_factory.DHCPFactory().provider with task_manager.acquire(self.context, self.node.uuid) as task: - api.delete_cleaning_ports(task) + self.assertRaises(exception.NodeCleaningFailure, + api.delete_cleaning_ports, + task) list_mock.assert_called_once_with( network_id='00000000-0000-0000-0000-000000000000') - error_mock.assert_called_once_with(task, mock.ANY) - @mock.patch('ironic.conductor.manager.cleaning_error_handler') @mock.patch.object(client.Client, 'delete_port') @mock.patch.object(client.Client, 'list_ports') - def test_delete_cleaning_ports_delete_fail(self, list_mock, delete_mock, - error_mock): + def test_delete_cleaning_ports_delete_fail(self, list_mock, delete_mock): # Check that if deleting ports fails, the node goes to cleanfail list_mock.return_value = {'ports': [self.neutron_port]} delete_mock.side_effect = neutron_client_exc.ConnectionFailed api = dhcp_factory.DHCPFactory().provider with task_manager.acquire(self.context, self.node.uuid) as task: - api.delete_cleaning_ports(task) + self.assertRaises(exception.NodeCleaningFailure, + api.delete_cleaning_ports, + task) list_mock.assert_called_once_with( network_id='00000000-0000-0000-0000-000000000000') delete_mock.assert_called_once_with(self.neutron_port['id']) - error_mock.assert_called_once_with(task, mock.ANY) diff --git a/ironic/tests/drivers/agent_pxe_config.template b/ironic/tests/drivers/agent_pxe_config.template index 414703a1c..7b26d58cf 100644 --- a/ironic/tests/drivers/agent_pxe_config.template +++ b/ironic/tests/drivers/agent_pxe_config.template @@ -2,4 +2,4 @@ default deploy label deploy kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel -append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk text test_param ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=agent_ipmitool root_device=vendor=fake,size=123 +append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk text test_param ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=agent_ipmitool root_device=vendor=fake,size=123 coreos.configdrive=0 diff --git a/ironic/tests/drivers/amt/test_vendor.py b/ironic/tests/drivers/amt/test_vendor.py index 1d1742918..a6ddfc769 100644 --- a/ironic/tests/drivers/amt/test_vendor.py +++ b/ironic/tests/drivers/amt/test_vendor.py @@ -37,7 +37,8 @@ class AMTPXEVendorPassthruTestCase(db_base.DbTestCase): driver='pxe_amt', driver_info=INFO_DICT) def test_vendor_routes(self): - expected = ['heartbeat', 'pass_deploy_info'] + expected = ['heartbeat', 'pass_deploy_info', + 'pass_bootloader_install_info'] with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: vendor_routes = task.driver.vendor.vendor_routes @@ -54,13 +55,64 @@ class AMTPXEVendorPassthruTestCase(db_base.DbTestCase): @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device') @mock.patch.object(pxe.VendorPassthru, 'pass_deploy_info') - def test_vendorpassthru_pass_deploy_info(self, mock_pxe_vendorpassthru, - mock_ensure): + def test_vendorpassthru_pass_deploy_info_netboot(self, + mock_pxe_vendorpassthru, + mock_ensure): kwargs = {'address': '123456'} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.node.provision_state = states.DEPLOYWAIT task.node.target_provision_state = states.ACTIVE + task.node.instance_info['capabilities'] = { + "boot_option": "netboot" + } task.driver.vendor.pass_deploy_info(task, **kwargs) mock_ensure.assert_called_with(task.node, boot_devices.PXE) mock_pxe_vendorpassthru.assert_called_once_with(task, **kwargs) + + @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device') + @mock.patch.object(pxe.VendorPassthru, 'pass_deploy_info') + def test_vendorpassthru_pass_deploy_info_localboot(self, + mock_pxe_vendorpassthru, + mock_ensure): + kwargs = {'address': '123456'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.provision_state = states.DEPLOYWAIT + task.node.target_provision_state = states.ACTIVE + task.node.instance_info['capabilities'] = {"boot_option": "local"} + task.driver.vendor.pass_deploy_info(task, **kwargs) + self.assertFalse(mock_ensure.called) + mock_pxe_vendorpassthru.assert_called_once_with(task, **kwargs) + + @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device') + @mock.patch.object(pxe.VendorPassthru, 'continue_deploy') + def test_vendorpassthru_continue_deploy_netboot(self, + mock_pxe_vendorpassthru, + mock_ensure): + kwargs = {'address': '123456'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.provision_state = states.DEPLOYWAIT + task.node.target_provision_state = states.ACTIVE + task.node.instance_info['capabilities'] = { + "boot_option": "netboot" + } + task.driver.vendor.continue_deploy(task, **kwargs) + mock_ensure.assert_called_with(task.node, boot_devices.PXE) + mock_pxe_vendorpassthru.assert_called_once_with(task, **kwargs) + + @mock.patch.object(amt_mgmt.AMTManagement, 'ensure_next_boot_device') + @mock.patch.object(pxe.VendorPassthru, 'continue_deploy') + def test_vendorpassthru_continue_deploy_localboot(self, + mock_pxe_vendorpassthru, + mock_ensure): + kwargs = {'address': '123456'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.provision_state = states.DEPLOYWAIT + task.node.target_provision_state = states.ACTIVE + task.node.instance_info['capabilities'] = {"boot_option": "local"} + task.driver.vendor.continue_deploy(task, **kwargs) + self.assertFalse(mock_ensure.called) + mock_pxe_vendorpassthru.assert_called_once_with(task, **kwargs) diff --git a/ironic/tests/drivers/drac/test_client.py b/ironic/tests/drivers/drac/test_client.py index a5df57bd6..247f34253 100644 --- a/ironic/tests/drivers/drac/test_client.py +++ b/ironic/tests/drivers/drac/test_client.py @@ -15,6 +15,7 @@ Test class for DRAC client wrapper. """ +import time from xml.etree import ElementTree import mock @@ -51,6 +52,25 @@ class DracClientTestCase(base.TestCase): None, self.resource_uri) mock_xml.context.assert_called_once_with() + @mock.patch.object(time, 'sleep', lambda seconds: None) + def test_wsman_enumerate_retry(self, mock_client_pywsman): + mock_xml = test_utils.mock_wsman_root('<test></test>') + mock_pywsman_client = mock_client_pywsman.Client.return_value + mock_pywsman_client.enumerate.side_effect = [None, mock_xml] + + client = drac_client.Client(**INFO_DICT) + client.wsman_enumerate(self.resource_uri) + + mock_options = mock_client_pywsman.ClientOptions.return_value + mock_options.set_flags.assert_called_once_with( + mock_client_pywsman.FLAG_ENUMERATION_OPTIMIZATION) + mock_options.set_max_elements.assert_called_once_with(100) + mock_pywsman_client.enumerate.assert_has_calls([ + mock.call(mock_options, None, self.resource_uri), + mock.call(mock_options, None, self.resource_uri) + ]) + mock_xml.context.assert_called_once_with() + def test_wsman_enumerate_with_additional_pull(self, mock_client_pywsman): mock_root = mock.Mock() mock_root.string.side_effect = [test_utils.build_soap_xml( @@ -118,6 +138,24 @@ class DracClientTestCase(base.TestCase): mock_pywsman_client.invoke.assert_called_once_with(mock_options, self.resource_uri, method_name, None) + @mock.patch.object(time, 'sleep', lambda seconds: None) + def test_wsman_invoke_retry(self, mock_client_pywsman): + result_xml = test_utils.build_soap_xml( + [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri) + mock_xml = test_utils.mock_wsman_root(result_xml) + mock_pywsman_client = mock_client_pywsman.Client.return_value + mock_pywsman_client.invoke.side_effect = [None, mock_xml] + + method_name = 'method' + client = drac_client.Client(**INFO_DICT) + client.wsman_invoke(self.resource_uri, method_name) + + mock_options = mock_client_pywsman.ClientOptions.return_value + mock_pywsman_client.invoke.assert_has_calls([ + mock.call(mock_options, self.resource_uri, method_name, None), + mock.call(mock_options, self.resource_uri, method_name, None) + ]) + def test_wsman_invoke_with_selectors(self, mock_client_pywsman): result_xml = test_utils.build_soap_xml( [{'ReturnValue': drac_client.RET_SUCCESS}], self.resource_uri) diff --git a/ironic/tests/drivers/drac/test_power.py b/ironic/tests/drivers/drac/test_power.py index a3fd5fc06..796368d24 100644 --- a/ironic/tests/drivers/drac/test_power.py +++ b/ironic/tests/drivers/drac/test_power.py @@ -137,7 +137,6 @@ class DracPowerTestCase(base.DbTestCase): @mock.patch.object(drac_power, '_set_power_state') def test_set_power_state(self, mock_set_power_state): - mock_set_power_state.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.power.set_power_state(task, states.POWER_ON) @@ -145,10 +144,22 @@ class DracPowerTestCase(base.DbTestCase): states.POWER_ON) @mock.patch.object(drac_power, '_set_power_state') - def test_reboot(self, mock_set_power_state): - mock_set_power_state.return_value = states.REBOOT + @mock.patch.object(drac_power, '_get_power_state') + def test_reboot(self, mock_get_power_state, mock_set_power_state): + mock_get_power_state.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: task.driver.power.reboot(task) mock_set_power_state.assert_called_once_with(task.node, states.REBOOT) + + @mock.patch.object(drac_power, '_set_power_state') + @mock.patch.object(drac_power, '_get_power_state') + def test_reboot_in_power_off(self, mock_get_power_state, + mock_set_power_state): + mock_get_power_state.return_value = states.POWER_OFF + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.power.reboot(task) + mock_set_power_state.assert_called_once_with(task.node, + states.POWER_ON)
\ No newline at end of file diff --git a/ironic/tests/drivers/elilo_efi_pxe_config.template b/ironic/tests/drivers/elilo_efi_pxe_config.template new file mode 100644 index 000000000..0dca09d8c --- /dev/null +++ b/ironic/tests/drivers/elilo_efi_pxe_config.template @@ -0,0 +1,16 @@ +default=deploy + +image=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel + label=deploy + initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk + append="selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param ip=%I::%G:%M:%H::on root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh boot_option=netboot boot_mode=uefi coreos.configdrive=0" + + +image=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/kernel + label=boot_partition + initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/ramdisk + append="root={{ ROOT }} ro text test_param ip=%I::%G:%M:%H::on" + +image=chain.c32 + label=boot_whole_disk + append="mbr:{{ DISK_IDENTIFIER }}" diff --git a/ironic/tests/drivers/ilo/test_common.py b/ironic/tests/drivers/ilo/test_common.py index fddd57ba7..a66b4b721 100644 --- a/ironic/tests/drivers/ilo/test_common.py +++ b/ironic/tests/drivers/ilo/test_common.py @@ -27,7 +27,6 @@ from ironic.common import swift from ironic.common import utils from ironic.conductor import task_manager from ironic.drivers.modules.ilo import common as ilo_common -from ironic.drivers import utils as driver_utils from ironic.tests.conductor import utils as mgr_utils from ironic.tests.db import base as db_base from ironic.tests.db import utils as db_utils @@ -300,42 +299,39 @@ class IloCommonMethodsTestCase(db_base.DbTestCase): get_pending_boot_mode_mock.assert_called_once_with() @mock.patch.object(ilo_common, 'set_boot_mode') - @mock.patch.object(driver_utils, 'get_node_capability') - def test_update_boot_mode_avbl(self, - node_capability_mock, - set_boot_mode_mock): - node_capability_mock.return_value = 'uefi' + def test_update_boot_mode_instance_info_exists(self, + set_boot_mode_mock): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: + task.node.instance_info['deploy_boot_mode'] = 'bios' ilo_common.update_boot_mode(task) - node_capability_mock.assert_called_once_with(task.node, - 'boot_mode') - set_boot_mode_mock.assert_called_once_with(task.node, 'uefi') + set_boot_mode_mock.assert_called_once_with(task.node, 'bios') + + @mock.patch.object(ilo_common, 'set_boot_mode') + def test_update_boot_mode_capabilities_exist(self, + set_boot_mode_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.properties['capabilities'] = 'boot_mode:bios' + ilo_common.update_boot_mode(task) + set_boot_mode_mock.assert_called_once_with(task.node, 'bios') - @mock.patch.object(driver_utils, 'rm_node_capability') - @mock.patch.object(driver_utils, 'add_node_capability') @mock.patch.object(ilo_common, 'get_ilo_object') - def test_update_boot_mode(self, get_ilo_object_mock, - add_node_capability_mock, - rm_node_capability_mock): + def test_update_boot_mode(self, get_ilo_object_mock): ilo_mock_obj = get_ilo_object_mock.return_value - ilo_mock_obj.get_pending_boot_mode.return_value = 'legacy' + ilo_mock_obj.get_pending_boot_mode.return_value = 'LEGACY' with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: ilo_common.update_boot_mode(task) get_ilo_object_mock.assert_called_once_with(task.node) ilo_mock_obj.get_pending_boot_mode.assert_called_once_with() - rm_node_capability_mock.assert_called_once_with(task, 'boot_mode') - add_node_capability_mock.assert_called_once_with(task, - 'boot_mode', - 'bios') + self.assertEqual('bios', + task.node.instance_info['deploy_boot_mode']) - @mock.patch.object(driver_utils, 'add_node_capability') @mock.patch.object(ilo_common, 'get_ilo_object') def test_update_boot_mode_unknown(self, - get_ilo_object_mock, - add_node_capability_mock): + get_ilo_object_mock): ilo_mock_obj = get_ilo_object_mock.return_value ilo_mock_obj.get_pending_boot_mode.return_value = 'UNKNOWN' set_pending_boot_mode_mock = ilo_mock_obj.set_pending_boot_mode @@ -346,15 +342,28 @@ class IloCommonMethodsTestCase(db_base.DbTestCase): get_ilo_object_mock.assert_called_once_with(task.node) ilo_mock_obj.get_pending_boot_mode.assert_called_once_with() set_pending_boot_mode_mock.assert_called_once_with('UEFI') - add_node_capability_mock.assert_called_once_with(task, - 'boot_mode', - 'uefi') + self.assertEqual('uefi', + task.node.instance_info['deploy_boot_mode']) + + @mock.patch.object(ilo_common, 'get_ilo_object') + def test_update_boot_mode_unknown_except(self, + get_ilo_object_mock): + ilo_mock_obj = get_ilo_object_mock.return_value + ilo_mock_obj.get_pending_boot_mode.return_value = 'UNKNOWN' + set_pending_boot_mode_mock = ilo_mock_obj.set_pending_boot_mode + exc = ilo_error.IloError('error') + set_pending_boot_mode_mock.side_effect = exc + + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.IloOperationError, + ilo_common.update_boot_mode, task) + get_ilo_object_mock.assert_called_once_with(task.node) + ilo_mock_obj.get_pending_boot_mode.assert_called_once_with() - @mock.patch.object(driver_utils, 'add_node_capability') @mock.patch.object(ilo_common, 'get_ilo_object') def test_update_boot_mode_legacy(self, - get_ilo_object_mock, - add_node_capability_mock): + get_ilo_object_mock): ilo_mock_obj = get_ilo_object_mock.return_value exc = ilo_error.IloCommandNotSupportedError('error') ilo_mock_obj.get_pending_boot_mode.side_effect = exc @@ -364,9 +373,8 @@ class IloCommonMethodsTestCase(db_base.DbTestCase): ilo_common.update_boot_mode(task) get_ilo_object_mock.assert_called_once_with(task.node) ilo_mock_obj.get_pending_boot_mode.assert_called_once_with() - add_node_capability_mock.assert_called_once_with(task, - 'boot_mode', - 'bios') + self.assertEqual('bios', + task.node.instance_info['deploy_boot_mode']) @mock.patch.object(ilo_common, 'set_boot_mode') def test_update_boot_mode_prop_boot_mode_exist(self, diff --git a/ironic/tests/drivers/ilo/test_deploy.py b/ironic/tests/drivers/ilo/test_deploy.py index 32ea63734..f44e532b9 100644 --- a/ironic/tests/drivers/ilo/test_deploy.py +++ b/ironic/tests/drivers/ilo/test_deploy.py @@ -124,26 +124,29 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase): boot_iso_expected = 'boot-iso-uuid' self.assertEqual(boot_iso_expected, boot_iso_actual) - @mock.patch.object(driver_utils, 'get_node_capability') + @mock.patch.object(deploy_utils, 'get_boot_mode_for_deploy') @mock.patch.object(images, 'get_image_properties') @mock.patch.object(ilo_deploy, '_parse_deploy_info') - def test__get_boot_iso_uefi_no_glance_image(self, deploy_info_mock, - image_props_mock, get_node_cap_mock): + def test__get_boot_iso_uefi_no_glance_image(self, + deploy_info_mock, + image_props_mock, + boot_mode_mock): deploy_info_mock.return_value = {'image_source': 'image-uuid', 'ilo_deploy_iso': 'deploy_iso_uuid'} image_props_mock.return_value = {'boot_iso': None, 'kernel_id': None, 'ramdisk_id': None} - get_node_cap_mock.return_value = 'uefi' + properties = {'capabilities': 'boot_mode:uefi'} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: + task.node.properties = properties boot_iso_result = ilo_deploy._get_boot_iso(task, 'root-uuid') deploy_info_mock.assert_called_once_with(task.node) image_props_mock.assert_called_once_with( task.context, 'image-uuid', ['boot_iso', 'kernel_id', 'ramdisk_id']) - get_node_cap_mock.assert_not_called(task.node, 'boot_mode') + self.assertFalse(boot_mode_mock.called) self.assertIsNone(boot_iso_result) @mock.patch.object(tempfile, 'NamedTemporaryFile') @@ -250,8 +253,7 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase): ilo_deploy._reboot_into(task, 'iso', opts) setup_vmedia_mock.assert_called_once_with(task, 'iso', opts) set_boot_device_mock.assert_called_once_with(task, - boot_devices.CDROM, - persistent=True) + boot_devices.CDROM) node_power_action_mock.assert_called_once_with(task, states.REBOOT) @mock.patch.object(ilo_deploy, '_reboot_into') @@ -271,39 +273,26 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase): @mock.patch.object(deploy_utils, 'is_secure_boot_requested') @mock.patch.object(ilo_common, 'set_secure_boot_mode') - def test__update_secure_boot_passed_true(self, - func_set_secure_boot_mode, - func_is_secure_boot_requested): + def test__update_secure_boot_mode_passed_true(self, + func_set_secure_boot_mode, + func_is_secure_boot_req): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - func_is_secure_boot_requested.return_value = True + func_is_secure_boot_req.return_value = True ilo_deploy._update_secure_boot_mode(task, True) func_set_secure_boot_mode.assert_called_once_with(task, True) @mock.patch.object(deploy_utils, 'is_secure_boot_requested') @mock.patch.object(ilo_common, 'set_secure_boot_mode') - def test__update_secure_boot_passed_False(self, - func_set_secure_boot_mode, - func_is_secure_boot_requested): + def test__update_secure_boot_mode_passed_False(self, + func_set_secure_boot_mode, + func_is_secure_boot_req): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - func_is_secure_boot_requested.return_value = False + func_is_secure_boot_req.return_value = False ilo_deploy._update_secure_boot_mode(task, False) self.assertFalse(func_set_secure_boot_mode.called) - @mock.patch.object(driver_utils, 'add_node_capability') - @mock.patch.object(driver_utils, 'rm_node_capability') - def test__enable_uefi_capability(self, func_rm_node_capability, - func_add_node_capability): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - ilo_deploy._enable_uefi_capability(task) - func_rm_node_capability.assert_called_once_with(task, - 'boot_mode') - func_add_node_capability.assert_called_once_with(task, - 'boot_mode', - 'uefi') - @mock.patch.object(ilo_common, 'set_secure_boot_mode') @mock.patch.object(ilo_common, 'get_secure_boot_mode') def test__disable_secure_boot_false(self, @@ -330,81 +319,103 @@ class IloDeployPrivateMethodsTestCase(db_base.DbTestCase): func_set_secure_boot_mode.assert_called_once_with(task, False) self.assertTrue(returned_state) + @mock.patch.object(ilo_deploy.LOG, 'debug') @mock.patch.object(ilo_deploy, 'exception') @mock.patch.object(ilo_common, 'get_secure_boot_mode') def test__disable_secure_boot_exception(self, func_get_secure_boot_mode, - exception_mock): + exception_mock, + mock_log): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: exception_mock.IloOperationNotSupported = Exception func_get_secure_boot_mode.side_effect = Exception returned_state = ilo_deploy._disable_secure_boot(task) func_get_secure_boot_mode.assert_called_once_with(task) + self.assertTrue(mock_log.called) self.assertFalse(returned_state) @mock.patch.object(ilo_common, 'update_boot_mode') - @mock.patch.object(deploy_utils, 'is_secure_boot_requested') @mock.patch.object(ilo_deploy, '_disable_secure_boot') @mock.patch.object(manager_utils, 'node_power_action') def test__prepare_node_for_deploy(self, func_node_power_action, func_disable_secure_boot, - func_is_secure_boot_requested, func_update_boot_mode): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: func_disable_secure_boot.return_value = False - func_is_secure_boot_requested.return_value = False ilo_deploy._prepare_node_for_deploy(task) func_node_power_action.assert_called_once_with(task, states.POWER_OFF) func_disable_secure_boot.assert_called_once_with(task) - func_is_secure_boot_requested.assert_called_once_with(task.node) func_update_boot_mode.assert_called_once_with(task) + bootmode = driver_utils.get_node_capability(task.node, "boot_mode") + self.assertIsNone(bootmode) @mock.patch.object(ilo_common, 'update_boot_mode') - @mock.patch.object(deploy_utils, 'is_secure_boot_requested') @mock.patch.object(ilo_deploy, '_disable_secure_boot') @mock.patch.object(manager_utils, 'node_power_action') def test__prepare_node_for_deploy_sec_boot_on(self, func_node_power_action, func_disable_secure_boot, - func_is_secure_boot_req, func_update_boot_mode): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: func_disable_secure_boot.return_value = True - func_is_secure_boot_req.return_value = False ilo_deploy._prepare_node_for_deploy(task) func_node_power_action.assert_called_once_with(task, states.POWER_OFF) func_disable_secure_boot.assert_called_once_with(task) - func_is_secure_boot_req.assert_called_once_with(task.node) self.assertFalse(func_update_boot_mode.called) + ret_boot_mode = task.node.instance_info['deploy_boot_mode'] + self.assertEqual('uefi', ret_boot_mode) + bootmode = driver_utils.get_node_capability(task.node, "boot_mode") + self.assertIsNone(bootmode) @mock.patch.object(ilo_common, 'update_boot_mode') - @mock.patch.object(ilo_deploy, '_enable_uefi_capability') - @mock.patch.object(deploy_utils, 'is_secure_boot_requested') @mock.patch.object(ilo_deploy, '_disable_secure_boot') @mock.patch.object(manager_utils, 'node_power_action') - def test__prepare_node_for_deploy_sec_boot_req(self, - func_node_power_action, - func_disable_secure_boot, - func_is_secure_boot_req, - func_enable_uefi_cap, - func_update_boot_mode): + def test__prepare_node_for_deploy_inst_info(self, + func_node_power_action, + func_disable_secure_boot, + func_update_boot_mode): + instance_info = {'capabilities': '{"secure_boot": "true"}'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + func_disable_secure_boot.return_value = False + task.node.instance_info = instance_info + ilo_deploy._prepare_node_for_deploy(task) + func_node_power_action.assert_called_once_with(task, + states.POWER_OFF) + func_disable_secure_boot.assert_called_once_with(task) + func_update_boot_mode.assert_called_once_with(task) + bootmode = driver_utils.get_node_capability(task.node, "boot_mode") + self.assertIsNone(bootmode) + deploy_boot_mode = task.node.instance_info.get('deploy_boot_mode') + self.assertIsNone(deploy_boot_mode) + + @mock.patch.object(ilo_common, 'update_boot_mode') + @mock.patch.object(ilo_deploy, '_disable_secure_boot') + @mock.patch.object(manager_utils, 'node_power_action') + def test__prepare_node_for_deploy_sec_boot_on_inst_info(self, + func_node_power_action, + func_disable_secure_boot, + func_update_boot_mode): + instance_info = {'capabilities': '{"secure_boot": "true"}'} with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: func_disable_secure_boot.return_value = True - func_is_secure_boot_req.return_value = True + task.node.instance_info = instance_info ilo_deploy._prepare_node_for_deploy(task) func_node_power_action.assert_called_once_with(task, states.POWER_OFF) func_disable_secure_boot.assert_called_once_with(task) - func_is_secure_boot_req.assert_called_once_with(task.node) - func_enable_uefi_cap.assert_called_once_with(task) self.assertFalse(func_update_boot_mode.called) + bootmode = driver_utils.get_node_capability(task.node, "boot_mode") + self.assertIsNone(bootmode) + deploy_boot_mode = task.node.instance_info.get('deploy_boot_mode') + self.assertIsNone(deploy_boot_mode) class IloVirtualMediaIscsiDeployTestCase(db_base.DbTestCase): @@ -739,6 +750,18 @@ class VendorPassthruTestCase(db_base.DbTestCase): get_deploy_info_mock.assert_called_once_with(task.node, foo='bar') + @mock.patch.object(iscsi_deploy, 'validate_pass_bootloader_info_input', + autospec=True) + def test_validate_pass_bootloader_install_info(self, + validate_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + kwargs = {'address': '1.2.3.4', 'key': 'fake-key', + 'status': 'SUCCEEDED', 'error': ''} + task.driver.vendor.validate( + task, method='pass_bootloader_install_info', **kwargs) + validate_mock.assert_called_once_with(task, kwargs) + @mock.patch.object(iscsi_deploy, 'get_deploy_info') def test_validate_heartbeat(self, get_deploy_info_mock): with task_manager.acquire(self.context, self.node.uuid, @@ -747,7 +770,22 @@ class VendorPassthruTestCase(db_base.DbTestCase): vendor.validate(task, method='heartbeat', foo='bar') self.assertFalse(get_deploy_info_mock.called) - @mock.patch.object(deploy_utils, 'notify_deploy_complete') + @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status', + autospec=True) + @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True) + def test_pass_bootloader_install_info(self, finish_deploy_mock, + validate_input_mock): + kwargs = {'method': 'pass_deploy_info', 'address': '123456'} + self.node.provision_state = states.DEPLOYWAIT + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.vendor.pass_bootloader_install_info(task, **kwargs) + finish_deploy_mock.assert_called_once_with(task, '123456') + validate_input_mock.assert_called_once_with(task, kwargs) + + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed') @mock.patch.object(ilo_deploy, '_update_secure_boot_mode') @mock.patch.object(ilo_common, 'update_boot_mode') @mock.patch.object(manager_utils, 'node_set_boot_device') @@ -760,7 +798,7 @@ class VendorPassthruTestCase(db_base.DbTestCase): setup_vmedia_mock, set_boot_device_mock, func_update_boot_mode, func_update_secure_boot_mode, - notify_deploy_complete_mock): + notify_ramdisk_to_proceed_mock): kwargs = {'method': 'pass_deploy_info', 'address': '123456'} continue_deploy_mock.return_value = {'root uuid': 'root-uuid'} get_boot_iso_mock.return_value = 'boot-iso' @@ -786,7 +824,7 @@ class VendorPassthruTestCase(db_base.DbTestCase): self.assertEqual('boot-iso', task.node.instance_info['ilo_boot_iso']) - notify_deploy_complete_mock.assert_called_once_with('123456') + notify_ramdisk_to_proceed_mock.assert_called_once_with('123456') @mock.patch.object(ilo_common, 'cleanup_vmedia_boot') def test_pass_deploy_info_bad(self, cleanup_vmedia_boot_mock): @@ -805,16 +843,19 @@ class VendorPassthruTestCase(db_base.DbTestCase): self.assertEqual(states.NOSTATE, task.node.target_provision_state) self.assertFalse(cleanup_vmedia_boot_mock.called) + @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', autospec=True) + @mock.patch.object(ilo_common, 'update_boot_mode', autospec=True) @mock.patch.object(manager_utils, 'node_power_action') @mock.patch.object(iscsi_deploy, 'continue_deploy') @mock.patch.object(ilo_common, 'cleanup_vmedia_boot') @mock.patch.object(ilo_deploy, '_get_boot_iso') def test_pass_deploy_info_create_boot_iso_fail(self, get_iso_mock, - cleanup_vmedia_boot_mock, continue_deploy_mock, node_power_mock): + cleanup_vmedia_boot_mock, continue_deploy_mock, node_power_mock, + update_boot_mode_mock, update_secure_boot_mode_mock): kwargs = {'address': '123456'} continue_deploy_mock.return_value = {'root uuid': 'root-uuid'} get_iso_mock.side_effect = exception.ImageCreationFailed( - image_type='iso', error="error") + image_type='iso', error="error") self.node.provision_state = states.DEPLOYWAIT self.node.target_provision_state = states.ACTIVE self.node.save() @@ -824,6 +865,8 @@ class VendorPassthruTestCase(db_base.DbTestCase): task.driver.vendor.pass_deploy_info(task, **kwargs) cleanup_vmedia_boot_mock.assert_called_once_with(task) + update_boot_mode_mock.assert_called_once_with(task) + update_secure_boot_mode_mock.assert_called_once_with(task, True) continue_deploy_mock.assert_called_once_with(task, **kwargs) get_iso_mock.assert_called_once_with(task, 'root-uuid') node_power_mock.assert_called_once_with(task, states.POWER_OFF) @@ -831,22 +874,57 @@ class VendorPassthruTestCase(db_base.DbTestCase): self.assertEqual(states.ACTIVE, task.node.target_provision_state) self.assertIsNotNone(task.node.last_error) - @mock.patch.object(deploy_utils, 'notify_deploy_complete') + @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True) + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed', + autospec=True) @mock.patch.object(manager_utils, 'node_set_boot_device') @mock.patch.object(ilo_deploy, '_update_secure_boot_mode') @mock.patch.object(ilo_common, 'update_boot_mode') @mock.patch.object(iscsi_deploy, 'continue_deploy') @mock.patch.object(ilo_common, 'cleanup_vmedia_boot') - def _test_pass_deploy_info_localboot(self, cleanup_vmedia_boot_mock, - continue_deploy_mock, - func_update_boot_mode, - func_update_secure_boot_mode, - set_boot_device_mock, - notify_deploy_complete_mock): + def test_pass_deploy_info_boot_option_local( + self, cleanup_vmedia_boot_mock, continue_deploy_mock, + func_update_boot_mode, func_update_secure_boot_mode, + set_boot_device_mock, notify_ramdisk_to_proceed_mock, + finish_deploy_mock): + kwargs = {'method': 'pass_deploy_info', 'address': '123456'} + continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'} + + self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} + self.node.provision_state = states.DEPLOYWAIT + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + vendor = task.driver.vendor + vendor.pass_deploy_info(task, **kwargs) + cleanup_vmedia_boot_mock.assert_called_once_with(task) + continue_deploy_mock.assert_called_once_with(task, **kwargs) + set_boot_device_mock.assert_called_once_with(task, + boot_devices.DISK, + persistent=True) + func_update_boot_mode.assert_called_once_with(task) + func_update_secure_boot_mode.assert_called_once_with(task, True) + notify_ramdisk_to_proceed_mock.assert_called_once_with('123456') + self.assertEqual(states.DEPLOYWAIT, task.node.provision_state) + self.assertEqual(states.ACTIVE, task.node.target_provision_state) + self.assertFalse(finish_deploy_mock.called) + + @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True) + @mock.patch.object(manager_utils, 'node_set_boot_device', autospec=True) + @mock.patch.object(ilo_deploy, '_update_secure_boot_mode', autospec=True) + @mock.patch.object(ilo_common, 'update_boot_mode', autospec=True) + @mock.patch.object(iscsi_deploy, 'continue_deploy', autospec=True) + @mock.patch.object(ilo_common, 'cleanup_vmedia_boot', autospec=True) + def _test_pass_deploy_info_whole_disk_image( + self, cleanup_vmedia_boot_mock, continue_deploy_mock, + func_update_boot_mode, func_update_secure_boot_mode, + set_boot_device_mock, notify_ramdisk_to_proceed_mock): kwargs = {'method': 'pass_deploy_info', 'address': '123456'} continue_deploy_mock.return_value = {'root uuid': '<some-uuid>'} + self.node.driver_internal_info = {'is_whole_disk_image': True} self.node.provision_state = states.DEPLOYWAIT self.node.target_provision_state = states.ACTIVE self.node.save() @@ -862,19 +940,15 @@ class VendorPassthruTestCase(db_base.DbTestCase): persistent=True) func_update_boot_mode.assert_called_once_with(task) func_update_secure_boot_mode.assert_called_once_with(task, True) - notify_deploy_complete_mock.assert_called_once_with('123456') - self.assertEqual(states.ACTIVE, task.node.provision_state) - self.assertEqual(states.NOSTATE, task.node.target_provision_state) + iscsi_deploy.finish_deploy.assert_called_once_with(task, '123456') - def test_pass_deploy_info_boot_option_local(self): + def test_pass_deploy_info_whole_disk_image_local(self): self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} self.node.save() - self._test_pass_deploy_info_localboot() + self._test_pass_deploy_info_whole_disk_image() def test_pass_deploy_info_whole_disk_image(self): - self.node.driver_internal_info = {'is_whole_disk_image': True} - self.node.save() - self._test_pass_deploy_info_localboot() + self._test_pass_deploy_info_whole_disk_image() @mock.patch.object(ilo_deploy, '_update_secure_boot_mode') @mock.patch.object(ilo_common, 'update_boot_mode') @@ -953,6 +1027,34 @@ class VendorPassthruTestCase(db_base.DbTestCase): 'configure_local_boot') @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy') @mock.patch.object(ilo_common, 'cleanup_vmedia_boot') + def test_continue_deploy_whole_disk_image( + self, cleanup_vmedia_boot_mock, do_agent_iscsi_deploy_mock, + configure_local_boot_mock, reboot_and_finish_deploy_mock, + boot_mode_cap_mock, update_secure_boot_mock): + self.node.provision_state = states.DEPLOYWAIT + self.node.target_provision_state = states.DEPLOYING + self.node.driver_internal_info = {'is_whole_disk_image': True} + self.node.save() + do_agent_iscsi_deploy_mock.return_value = { + 'disk identifier': 'some-disk-id'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.vendor.continue_deploy(task) + cleanup_vmedia_boot_mock.assert_called_once_with(task) + do_agent_iscsi_deploy_mock.assert_called_once_with(task, + mock.ANY) + configure_local_boot_mock.assert_called_once_with( + task, root_uuid=None, efi_system_part_uuid=None) + reboot_and_finish_deploy_mock.assert_called_once_with(task) + + @mock.patch.object(ilo_deploy, '_update_secure_boot_mode') + @mock.patch.object(ilo_common, 'update_boot_mode') + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + 'reboot_and_finish_deploy') + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + 'configure_local_boot') + @mock.patch.object(iscsi_deploy, 'do_agent_iscsi_deploy') + @mock.patch.object(ilo_common, 'cleanup_vmedia_boot') def test_continue_deploy_localboot_uefi(self, cleanup_vmedia_boot_mock, do_agent_iscsi_deploy_mock, configure_local_boot_mock, @@ -1003,10 +1105,25 @@ class IloPXEDeployTestCase(db_base.DbTestCase): pxe_prepare_mock): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: + task.node.properties['capabilities'] = 'boot_mode:uefi' task.driver.deploy.prepare(task) update_boot_mode_mock.assert_called_once_with(task) pxe_prepare_mock.assert_called_once_with(task) + @mock.patch.object(pxe.PXEDeploy, 'prepare') + @mock.patch.object(ilo_common, 'update_boot_mode') + def test_prepare_uefi_whole_disk_image_fail(self, + update_boot_mode_mock, + pxe_prepare_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.properties['capabilities'] = 'boot_mode:uefi' + task.node.driver_internal_info['is_whole_disk_image'] = True + self.assertRaises(exception.InvalidParameterValue, + task.driver.deploy.prepare, task) + update_boot_mode_mock.assert_called_once_with(task) + self.assertFalse(pxe_prepare_mock.called) + @mock.patch.object(pxe.PXEDeploy, 'deploy') @mock.patch.object(manager_utils, 'node_set_boot_device') def test_deploy_boot_mode_exists(self, set_persistent_mock, @@ -1027,7 +1144,8 @@ class IloPXEVendorPassthruTestCase(db_base.DbTestCase): driver='pxe_ilo', driver_info=INFO_DICT) def test_vendor_routes(self): - expected = ['heartbeat', 'pass_deploy_info'] + expected = ['heartbeat', 'pass_deploy_info', + 'pass_bootloader_install_info'] with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: vendor_routes = task.driver.vendor.vendor_routes @@ -1067,8 +1185,10 @@ class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase): @mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance') @mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success') + @mock.patch.object(ilo_common, 'update_boot_mode') @mock.patch.object(ilo_deploy, '_update_secure_boot_mode') def test_reboot_to_instance(self, func_update_secure_boot_mode, + func_update_boot_mode, check_deploy_success_mock, agent_reboot_to_instance_mock): kwargs = {'address': '123456'} @@ -1077,14 +1197,17 @@ class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase): shared=False) as task: task.driver.vendor.reboot_to_instance(task, **kwargs) check_deploy_success_mock.called_once_with(task.node) + func_update_boot_mode.assert_called_once_with(task) func_update_secure_boot_mode.assert_called_once_with(task, True) agent_reboot_to_instance_mock.assert_called_once_with(task, **kwargs) @mock.patch.object(agent.AgentVendorInterface, 'reboot_to_instance') @mock.patch.object(agent.AgentVendorInterface, 'check_deploy_success') + @mock.patch.object(ilo_common, 'update_boot_mode') @mock.patch.object(ilo_deploy, '_update_secure_boot_mode') def test_reboot_to_instance_deploy_fail(self, func_update_secure_boot_mode, + func_update_boot_mode, check_deploy_success_mock, agent_reboot_to_instance_mock): kwargs = {'address': '123456'} @@ -1093,6 +1216,7 @@ class IloVirtualMediaAgentVendorInterfaceTestCase(db_base.DbTestCase): shared=False) as task: task.driver.vendor.reboot_to_instance(task, **kwargs) check_deploy_success_mock.called_once_with(task.node) + self.assertFalse(func_update_boot_mode.called) self.assertFalse(func_update_secure_boot_mode.called) agent_reboot_to_instance_mock.assert_called_once_with(task, **kwargs) diff --git a/ironic/tests/drivers/ilo/test_inspect.py b/ironic/tests/drivers/ilo/test_inspect.py index 129f52b0f..9b7461a7b 100644 --- a/ironic/tests/drivers/ilo/test_inspect.py +++ b/ironic/tests/drivers/ilo/test_inspect.py @@ -48,78 +48,30 @@ class IloInspectTestCase(db_base.DbTestCase): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: properties = ilo_common.REQUIRED_PROPERTIES.copy() - properties.update(ilo_common.INSPECT_PROPERTIES) self.assertEqual(properties, task.driver.inspect.get_properties()) @mock.patch.object(ilo_common, 'parse_driver_info') - def test_validate_inspect_ports_valid_with_comma(self, driver_info_mock): + def test_validate(self, driver_info_mock): with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - driver_info_mock.return_value = {'inspect_ports': '1,2'} task.driver.inspect.validate(task) driver_info_mock.assert_called_once_with(task.node) - @mock.patch.object(ilo_common, 'parse_driver_info') - def test_validate_inspect_ports_valid_None(self, driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - driver_info_mock.return_value = {'inspect_ports': 'None'} - task.driver.inspect.validate(task) - driver_info_mock.assert_called_once_with(task.node) - - @mock.patch.object(ilo_common, 'parse_driver_info') - def test_validate_inspect_ports_valid_all(self, driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - driver_info_mock.return_value = {'inspect_ports': 'all'} - task.driver.inspect.validate(task) - driver_info_mock.assert_called_once_with(task.node) - - @mock.patch.object(ilo_common, 'parse_driver_info') - def test_validate_inspect_ports_valid_single(self, driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - driver_info_mock.return_value = {'inspect_ports': '1'} - task.driver.inspect.validate(task) - driver_info_mock.assert_called_once_with(task.node) - - @mock.patch.object(ilo_common, 'parse_driver_info') - def test_validate_inspect_ports_invalid(self, driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - driver_info_mock.return_value = {'inspect_ports': 'abc'} - self.assertRaises(exception.InvalidParameterValue, - task.driver.inspect.validate, task) - driver_info_mock.assert_called_once_with(task.node) - - @mock.patch.object(ilo_common, 'parse_driver_info') - def test_validate_inspect_ports_missing(self, driver_info_mock): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - driver_info_mock.return_value = {'xyz': 'abc'} - self.assertRaises(exception.MissingParameterValue, - task.driver.inspect.validate, task) - driver_info_mock.assert_called_once_with(task.node) - @mock.patch.object(ilo_inspect, '_get_capabilities') @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist') - @mock.patch.object(ilo_inspect, '_get_macs_for_desired_ports') @mock.patch.object(ilo_inspect, '_get_essential_properties') @mock.patch.object(ilo_power.IloPower, 'get_power_state') @mock.patch.object(ilo_common, 'get_ilo_object') def test_inspect_essential_ok(self, get_ilo_object_mock, power_mock, get_essential_mock, - desired_macs_mock, create_port_mock, get_capabilities_mock): ilo_object_mock = get_ilo_object_mock.return_value properties = {'memory_mb': '512', 'local_gb': '10', 'cpus': '1', 'cpu_arch': 'x86_64'} macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - desired_macs_mock.return_value = {'Port 1': 'aa:aa:aa:aa:aa:aa', - 'Port 2': 'bb:bb:bb:bb:bb:bb'} capabilities = '' result = {'properties': properties, 'macs': macs} get_essential_mock.return_value = result @@ -127,7 +79,6 @@ class IloInspectTestCase(db_base.DbTestCase): power_mock.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - task.node.driver_info = {'inspect_ports': 'all'} task.driver.inspect.inspect_hardware(task) self.assertEqual(properties, task.node.properties) power_mock.assert_called_once_with(task) @@ -139,7 +90,6 @@ class IloInspectTestCase(db_base.DbTestCase): @mock.patch.object(ilo_inspect, '_get_capabilities') @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist') - @mock.patch.object(ilo_inspect, '_get_macs_for_desired_ports') @mock.patch.object(ilo_inspect, '_get_essential_properties') @mock.patch.object(conductor_utils, 'node_power_action') @mock.patch.object(ilo_power.IloPower, 'get_power_state') @@ -148,15 +98,12 @@ class IloInspectTestCase(db_base.DbTestCase): power_mock, set_power_mock, get_essential_mock, - desired_macs_mock, create_port_mock, get_capabilities_mock): ilo_object_mock = get_ilo_object_mock.return_value properties = {'memory_mb': '512', 'local_gb': '10', 'cpus': '1', 'cpu_arch': 'x86_64'} macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - desired_macs_mock.return_value = {'Port 1': 'aa:aa:aa:aa:aa:aa', - 'Port 2': 'bb:bb:bb:bb:bb:bb'} capabilities = '' result = {'properties': properties, 'macs': macs} get_essential_mock.return_value = result @@ -164,7 +111,6 @@ class IloInspectTestCase(db_base.DbTestCase): power_mock.return_value = states.POWER_OFF with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - task.node.driver_info = {'inspect_ports': 'all'} task.driver.inspect.inspect_hardware(task) self.assertEqual(properties, task.node.properties) power_mock.assert_called_once_with(task) @@ -177,22 +123,18 @@ class IloInspectTestCase(db_base.DbTestCase): @mock.patch.object(ilo_inspect, '_get_capabilities') @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist') - @mock.patch.object(ilo_inspect, '_get_macs_for_desired_ports') @mock.patch.object(ilo_inspect, '_get_essential_properties') @mock.patch.object(ilo_power.IloPower, 'get_power_state') @mock.patch.object(ilo_common, 'get_ilo_object') def test_inspect_essential_capabilities_ok(self, get_ilo_object_mock, power_mock, get_essential_mock, - desired_macs_mock, create_port_mock, get_capabilities_mock): ilo_object_mock = get_ilo_object_mock.return_value properties = {'memory_mb': '512', 'local_gb': '10', 'cpus': '1', 'cpu_arch': 'x86_64'} macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - desired_macs_mock.return_value = {'Port 1': 'aa:aa:aa:aa:aa:aa', - 'Port 2': 'bb:bb:bb:bb:bb:bb'} capability_str = 'BootMode:uefi' capabilities = {'BootMode': 'uefi'} result = {'properties': properties, 'macs': macs} @@ -201,7 +143,6 @@ class IloInspectTestCase(db_base.DbTestCase): power_mock.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - task.node.driver_info = {'inspect_ports': 'all'} task.driver.inspect.inspect_hardware(task) expected_properties = {'memory_mb': '512', 'local_gb': '10', 'cpus': '1', 'cpu_arch': 'x86_64', @@ -216,14 +157,12 @@ class IloInspectTestCase(db_base.DbTestCase): @mock.patch.object(ilo_inspect, '_get_capabilities') @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist') - @mock.patch.object(ilo_inspect, '_get_macs_for_desired_ports') @mock.patch.object(ilo_inspect, '_get_essential_properties') @mock.patch.object(ilo_power.IloPower, 'get_power_state') @mock.patch.object(ilo_common, 'get_ilo_object') def test_inspect_essential_capabilities_exist_ok(self, get_ilo_object_mock, power_mock, get_essential_mock, - desired_macs_mock, create_port_mock, get_capabilities_mock): ilo_object_mock = get_ilo_object_mock.return_value @@ -231,8 +170,6 @@ class IloInspectTestCase(db_base.DbTestCase): 'cpus': '1', 'cpu_arch': 'x86_64', 'somekey': 'somevalue'} macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - desired_macs_mock.return_value = {'Port 1': 'aa:aa:aa:aa:aa:aa', - 'Port 2': 'bb:bb:bb:bb:bb:bb'} result = {'properties': properties, 'macs': macs} capabilities = {'BootMode': 'uefi'} get_essential_mock.return_value = result @@ -240,7 +177,6 @@ class IloInspectTestCase(db_base.DbTestCase): power_mock.return_value = states.POWER_ON with task_manager.acquire(self.context, self.node.uuid, shared=False) as task: - task.node.driver_info = {'inspect_ports': 'all'} task.node.properties = {'capabilities': 'foo:bar'} expected_capabilities = ('BootMode:uefi,' 'foo:bar') @@ -260,75 +196,6 @@ class IloInspectTestCase(db_base.DbTestCase): ilo_object_mock) create_port_mock.assert_called_once_with(task.node, macs) - @mock.patch.object(ilo_inspect, '_get_capabilities') - @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist') - @mock.patch.object(ilo_inspect, '_get_macs_for_desired_ports') - @mock.patch.object(ilo_inspect, '_get_essential_properties') - @mock.patch.object(ilo_power.IloPower, 'get_power_state') - @mock.patch.object(ilo_common, 'get_ilo_object') - def test_inspect_hardware_port_desired(self, get_ilo_object_mock, - power_mock, - get_essential_mock, - desired_macs_mock, - create_port_mock, - get_capabilities_mock): - ilo_object_mock = get_ilo_object_mock.return_value - properties = {'memory_mb': '512', 'local_gb': '10', - 'cpus': '1', 'cpu_arch': 'x86_64'} - macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - result = {'properties': properties, 'macs': macs} - macs_input_given = {'Port 1': 'aa:aa:aa:aa:aa:aa'} - desired_macs_mock.return_value = macs_input_given - capabilities = '' - get_essential_mock.return_value = result - get_capabilities_mock.return_value = capabilities - power_mock.return_value = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_info = {'inspect_ports': '1'} - task.driver.inspect.inspect_hardware(task) - power_mock.assert_called_once_with(task) - get_essential_mock.assert_called_once_with(task.node, - ilo_object_mock) - self.assertEqual(task.node.properties, result['properties']) - get_capabilities_mock.assert_called_once_with(task.node, - ilo_object_mock) - create_port_mock.assert_called_once_with(task.node, - macs_input_given) - - @mock.patch.object(ilo_inspect, '_get_capabilities') - @mock.patch.object(ilo_inspect, '_create_ports_if_not_exist') - @mock.patch.object(ilo_inspect, '_get_macs_for_desired_ports') - @mock.patch.object(ilo_inspect, '_get_essential_properties') - @mock.patch.object(ilo_power.IloPower, 'get_power_state') - @mock.patch.object(ilo_common, 'get_ilo_object') - def test_inspect_hardware_port_desired_none(self, get_ilo_object_mock, - power_mock, - get_essential_mock, - desired_macs_mock, - create_port_mock, - get_capabilities_mock): - ilo_object_mock = get_ilo_object_mock.return_value - properties = {'memory_mb': '512', 'local_gb': '10', - 'cpus': '1', 'cpu_arch': 'x86_64'} - macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - result = {'properties': properties, 'macs': macs} - macs_input_given = {'Port 1': 'aa:aa:aa:aa:aa:aa'} - capabilities = '' - get_capabilities_mock.return_value = capabilities - desired_macs_mock.return_value = macs_input_given - get_essential_mock.return_value = result - power_mock.return_value = states.POWER_ON - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.driver_info = {'inspect_ports': 'none'} - task.driver.inspect.inspect_hardware(task) - power_mock.assert_called_once_with(task) - get_essential_mock.assert_called_once_with(task.node, - ilo_object_mock) - self.assertEqual(task.node.properties, result['properties']) - create_port_mock.assert_not_called() - class TestInspectPrivateMethods(db_base.DbTestCase): @@ -347,7 +214,7 @@ class TestInspectPrivateMethods(db_base.DbTestCase): port_dict1 = {'address': 'aa:aa:aa:aa:aa:aa', 'node_id': node_id} port_dict2 = {'address': 'bb:bb:bb:bb:bb:bb', 'node_id': node_id} ilo_inspect._create_ports_if_not_exist(self.node, macs) - instance_mock.assert_called_once() + instance_mock.assert_called_once_with() self.assertTrue(log_mock.called) db_obj.create_port.assert_any_call(port_dict1) db_obj.create_port.assert_any_call(port_dict2) @@ -361,7 +228,7 @@ class TestInspectPrivateMethods(db_base.DbTestCase): dbapi_mock.create_port.side_effect = exception.MACAlreadyExists('f') macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} ilo_inspect._create_ports_if_not_exist(self.node, macs) - instance_mock.assert_called_once() + instance_mock.assert_called_once_with() self.assertTrue(log_mock.called) def test__get_essential_properties_ok(self): @@ -523,44 +390,3 @@ class TestInspectPrivateMethods(db_base.DbTestCase): set2 = set(cap_returned.split(',')) self.assertEqual(set1, set2) self.assertIsInstance(cap_returned, str) - - def test__get_macs_for_desired_ports(self): - driver_info_mock = {'inspect_ports': '1,2'} - self.node.driver_info = driver_info_mock - macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - expected_macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', - 'Port 2': 'bb:bb:bb:bb:bb:bb'} - macs_out = ( - ilo_inspect._get_macs_for_desired_ports(self.node, - macs)) - self.assertEqual(expected_macs, macs_out) - - def test__get_macs_for_desired_ports_few(self): - driver_info_mock = {'inspect_ports': '1,2'} - self.node.driver_info = driver_info_mock - macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb', - 'Port 3': 'cc:cc:cc:cc:cc:cc', 'Port 4': 'dd:dd:dd:dd:dd:dd'} - expected_macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', - 'Port 2': 'bb:bb:bb:bb:bb:bb'} - macs_out = ( - ilo_inspect._get_macs_for_desired_ports(self.node, - macs)) - self.assertEqual(expected_macs, macs_out) - - def test__get_macs_for_desired_ports_one(self): - driver_info_mock = {'inspect_ports': '1'} - self.node.driver_info = driver_info_mock - macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - expected_macs = {'Port 1': 'aa:aa:aa:aa:aa:aa'} - macs_out = ( - ilo_inspect._get_macs_for_desired_ports(self.node, - macs)) - self.assertEqual(expected_macs, macs_out) - - def test__get_macs_for_desired_ports_none(self): - driver_info_mock = {} - self.node.driver_info = driver_info_mock - macs = {'Port 1': 'aa:aa:aa:aa:aa:aa', 'Port 2': 'bb:bb:bb:bb:bb:bb'} - self.assertRaises(exception.HardwareInspectionFailure, - ilo_inspect._get_macs_for_desired_ports, - self.node, macs) diff --git a/ironic/tests/drivers/ilo/test_power.py b/ironic/tests/drivers/ilo/test_power.py index 2ed8d97fb..a7f87f450 100644 --- a/ironic/tests/drivers/ilo/test_power.py +++ b/ironic/tests/drivers/ilo/test_power.py @@ -137,12 +137,26 @@ class IloPowerInternalMethodsTestCase(db_base.DbTestCase): get_ilo_object_mock): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: + task.node.provision_state = states.ACTIVE task.node.instance_info['ilo_boot_iso'] = 'boot-iso' ilo_power._attach_boot_iso(task) setup_vmedia_mock.assert_called_once_with(task, 'boot-iso') set_boot_device_mock.assert_called_once_with(task, boot_devices.CDROM) + @mock.patch.object(manager_utils, 'node_set_boot_device') + @mock.patch.object(ilo_common, 'setup_vmedia_for_boot') + def test__attach_boot_iso_on_rebuild(self, setup_vmedia_mock, + set_boot_device_mock, + get_ilo_object_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.provision_state = states.DEPLOYING + task.node.instance_info['ilo_boot_iso'] = 'boot-iso' + ilo_power._attach_boot_iso(task) + self.assertFalse(setup_vmedia_mock.called) + self.assertFalse(set_boot_device_mock.called) + class IloPowerTestCase(db_base.DbTestCase): diff --git a/ironic/tests/drivers/ipxe_config.template b/ironic/tests/drivers/ipxe_config.template new file mode 100644 index 000000000..bc803d4a7 --- /dev/null +++ b/ironic/tests/drivers/ipxe_config.template @@ -0,0 +1,21 @@ +#!ipxe + +dhcp + +goto deploy + +:deploy +kernel http://1.2.3.4:1234/deploy_kernel selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot ip=${ip}:${next-server}:${gateway}:${netmask} BOOTIF=${mac} root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh coreos.configdrive=0 + +initrd http://1.2.3.4:1234/deploy_ramdisk +boot + +:boot_partition +kernel http://1.2.3.4:1234/kernel root={{ ROOT }} ro text test_param +initrd http://1.2.3.4:1234/ramdisk +boot + +:boot_whole_disk +kernel chain.c32 +append mbr:{{ DISK_IDENTIFIER }} +boot diff --git a/ironic/tests/drivers/pxe_config.template b/ironic/tests/drivers/pxe_config.template index d77f17e6c..936a9620e 100644 --- a/ironic/tests/drivers/pxe_config.template +++ b/ironic/tests/drivers/pxe_config.template @@ -2,7 +2,7 @@ default deploy label deploy kernel /tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_kernel -append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh boot_mode=bios +append initrd=/tftpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/deploy_ramdisk selinux=0 disk=cciss/c0d0,sda,hda,vda iscsi_target_iqn=iqn-1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_id=1be26c0b-03f2-4d2e-ae87-c02d7f33c123 deployment_key=0123456789ABCDEFGHIJKLMNOPQRSTUV ironic_api_url=http://192.168.122.184:6385 troubleshoot=0 text test_param boot_option=netboot root_device=vendor=fake,size=123 ipa-api-url=http://192.168.122.184:6385 ipa-driver-name=pxe_ssh boot_mode=bios coreos.configdrive=0 ipappend 3 diff --git a/ironic/tests/drivers/test_agent.py b/ironic/tests/drivers/test_agent.py index 7e5ed3133..bb9675f3f 100644 --- a/ironic/tests/drivers/test_agent.py +++ b/ironic/tests/drivers/test_agent.py @@ -48,6 +48,7 @@ class TestAgentMethods(db_base.DbTestCase): options = agent.build_agent_options(self.node) self.assertEqual('api-url', options['ipa-api-url']) self.assertEqual('fake_agent', options['ipa-driver-name']) + self.assertEqual(0, options['coreos.configdrive']) @mock.patch.object(keystone, 'get_service_url') def test_build_agent_options_keystone(self, get_url_mock): @@ -57,6 +58,7 @@ class TestAgentMethods(db_base.DbTestCase): options = agent.build_agent_options(self.node) self.assertEqual('api-url', options['ipa-api-url']) self.assertEqual('fake_agent', options['ipa-driver-name']) + self.assertEqual(0, options['coreos.configdrive']) def test_build_agent_options_root_device_hints(self): self.config(api_url='api-url', group='conductor') @@ -162,6 +164,14 @@ class TestAgentDeploy(db_base.DbTestCase): self.assertIn('driver_info.deploy_ramdisk', str(e)) self.assertIn('driver_info.deploy_kernel', str(e)) + def test_validate_driver_info_manage_tftp_false(self): + self.config(manage_tftp=False, group='agent') + self.node.driver_info = {} + self.node.save() + with task_manager.acquire( + self.context, self.node['uuid'], shared=False) as task: + self.driver.validate(task) + def test_validate_instance_info_missing_params(self): self.node.instance_info = {} self.node.save() @@ -183,6 +193,13 @@ class TestAgentDeploy(db_base.DbTestCase): self.assertRaises(exception.MissingParameterValue, self.driver.validate, task) + def test_validate_agent_fail_partition_image(self): + with task_manager.acquire( + self.context, self.node['uuid'], shared=False) as task: + task.node.driver_internal_info['is_whole_disk_image'] = False + self.assertRaises(exception.InvalidParameterValue, + self.driver.validate, task) + def test_validate_invalid_root_device_hints(self): with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: @@ -190,6 +207,37 @@ class TestAgentDeploy(db_base.DbTestCase): self.assertRaises(exception.InvalidParameterValue, task.driver.deploy.validate, task) + @mock.patch.object(agent, '_cache_tftp_images') + @mock.patch.object(pxe_utils, 'create_pxe_config') + @mock.patch.object(agent, '_build_pxe_config_options') + @mock.patch.object(agent, '_get_tftp_image_info') + def test__prepare_pxe_boot(self, pxe_info_mock, options_mock, + create_mock, cache_mock): + with task_manager.acquire( + self.context, self.node['uuid'], shared=False) as task: + agent._prepare_pxe_boot(task) + pxe_info_mock.assert_called_once_with(task.node) + options_mock.assert_called_once_with(task.node, mock.ANY) + create_mock.assert_called_once_with( + task, mock.ANY, CONF.agent.agent_pxe_config_template) + cache_mock.assert_called_once_with(task.context, task.node, + mock.ANY) + + @mock.patch.object(agent, '_cache_tftp_images') + @mock.patch.object(pxe_utils, 'create_pxe_config') + @mock.patch.object(agent, '_build_pxe_config_options') + @mock.patch.object(agent, '_get_tftp_image_info') + def test__prepare_pxe_boot_manage_tftp_false( + self, pxe_info_mock, options_mock, create_mock, cache_mock): + self.config(manage_tftp=False, group='agent') + with task_manager.acquire( + self.context, self.node['uuid'], shared=False) as task: + agent._prepare_pxe_boot(task) + self.assertFalse(pxe_info_mock.called) + self.assertFalse(options_mock.called) + self.assertFalse(create_mock.called) + self.assertFalse(cache_mock.called) + @mock.patch.object(dhcp_factory.DHCPFactory, 'update_dhcp') @mock.patch('ironic.conductor.utils.node_set_boot_device') @mock.patch('ironic.conductor.utils.node_power_action') @@ -212,6 +260,36 @@ class TestAgentDeploy(db_base.DbTestCase): power_mock.assert_called_once_with(task, states.POWER_OFF) self.assertEqual(driver_return, states.DELETED) + @mock.patch.object(pxe_utils, 'clean_up_pxe_config') + @mock.patch.object(agent, 'AgentTFTPImageCache') + @mock.patch('ironic.common.utils.unlink_without_raise') + @mock.patch.object(agent, '_get_tftp_image_info') + def test__clean_up_pxe(self, info_mock, unlink_mock, cache_mock, + clean_mock): + info_mock.return_value = {'label': ['fake1', 'fake2']} + with task_manager.acquire( + self.context, self.node['uuid'], shared=False) as task: + agent._clean_up_pxe(task) + info_mock.assert_called_once_with(task.node) + unlink_mock.assert_called_once_with('fake2') + clean_mock.assert_called_once_with(task) + + @mock.patch.object(pxe_utils, 'clean_up_pxe_config') + @mock.patch.object(agent.AgentTFTPImageCache, 'clean_up') + @mock.patch('ironic.common.utils.unlink_without_raise') + @mock.patch.object(agent, '_get_tftp_image_info') + def test__clean_up_pxe_manage_tftp_false( + self, info_mock, unlink_mock, cache_mock, clean_mock): + self.config(manage_tftp=False, group='agent') + info_mock.return_value = {'label': ['fake1', 'fake2']} + with task_manager.acquire( + self.context, self.node['uuid'], shared=False) as task: + agent._clean_up_pxe(task) + self.assertFalse(info_mock.called) + self.assertFalse(unlink_mock.called) + self.assertFalse(cache_mock.called) + self.assertFalse(clean_mock.called) + @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports') @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.create_cleaning_ports') @mock.patch('ironic.drivers.modules.agent._do_pxe_boot') @@ -226,8 +304,8 @@ class TestAgentDeploy(db_base.DbTestCase): self.driver.prepare_cleaning(task)) prepare_mock.assert_called_once_with(task) boot_mock.assert_called_once_with(task, ports) - create_mock.assert_called_once() - delete_mock.assert_called_once() + create_mock.assert_called_once_with(task) + delete_mock.assert_called_once_with(task) @mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.delete_cleaning_ports') @mock.patch('ironic.drivers.modules.agent._clean_up_pxe') @@ -238,7 +316,7 @@ class TestAgentDeploy(db_base.DbTestCase): self.assertIsNone(self.driver.tear_down_cleaning(task)) power_mock.assert_called_once_with(task, states.POWER_OFF) cleanup_mock.assert_called_once_with(task) - neutron_mock.assert_called_once() + neutron_mock.assert_called_once_with(task) @mock.patch('ironic.drivers.modules.deploy_utils.agent_get_clean_steps') def test_get_clean_steps(self, mock_get_clean_steps): @@ -254,10 +332,11 @@ class TestAgentDeploy(db_base.DbTestCase): @mock.patch('ironic.drivers.modules.deploy_utils.agent_get_clean_steps') def test_get_clean_steps_config_priority(self, mock_get_clean_steps): # Test that we can override the priority of get clean steps - self.config(agent_erase_devices_priority=20, group='agent') + # Use 0 because it is an edge case (false-y) and used in devstack + self.config(agent_erase_devices_priority=0, group='agent') mock_steps = [{'priority': 10, 'interface': 'deploy', 'step': 'erase_devices'}] - expected_steps = [{'priority': 20, 'interface': 'deploy', + expected_steps = [{'priority': 0, 'interface': 'deploy', 'step': 'erase_devices'}] mock_get_clean_steps.return_value = mock_steps with task_manager.acquire(self.context, self.node.uuid) as task: @@ -399,6 +478,7 @@ class TestAgentVendor(db_base.DbTestCase): 'deployment_ari_path': 'fake-node/deploy_ramdisk', 'ipa-api-url': 'api-url', 'ipa-driver-name': u'fake_agent', + 'coreos.configdrive': 0, 'pxe_append_params': 'foo bar'} if root_device_hints: diff --git a/ironic/tests/drivers/test_agent_base_vendor.py b/ironic/tests/drivers/test_agent_base_vendor.py index 31596abc1..e672df85b 100644 --- a/ironic/tests/drivers/test_agent_base_vendor.py +++ b/ironic/tests/drivers/test_agent_base_vendor.py @@ -284,6 +284,29 @@ class TestBaseAgentVendor(db_base.DbTestCase): '1be26c0b-03f2-4d2e-ae87-c02d7f33c123: Failed checking if deploy ' 'is done. exception: LlamaException') + @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'continue_deploy') + @mock.patch.object(agent_base_vendor.BaseAgentVendor, 'reboot_to_instance') + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + '_notify_conductor_resume_clean') + def test_heartbeat_noops_maintenance_mode(self, ncrc_mock, rti_mock, + cd_mock): + """Ensures that heartbeat() no-ops for a maintenance node.""" + kwargs = { + 'agent_url': 'http://127.0.0.1:9999/bar' + } + self.node.maintenance = True + for state in (states.AVAILABLE, states.DEPLOYWAIT, states.DEPLOYING, + states.CLEANING): + self.node.provision_state = state + self.node.save() + with task_manager.acquire( + self.context, self.node['uuid'], shared=True) as task: + self.passthru.heartbeat(task, **kwargs) + + self.assertEqual(0, ncrc_mock.call_count) + self.assertEqual(0, rti_mock.call_count) + self.assertEqual(0, cd_mock.call_count) + def test_vendor_passthru_vendor_routes(self): expected = ['heartbeat'] with task_manager.acquire(self.context, self.node.uuid, @@ -336,7 +359,9 @@ class TestBaseAgentVendor(db_base.DbTestCase): 'command_status': 'SUCCESS', 'command_error': None} with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: - self.passthru.configure_local_boot(task, 'some-root-uuid') + task.node.driver_internal_info['is_whole_disk_image'] = False + self.passthru.configure_local_boot(task, + root_uuid='some-root-uuid') try_set_boot_device_mock.assert_called_once_with( task, boot_devices.DISK) install_bootloader_mock.assert_called_once_with( @@ -351,6 +376,7 @@ class TestBaseAgentVendor(db_base.DbTestCase): 'command_status': 'SUCCESS', 'command_error': None} with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: + task.node.driver_internal_info['is_whole_disk_image'] = False self.passthru.configure_local_boot( task, root_uuid='some-root-uuid', efi_system_part_uuid='efi-system-part-uuid') @@ -360,6 +386,29 @@ class TestBaseAgentVendor(db_base.DbTestCase): task.node, root_uuid='some-root-uuid', efi_system_part_uuid='efi-system-part-uuid') + @mock.patch.object(deploy_utils, 'try_set_boot_device') + @mock.patch.object(agent_client.AgentClient, 'install_bootloader') + def test_configure_local_boot_whole_disk_image( + self, install_bootloader_mock, try_set_boot_device_mock): + with task_manager.acquire(self.context, self.node['uuid'], + shared=False) as task: + self.passthru.configure_local_boot(task) + self.assertFalse(install_bootloader_mock.called) + try_set_boot_device_mock.assert_called_once_with( + task, boot_devices.DISK) + + @mock.patch.object(deploy_utils, 'try_set_boot_device') + @mock.patch.object(agent_client.AgentClient, 'install_bootloader') + def test_configure_local_boot_no_root_uuid( + self, install_bootloader_mock, try_set_boot_device_mock): + with task_manager.acquire(self.context, self.node['uuid'], + shared=False) as task: + task.node.driver_internal_info['is_whole_disk_image'] = False + self.passthru.configure_local_boot(task) + self.assertFalse(install_bootloader_mock.called) + try_set_boot_device_mock.assert_called_once_with( + task, boot_devices.DISK) + @mock.patch.object(agent_client.AgentClient, 'install_bootloader') def test_configure_local_boot_boot_loader_install_fail( self, install_bootloader_mock): @@ -370,9 +419,10 @@ class TestBaseAgentVendor(db_base.DbTestCase): self.node.save() with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: + task.node.driver_internal_info['is_whole_disk_image'] = False self.assertRaises(exception.InstanceDeployFailure, self.passthru.configure_local_boot, - task, 'some-root-uuid') + task, root_uuid='some-root-uuid') install_bootloader_mock.assert_called_once_with( task.node, root_uuid='some-root-uuid', efi_system_part_uuid=None) @@ -391,9 +441,10 @@ class TestBaseAgentVendor(db_base.DbTestCase): self.node.save() with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: + task.node.driver_internal_info['is_whole_disk_image'] = False self.assertRaises(exception.InstanceDeployFailure, self.passthru.configure_local_boot, - task, 'some-root-uuid') + task, root_uuid='some-root-uuid') install_bootloader_mock.assert_called_once_with( task.node, root_uuid='some-root-uuid', efi_system_part_uuid=None) @@ -406,8 +457,20 @@ class TestBaseAgentVendor(db_base.DbTestCase): '_notify_conductor_resume_clean') @mock.patch.object(agent_client.AgentClient, 'get_commands_status') def test_continue_cleaning(self, status_mock, notify_mock): + # Test a successful execute clean step on the agent + self.node.clean_step = { + 'priority': 10, + 'interface': 'deploy', + 'step': 'erase_devices', + 'reboot_requested': False + } + self.node.save() status_mock.return_value = [{ 'command_status': 'SUCCEEDED', + 'command_name': 'execute_clean_step', + 'command_result': { + 'clean_step': self.node.clean_step + } }] with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: @@ -417,20 +480,54 @@ class TestBaseAgentVendor(db_base.DbTestCase): @mock.patch.object(agent_base_vendor.BaseAgentVendor, '_notify_conductor_resume_clean') @mock.patch.object(agent_client.AgentClient, 'get_commands_status') + def test_continue_cleaning_old_command(self, status_mock, notify_mock): + # Test when a second execute_clean_step happens to the agent, but + # the new step hasn't started yet. + self.node.clean_step = { + 'priority': 10, + 'interface': 'deploy', + 'step': 'erase_devices', + 'reboot_requested': False + } + self.node.save() + status_mock.return_value = [{ + 'command_status': 'SUCCEEDED', + 'command_name': 'execute_clean_step', + 'command_result': { + 'priority': 20, + 'interface': 'deploy', + 'step': 'update_firmware', + 'reboot_requested': False + } + }] + with task_manager.acquire(self.context, self.node['uuid'], + shared=False) as task: + self.passthru.continue_cleaning(task) + self.assertFalse(notify_mock.called) + + @mock.patch.object(agent_base_vendor.BaseAgentVendor, + '_notify_conductor_resume_clean') + @mock.patch.object(agent_client.AgentClient, 'get_commands_status') def test_continue_cleaning_running(self, status_mock, notify_mock): + # Test that no action is taken while a clean step is executing status_mock.return_value = [{ 'command_status': 'RUNNING', + 'command_name': 'execute_clean_step', + 'command_result': {} }] with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: self.passthru.continue_cleaning(task) - notify_mock.assert_not_called() + self.assertFalse(notify_mock.called) @mock.patch('ironic.conductor.manager.cleaning_error_handler') @mock.patch.object(agent_client.AgentClient, 'get_commands_status') def test_continue_cleaning_fail(self, status_mock, error_mock): + # Test the a failure puts the node in CLEANFAIL status_mock.return_value = [{ 'command_status': 'FAILED', + 'command_name': 'execute_clean_step', + 'command_result': {} }] with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: @@ -443,8 +540,11 @@ class TestBaseAgentVendor(db_base.DbTestCase): @mock.patch.object(agent_client.AgentClient, 'get_commands_status') def test_continue_cleaning_clean_version_mismatch( self, status_mock, notify_mock, steps_mock): + # Test that cleaning is restarted if there is a version mismatch status_mock.return_value = [{ 'command_status': 'CLEAN_VERSION_MISMATCH', + 'command_name': 'execute_clean_step', + 'command_result': {} }] with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: @@ -455,8 +555,11 @@ class TestBaseAgentVendor(db_base.DbTestCase): @mock.patch('ironic.conductor.manager.cleaning_error_handler') @mock.patch.object(agent_client.AgentClient, 'get_commands_status') def test_continue_cleaning_unknown(self, status_mock, error_mock): + # Test that unknown commands are treated as failures status_mock.return_value = [{ 'command_status': 'UNKNOWN', + 'command_name': 'execute_clean_step', + 'command_result': {} }] with task_manager.acquire(self.context, self.node['uuid'], shared=False) as task: diff --git a/ironic/tests/drivers/test_agent_client.py b/ironic/tests/drivers/test_agent_client.py index 80a0f69af..bfe91f83a 100644 --- a/ironic/tests/drivers/test_agent_client.py +++ b/ironic/tests/drivers/test_agent_client.py @@ -16,6 +16,7 @@ import json import mock import requests +import six from ironic.common import exception from ironic.drivers.modules import agent_client @@ -23,12 +24,12 @@ from ironic.tests import base class MockResponse(object): - def __init__(self, data): - self.data = data - self.text = json.dumps(data) + def __init__(self, text): + assert isinstance(text, six.string_types) + self.text = text def json(self): - return self.data + return json.loads(self.text) class MockNode(object): @@ -75,7 +76,8 @@ class TestAgentClient(base.TestCase): def test__command(self): response_data = {'status': 'ok'} - self.client.session.post.return_value = MockResponse(response_data) + response_text = json.dumps(response_data) + self.client.session.post.return_value = MockResponse(response_text) method = 'standby.run_image' image_info = {'image_id': 'test_image'} params = {'image_info': image_info} @@ -92,6 +94,26 @@ class TestAgentClient(base.TestCase): headers=headers, params={'wait': 'false'}) + def test__command_fail_json(self): + response_text = 'this be not json matey!' + self.client.session.post.return_value = MockResponse(response_text) + method = 'standby.run_image' + image_info = {'image_id': 'test_image'} + params = {'image_info': image_info} + + url = self.client._get_command_url(self.node) + body = self.client._get_command_body(method, params) + headers = {'Content-Type': 'application/json'} + + self.assertRaises(exception.IronicException, + self.client._command, + self.node, method, params) + self.client.session.post.assert_called_once_with( + url, + data=body, + headers=headers, + params={'wait': 'false'}) + def test_get_commands_status(self): with mock.patch.object(self.client.session, 'get') as mock_get: res = mock.Mock() diff --git a/ironic/tests/drivers/test_deploy_utils.py b/ironic/tests/drivers/test_deploy_utils.py index dfbcc5532..fdb6653a3 100644 --- a/ironic/tests/drivers/test_deploy_utils.py +++ b/ironic/tests/drivers/test_deploy_utils.py @@ -37,6 +37,7 @@ from ironic.common import states from ironic.common import utils as common_utils from ironic.conductor import task_manager from ironic.conductor import utils as manager_utils +from ironic.drivers.modules import agent_client from ironic.drivers.modules import deploy_utils as utils from ironic.drivers.modules import image_cache from ironic.tests import base as tests_base @@ -180,7 +181,7 @@ image=kernel image=chain.c32 label=boot_whole_disk - append mbr:{{ DISK_IDENTIFIER }} + append="mbr:{{ DISK_IDENTIFIER }}" """ _UEFI_PXECONF_BOOT_PARTITION = """ @@ -198,7 +199,7 @@ image=kernel image=chain.c32 label=boot_whole_disk - append mbr:{{ DISK_IDENTIFIER }} + append="mbr:{{ DISK_IDENTIFIER }}" """ _UEFI_PXECONF_BOOT_WHOLE_DISK = """ @@ -216,7 +217,7 @@ image=kernel image=chain.c32 label=boot_whole_disk - append mbr:0x12345678 + append="mbr:0x12345678" """ @@ -1019,13 +1020,18 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.swap_part = '/dev/fake-part1' self.root_part = '/dev/fake-part2' - self.mock_ibd = mock.patch.object(utils, 'is_block_device').start() - self.mock_mp = mock.patch.object(utils, 'make_partitions').start() - self.addCleanup(self.mock_ibd.stop) - self.addCleanup(self.mock_mp.stop) - self.mock_remlbl = mock.patch.object(utils, - 'destroy_disk_metadata').start() - self.addCleanup(self.mock_remlbl.stop) + self.mock_ibd_obj = mock.patch.object( + utils, 'is_block_device', autospec=True) + self.mock_ibd = self.mock_ibd_obj.start() + self.addCleanup(self.mock_ibd_obj.stop) + self.mock_mp_obj = mock.patch.object( + utils, 'make_partitions', autospec=True) + self.mock_mp = self.mock_mp_obj.start() + self.addCleanup(self.mock_mp_obj.stop) + self.mock_remlbl_obj = mock.patch.object( + utils, 'destroy_disk_metadata', autospec=True) + self.mock_remlbl = self.mock_remlbl_obj.start() + self.addCleanup(self.mock_remlbl_obj.stop) self.mock_mp.return_value = {'swap': self.swap_part, 'root': self.root_part} @@ -1043,7 +1049,7 @@ class WorkOnDiskTestCase(tests_base.TestCase): boot_mode="bios") def test_no_swap_partition(self): - self.mock_ibd.side_effect = [True, False] + self.mock_ibd.side_effect = iter([True, False]) calls = [mock.call(self.root_part), mock.call(self.swap_part)] self.assertRaises(exception.InstanceDeployFailure, @@ -1067,7 +1073,7 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.mock_mp.return_value = {'ephemeral': ephemeral_part, 'swap': swap_part, 'root': root_part} - self.mock_ibd.side_effect = [True, True, False] + self.mock_ibd.side_effect = iter([True, True, False]) calls = [mock.call(root_part), mock.call(swap_part), mock.call(ephemeral_part)] @@ -1095,7 +1101,7 @@ class WorkOnDiskTestCase(tests_base.TestCase): self.mock_mp.return_value = {'swap': swap_part, 'configdrive': configdrive_part, 'root': root_part} - self.mock_ibd.side_effect = [True, True, False] + self.mock_ibd.side_effect = iter([True, True, False]) calls = [mock.call(root_part), mock.call(swap_part), mock.call(configdrive_part)] @@ -1453,7 +1459,7 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase): utils.parse_instance_info_capabilities, self.node) def test_is_secure_boot_requested_true(self): - self.node.instance_info = {'capabilities': {"secure_boot": "true"}} + self.node.instance_info = {'capabilities': {"secure_boot": "tRue"}} self.assertTrue(utils.is_secure_boot_requested(self.node)) def test_is_secure_boot_requested_false(self): @@ -1464,6 +1470,27 @@ class ParseInstanceInfoCapabilitiesTestCase(tests_base.TestCase): self.node.instance_info = {'capabilities': {"secure_boot": "invalid"}} self.assertFalse(utils.is_secure_boot_requested(self.node)) + def test_get_boot_mode_for_deploy_using_capabilities(self): + properties = {'capabilities': 'boot_mode:uefi,cap2:value2'} + self.node.properties = properties + + result = utils.get_boot_mode_for_deploy(self.node) + self.assertEqual('uefi', result) + + def test_get_boot_mode_for_deploy_using_instance_info_cap(self): + instance_info = {'capabilities': {'secure_boot': 'True'}} + self.node.instance_info = instance_info + + result = utils.get_boot_mode_for_deploy(self.node) + self.assertEqual('uefi', result) + + def test_get_boot_mode_for_deploy_using_instance_info(self): + instance_info = {'deploy_boot_mode': 'bios'} + self.node.instance_info = instance_info + + result = utils.get_boot_mode_for_deploy(self.node) + self.assertEqual('bios', result) + class TrySetBootDeviceTestCase(db_base.DbTestCase): @@ -1526,7 +1553,9 @@ class AgentCleaningTestCase(db_base.DbTestCase): def setUp(self): super(AgentCleaningTestCase, self).setUp() mgr_utils.mock_the_extension_manager(driver='fake_agent') - n = {'driver': 'fake_agent'} + n = {'driver': 'fake_agent', + 'driver_internal_info': {'agent_url': 'http://127.0.0.1:9999'}} + self.node = obj_utils.create_test_node(self.context, **n) self.ports = [obj_utils.create_test_port(self.context, node_id=self.node.id)] @@ -1551,39 +1580,34 @@ class AgentCleaningTestCase(db_base.DbTestCase): } @mock.patch('ironic.objects.Port.list_by_node_id') - @mock.patch('ironic.drivers.modules.deploy_utils._get_agent_client') - def test_get_clean_steps(self, get_client_mock, list_ports_mock): - client_mock = mock.Mock() - client_mock.get_clean_steps.return_value = { + @mock.patch.object(agent_client.AgentClient, 'get_clean_steps') + def test_get_clean_steps(self, client_mock, list_ports_mock): + client_mock.return_value = { 'command_result': self.clean_steps} - get_client_mock.return_value = client_mock list_ports_mock.return_value = self.ports with task_manager.acquire( self.context, self.node['uuid'], shared=False) as task: response = utils.agent_get_clean_steps(task) - client_mock.get_clean_steps.assert_called_once_with(task.node, - self.ports) + client_mock.assert_called_once_with(task.node, self.ports) self.assertEqual('1', task.node.driver_internal_info[ 'hardware_manager_version']) # Since steps are returned in dicts, they have non-deterministic # ordering self.assertEqual(2, len(response)) - self.assertTrue(self.clean_steps['clean_steps'][ - 'GenericHardwareManager'][0] in response) - self.assertTrue(self.clean_steps['clean_steps'][ - 'SpecificHardwareManager'][0] in response) + self.assertIn(self.clean_steps['clean_steps'][ + 'GenericHardwareManager'][0], response) + self.assertIn(self.clean_steps['clean_steps'][ + 'SpecificHardwareManager'][0], response) @mock.patch('ironic.objects.Port.list_by_node_id') - @mock.patch('ironic.drivers.modules.deploy_utils._get_agent_client') - def test_get_clean_steps_missing_steps(self, get_client_mock, + @mock.patch.object(agent_client.AgentClient, 'get_clean_steps') + def test_get_clean_steps_missing_steps(self, client_mock, list_ports_mock): - client_mock = mock.Mock() del self.clean_steps['clean_steps'] - client_mock.get_clean_steps.return_value = { + client_mock.return_value = { 'command_result': self.clean_steps} - get_client_mock.return_value = client_mock list_ports_mock.return_value = self.ports with task_manager.acquire( @@ -1591,16 +1615,13 @@ class AgentCleaningTestCase(db_base.DbTestCase): self.assertRaises(exception.NodeCleaningFailure, utils.agent_get_clean_steps, task) - client_mock.get_clean_steps.assert_called_once_with(task.node, - self.ports) + client_mock.assert_called_once_with(task.node, self.ports) @mock.patch('ironic.objects.Port.list_by_node_id') - @mock.patch('ironic.drivers.modules.deploy_utils._get_agent_client') - def test_execute_clean_step(self, get_client_mock, list_ports_mock): - client_mock = mock.Mock() - client_mock.execute_clean_step.return_value = { + @mock.patch.object(agent_client.AgentClient, 'execute_clean_step') + def test_execute_clean_step(self, client_mock, list_ports_mock): + client_mock.return_value = { 'command_status': 'SUCCEEDED'} - get_client_mock.return_value = client_mock list_ports_mock.return_value = self.ports with task_manager.acquire( @@ -1611,13 +1632,10 @@ class AgentCleaningTestCase(db_base.DbTestCase): self.assertEqual(states.CLEANING, response) @mock.patch('ironic.objects.Port.list_by_node_id') - @mock.patch('ironic.drivers.modules.deploy_utils._get_agent_client') - def test_execute_clean_step_running(self, get_client_mock, - list_ports_mock): - client_mock = mock.Mock() - client_mock.execute_clean_step.return_value = { + @mock.patch.object(agent_client.AgentClient, 'execute_clean_step') + def test_execute_clean_step_running(self, client_mock, list_ports_mock): + client_mock.return_value = { 'command_status': 'RUNNING'} - get_client_mock.return_value = client_mock list_ports_mock.return_value = self.ports with task_manager.acquire( @@ -1628,13 +1646,11 @@ class AgentCleaningTestCase(db_base.DbTestCase): self.assertEqual(states.CLEANING, response) @mock.patch('ironic.objects.Port.list_by_node_id') - @mock.patch('ironic.drivers.modules.deploy_utils._get_agent_client') - def test_execute_clean_step_version_mismatch(self, get_client_mock, + @mock.patch.object(agent_client.AgentClient, 'execute_clean_step') + def test_execute_clean_step_version_mismatch(self, client_mock, list_ports_mock): - client_mock = mock.Mock() - client_mock.execute_clean_step.return_value = { + client_mock.return_value = { 'command_status': 'RUNNING'} - get_client_mock.return_value = client_mock list_ports_mock.return_value = self.ports with task_manager.acquire( diff --git a/ironic/tests/drivers/test_ipmitool.py b/ironic/tests/drivers/test_ipmitool.py index a27060d0f..2e0097fba 100644 --- a/ironic/tests/drivers/test_ipmitool.py +++ b/ironic/tests/drivers/test_ipmitool.py @@ -840,6 +840,86 @@ class IPMIToolPrivateMethodTestCase(db_base.DbTestCase): mock_support.assert_called_once_with('timing') mock_pwf.assert_called_once_with(self.info['password']) mock_exec.assert_called_once_with(*args) + self.assertEqual(1, mock_exec.call_count) + + @mock.patch.object(ipmi, '_is_option_supported', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + def test__exec_ipmitool_exception_retry(self, + mock_exec, mock_support, mock_sleep): + + ipmi.LAST_CMD_TIME = {} + mock_support.return_value = False + mock_exec.side_effect = iter([ + processutils.ProcessExecutionError( + stderr="insufficient resources for session" + ), + (None, None) + ]) + + # Directly set the configuration values such that + # the logic will cause _exec_ipmitool to retry twice. + self.config(min_command_interval=1, group='ipmi') + self.config(retry_timeout=2, group='ipmi') + + ipmi._exec_ipmitool(self.info, 'A B C') + + mock_support.assert_called_once_with('timing') + self.assertEqual(2, mock_exec.call_count) + + @mock.patch.object(ipmi, '_is_option_supported', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + def test__exec_ipmitool_exception_retries_exceeded(self, + mock_exec, mock_support, mock_sleep): + + ipmi.LAST_CMD_TIME = {} + mock_support.return_value = False + + mock_exec.side_effect = processutils.ProcessExecutionError( + stderr="insufficient resources for session" + ) + + # Directly set the configuration values such that + # the logic will cause _exec_ipmitool to timeout. + self.config(min_command_interval=1, group='ipmi') + self.config(retry_timeout=1, group='ipmi') + + self.assertRaises(processutils.ProcessExecutionError, + ipmi._exec_ipmitool, + self.info, 'A B C') + mock_support.assert_called_once_with('timing') + self.assertEqual(1, mock_exec.call_count) + + @mock.patch.object(ipmi, '_is_option_supported', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + def test__exec_ipmitool_exception_non_retryable_failure(self, + mock_exec, mock_support, mock_sleep): + + ipmi.LAST_CMD_TIME = {} + mock_support.return_value = False + + # Return a retryable error, then an error that cannot + # be retried thus resulting in a single retry + # attempt by _exec_ipmitool. + mock_exec.side_effect = iter([ + processutils.ProcessExecutionError( + stderr="insufficient resources for session" + ), + processutils.ProcessExecutionError( + stderr="Unknown" + ), + ]) + + # Directly set the configuration values such that + # the logic will cause _exec_ipmitool to retry up + # to 3 times. + self.config(min_command_interval=1, group='ipmi') + self.config(retry_timeout=3, group='ipmi') + + self.assertRaises(processutils.ProcessExecutionError, + ipmi._exec_ipmitool, + self.info, 'A B C') + mock_support.assert_called_once_with('timing') + self.assertEqual(2, mock_exec.call_count) @mock.patch.object(ipmi, '_exec_ipmitool', autospec=True) def test__power_status_on(self, mock_exec, mock_sleep): diff --git a/ironic/tests/drivers/test_iscsi_deploy.py b/ironic/tests/drivers/test_iscsi_deploy.py index 01b4c853e..4d3803542 100644 --- a/ironic/tests/drivers/test_iscsi_deploy.py +++ b/ironic/tests/drivers/test_iscsi_deploy.py @@ -429,6 +429,7 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): 'ironic_api_url': api_url, 'boot_option': expected_boot_option, 'boot_mode': expected_boot_mode, + 'coreos.configdrive': 0, } if expected_root_device: @@ -486,6 +487,25 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url, expected_boot_option=expected) + @mock.patch.object(keystone, 'get_service_url', autospec=True) + @mock.patch.object(utils, 'random_alnum', autospec=True) + def test_build_deploy_ramdisk_options_whole_disk_image(self, mock_alnum, + mock_get_url): + """Tests a hack to boot_option for whole disk images. + + This hack is in place to fix bug #1441556. + """ + self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} + dii = self.node.driver_internal_info + dii['is_whole_disk_image'] = True + self.node.driver_internal_info = dii + self.node.save() + expected = 'netboot' + fake_api_url = 'http://127.0.0.1:6385' + self.config(api_url=fake_api_url, group='conductor') + self._test_build_deploy_ramdisk_options(mock_alnum, fake_api_url, + expected_boot_option=expected) + def test_get_boot_option(self): self.node.instance_info = {'capabilities': '{"boot_option": "local"}'} result = iscsi_deploy.get_boot_option(self.node) @@ -777,3 +797,118 @@ class IscsiDeployMethodsTestCase(db_base.DbTestCase): self.assertEqual(states.DEPLOYFAIL, self.node.provision_state) self.assertEqual(states.ACTIVE, self.node.target_provision_state) self.assertIsNotNone(self.node.last_error) + + def test_validate_pass_bootloader_info_input(self): + params = {'key': 'some-random-key', 'address': '1.2.3.4', + 'error': '', 'status': 'SUCCEEDED'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.instance_info['deploy_key'] = 'some-random-key' + # Assert that the method doesn't raise + iscsi_deploy.validate_pass_bootloader_info_input(task, params) + + def test_validate_pass_bootloader_info_missing_status(self): + params = {'key': 'some-random-key', 'address': '1.2.3.4'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.MissingParameterValue, + iscsi_deploy.validate_pass_bootloader_info_input, + task, params) + + def test_validate_pass_bootloader_info_missing_key(self): + params = {'status': 'SUCCEEDED', 'address': '1.2.3.4'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.MissingParameterValue, + iscsi_deploy.validate_pass_bootloader_info_input, + task, params) + + def test_validate_pass_bootloader_info_missing_address(self): + params = {'status': 'SUCCEEDED', 'key': 'some-random-key'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + self.assertRaises(exception.MissingParameterValue, + iscsi_deploy.validate_pass_bootloader_info_input, + task, params) + + def test_validate_pass_bootloader_info_input_invalid_key(self): + params = {'key': 'some-other-key', 'address': '1.2.3.4', + 'status': 'SUCCEEDED'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.instance_info['deploy_key'] = 'some-random-key' + self.assertRaises(exception.InvalidParameterValue, + iscsi_deploy.validate_pass_bootloader_info_input, + task, params) + + def test_validate_bootloader_install_status(self): + kwargs = {'key': 'abcdef', 'status': 'SUCCEEDED', 'error': ''} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.instance_info['deploy_key'] = 'abcdef' + # Nothing much to assert except that it shouldn't raise. + iscsi_deploy.validate_bootloader_install_status(task, kwargs) + + @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True) + def test_validate_bootloader_install_status_install_failed( + self, set_fail_state_mock): + kwargs = {'key': 'abcdef', 'status': 'FAILED', 'error': 'some-error'} + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.node.provision_state = states.DEPLOYING + task.node.target_provision_state = states.ACTIVE + task.node.instance_info['deploy_key'] = 'abcdef' + self.assertRaises(exception.InstanceDeployFailure, + iscsi_deploy.validate_bootloader_install_status, + task, kwargs) + set_fail_state_mock.assert_called_once_with(task, mock.ANY) + + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed', + autospec=True) + def test_finish_deploy(self, notify_mock): + self.node.provision_state = states.DEPLOYING + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + iscsi_deploy.finish_deploy(task, '1.2.3.4') + notify_mock.assert_called_once_with('1.2.3.4') + self.assertEqual(states.ACTIVE, task.node.provision_state) + self.assertEqual(states.NOSTATE, task.node.target_provision_state) + + @mock.patch.object(deploy_utils, 'set_failed_state', autospec=True) + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed', + autospec=True) + def test_finish_deploy_notify_fails(self, notify_mock, + set_fail_state_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + notify_mock.side_effect = RuntimeError() + self.assertRaises(exception.InstanceDeployFailure, + iscsi_deploy.finish_deploy, task, '1.2.3.4') + set_fail_state_mock.assert_called_once_with(task, mock.ANY) + + @mock.patch.object(manager_utils, 'node_power_action') + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed', + autospec=True) + def test_finish_deploy_ssh_with_local_boot(self, notify_mock, + node_power_mock): + instance_info = dict(INST_INFO_DICT) + instance_info['capabilities'] = {'boot_option': 'local'} + n = { + 'uuid': uuidutils.generate_uuid(), + 'driver': 'fake_ssh', + 'instance_info': instance_info, + 'provision_state': states.DEPLOYING, + 'target_provision_state': states.ACTIVE, + } + mgr_utils.mock_the_extension_manager(driver="fake_ssh") + node = obj_utils.create_test_node(self.context, **n) + + with task_manager.acquire(self.context, node.uuid, + shared=False) as task: + iscsi_deploy.finish_deploy(task, '1.2.3.4') + notify_mock.assert_called_once_with('1.2.3.4') + self.assertEqual(states.ACTIVE, task.node.provision_state) + self.assertEqual(states.NOSTATE, task.node.target_provision_state) + node_power_mock.assert_called_once_with(task, states.REBOOT) diff --git a/ironic/tests/drivers/test_pxe.py b/ironic/tests/drivers/test_pxe.py index a7a211f75..0c6168c86 100644 --- a/ironic/tests/drivers/test_pxe.py +++ b/ironic/tests/drivers/test_pxe.py @@ -198,7 +198,8 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options') @mock.patch.object(pxe_utils, '_build_pxe_config') def _test_build_pxe_config_options(self, build_pxe_mock, deploy_opts_mock, - ipxe_enabled=False): + whle_dsk_img=False, + ipxe_enabled=False): self.config(pxe_append_params='test_param', group='pxe') # NOTE: right '/' should be removed from url string self.config(api_url='http://192.168.122.184:6385', group='conductor') @@ -212,9 +213,11 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'ironic_api_url': 'fake-api-url', 'boot_option': 'netboot', 'boot_mode': 'bios', + 'coreos.configdrive': 0, } deploy_opts_mock.return_value = fake_deploy_opts + self.node.driver_internal_info['is_whole_disk_image'] = whle_dsk_img tftp_server = CONF.pxe.tftp_server @@ -241,6 +244,10 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'ramdisk') root_dir = CONF.pxe.tftp_root + if whle_dsk_img: + ramdisk = 'no_ramdisk' + kernel = 'no_kernel' + expected_options = { 'ari_path': ramdisk, 'deployment_ari_path': deploy_ramdisk, @@ -278,30 +285,36 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): self.assertEqual(expected_options, options) def test__build_pxe_config_options(self): - self._test_build_pxe_config_options(ipxe_enabled=False) + self._test_build_pxe_config_options(whle_dsk_img=True, + ipxe_enabled=False) def test__build_pxe_config_options_ipxe(self): - self._test_build_pxe_config_options(ipxe_enabled=True) + self._test_build_pxe_config_options(whle_dsk_img=True, + ipxe_enabled=True) def test__build_pxe_config_options_without_is_whole_disk_image(self): del self.node.driver_internal_info['is_whole_disk_image'] self.node.save() - self._test_build_pxe_config_options(ipxe_enabled=False) + self._test_build_pxe_config_options(whle_dsk_img=False, + ipxe_enabled=False) @mock.patch.object(iscsi_deploy, 'build_deploy_ramdisk_options') @mock.patch.object(pxe_utils, '_build_pxe_config') - def _test_build_pxe_config_options_whole_disk_image(self, build_pxe_mock, - deploy_opts_mock, ipxe_enabled=False): + def test__build_pxe_config_options_whole_disk_image(self, + build_pxe_mock, + deploy_opts_mock, + ipxe_enabled=False): self.config(pxe_append_params='test_param', group='pxe') # NOTE: right '/' should be removed from url string - self.config(api_url='http://192.168.122.184:6385/', group='conductor') + self.config(api_url='http://192.168.122.184:6385', group='conductor') self.config(disk_devices='sda', group='pxe') fake_deploy_opts = {'iscsi_target_iqn': 'fake-iqn', 'deployment_id': 'fake-deploy-id', 'deployment_key': 'fake-deploy-key', 'disk': 'fake-disk', - 'ironic_api_url': 'fake-api-url'} + 'ironic_api_url': 'fake-api-url', + 'coreos.configdrive': 0} deploy_opts_mock.return_value = fake_deploy_opts @@ -329,6 +342,10 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): 'pxe_append_params': 'test_param', 'deployment_aki_path': deploy_kernel, 'tftp_server': tftp_server, + 'aki_path': 'no_kernel', + 'ari_path': 'no_ramdisk', + 'ipa-api-url': CONF.conductor.api_url, + 'ipa-driver-name': self.node.driver, } expected_options.update(fake_deploy_opts) @@ -401,6 +418,48 @@ class PXEPrivateMethodsTestCase(db_base.DbTestCase): fake_pxe_info.values(), True) + @mock.patch.object(pxe.LOG, 'error') + def test_validate_boot_option_for_uefi_exc(self, mock_log): + properties = {'capabilities': 'boot_mode:uefi'} + instance_info = {"boot_option": "netboot"} + self.node.properties = properties + self.node.instance_info['capabilities'] = instance_info + self.node.driver_internal_info['is_whole_disk_image'] = True + self.assertRaises(exception.InvalidParameterValue, + pxe.validate_boot_option_for_uefi, + self.node) + self.assertTrue(mock_log.called) + + @mock.patch.object(pxe.LOG, 'error') + def test_validate_boot_option_for_uefi_noexc_one(self, mock_log): + properties = {'capabilities': 'boot_mode:uefi'} + instance_info = {"boot_option": "local"} + self.node.properties = properties + self.node.instance_info['capabilities'] = instance_info + self.node.driver_internal_info['is_whole_disk_image'] = True + pxe.validate_boot_option_for_uefi(self.node) + self.assertFalse(mock_log.called) + + @mock.patch.object(pxe.LOG, 'error') + def test_validate_boot_option_for_uefi_noexc_two(self, mock_log): + properties = {'capabilities': 'boot_mode:bios'} + instance_info = {"boot_option": "local"} + self.node.properties = properties + self.node.instance_info['capabilities'] = instance_info + self.node.driver_internal_info['is_whole_disk_image'] = True + pxe.validate_boot_option_for_uefi(self.node) + self.assertFalse(mock_log.called) + + @mock.patch.object(pxe.LOG, 'error') + def test_validate_boot_option_for_uefi_noexc_three(self, mock_log): + properties = {'capabilities': 'boot_mode:uefi'} + instance_info = {"boot_option": "local"} + self.node.properties = properties + self.node.instance_info['capabilities'] = instance_info + self.node.driver_internal_info['is_whole_disk_image'] = False + pxe.validate_boot_option_for_uefi(self.node) + self.assertFalse(mock_log.called) + class PXEDriverTestCase(db_base.DbTestCase): @@ -484,6 +543,17 @@ class PXEDriverTestCase(db_base.DbTestCase): self.assertRaises(exception.InvalidParameterValue, task.driver.deploy.validate, task) + def test_validate_fail_invalid_config_uefi_whole_disk_image(self): + properties = {'capabilities': 'boot_mode:uefi,boot_option:netboot'} + instance_info = {"boot_option": "netboot"} + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + task.node.properties = properties + task.node.instance_info['capabilities'] = instance_info + task.node.driver_internal_info['is_whole_disk_image'] = True + self.assertRaises(exception.InvalidParameterValue, + task.driver.deploy.validate, task) + @mock.patch.object(base_image_service.BaseImageService, '_show') def test_validate_fail_invalid_boot_option(self, mock_glance): properties = {'capabilities': 'boot_option:foo,dog:wuff'} @@ -611,6 +681,33 @@ class PXEDriverTestCase(db_base.DbTestCase): address='123456', iqn='aaa-bbb', key='fake-12345') + @mock.patch.object(iscsi_deploy, 'validate_pass_bootloader_info_input', + autospec=True) + def test_vendor_passthru_pass_bootloader_install_info(self, + validate_mock): + with task_manager.acquire(self.context, self.node.uuid, + shared=True) as task: + kwargs = {'address': '1.2.3.4', 'key': 'fake-key', + 'status': 'SUCCEEDED', 'error': ''} + task.driver.vendor.validate( + task, method='pass_bootloader_install_info', **kwargs) + validate_mock.assert_called_once_with(task, kwargs) + + @mock.patch.object(iscsi_deploy, 'validate_bootloader_install_status', + autospec=True) + @mock.patch.object(iscsi_deploy, 'finish_deploy', autospec=True) + def test_pass_bootloader_install_info(self, finish_deploy_mock, + validate_input_mock): + kwargs = {'method': 'pass_deploy_info', 'address': '123456'} + self.node.provision_state = states.DEPLOYWAIT + self.node.target_provision_state = states.ACTIVE + self.node.save() + with task_manager.acquire(self.context, self.node.uuid, + shared=False) as task: + task.driver.vendor.pass_bootloader_install_info(task, **kwargs) + finish_deploy_mock.assert_called_once_with(task, '123456') + validate_input_mock.assert_called_once_with(task, kwargs) + @mock.patch.object(pxe, '_get_image_info') @mock.patch.object(pxe, '_cache_ramdisk_kernel') @mock.patch.object(pxe, '_build_pxe_config_options') @@ -831,7 +928,7 @@ class PXEDriverTestCase(db_base.DbTestCase): @mock.patch.object(pxe_utils, 'clean_up_pxe_config') @mock.patch.object(manager_utils, 'node_set_boot_device') - @mock.patch.object(deploy_utils, 'notify_deploy_complete') + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed') @mock.patch.object(deploy_utils, 'switch_pxe_config') @mock.patch.object(iscsi_deploy, 'InstanceImageCache') @mock.patch.object(deploy_utils, 'deploy_partition_image') @@ -862,8 +959,6 @@ class PXEDriverTestCase(db_base.DbTestCase): task, address='123456', iqn='aaa-bbb', key='fake-56789') self.node.refresh() - self.assertEqual(states.ACTIVE, self.node.provision_state) - self.assertEqual(states.NOSTATE, self.node.target_provision_state) self.assertEqual(states.POWER_ON, self.node.power_state) self.assertIn('root_uuid_or_disk_id', self.node.driver_internal_info) self.assertIsNone(self.node.last_error) @@ -887,7 +982,7 @@ class PXEDriverTestCase(db_base.DbTestCase): @mock.patch.object(pxe_utils, 'clean_up_pxe_config') @mock.patch.object(manager_utils, 'node_set_boot_device') - @mock.patch.object(deploy_utils, 'notify_deploy_complete') + @mock.patch.object(deploy_utils, 'notify_ramdisk_to_proceed') @mock.patch.object(deploy_utils, 'switch_pxe_config') @mock.patch.object(iscsi_deploy, 'InstanceImageCache') @mock.patch.object(deploy_utils, 'deploy_disk_image') @@ -923,8 +1018,6 @@ class PXEDriverTestCase(db_base.DbTestCase): key='fake-56789') self.node.refresh() - self.assertEqual(states.ACTIVE, self.node.provision_state) - self.assertEqual(states.NOSTATE, self.node.target_provision_state) self.assertEqual(states.POWER_ON, self.node.power_state) self.assertIsNone(self.node.last_error) self.assertFalse(os.path.exists(token_path)) @@ -947,15 +1040,23 @@ class PXEDriverTestCase(db_base.DbTestCase): def test_pass_deploy_info_deploy(self): self._test_pass_deploy_info_deploy(False) + self.assertEqual(states.ACTIVE, self.node.provision_state) + self.assertEqual(states.NOSTATE, self.node.target_provision_state) def test_pass_deploy_info_localboot(self): self._test_pass_deploy_info_deploy(True) + self.assertEqual(states.DEPLOYWAIT, self.node.provision_state) + self.assertEqual(states.ACTIVE, self.node.target_provision_state) def test_pass_deploy_info_whole_disk_image(self): self._test_pass_deploy_info_whole_disk_image(False) + self.assertEqual(states.ACTIVE, self.node.provision_state) + self.assertEqual(states.NOSTATE, self.node.target_provision_state) def test_pass_deploy_info_whole_disk_image_localboot(self): self._test_pass_deploy_info_whole_disk_image(True) + self.assertEqual(states.ACTIVE, self.node.provision_state) + self.assertEqual(states.NOSTATE, self.node.target_provision_state) def test_pass_deploy_info_invalid(self): self.node.power_state = states.POWER_ON @@ -986,7 +1087,8 @@ class PXEDriverTestCase(db_base.DbTestCase): "pass_deploy_info was not called once.") def test_vendor_routes(self): - expected = ['heartbeat', 'pass_deploy_info'] + expected = ['heartbeat', 'pass_deploy_info', + 'pass_bootloader_install_info'] with task_manager.acquire(self.context, self.node.uuid, shared=True) as task: vendor_routes = task.driver.vendor.vendor_routes diff --git a/ironic/tests/drivers/test_seamicro.py b/ironic/tests/drivers/test_seamicro.py index ad4586828..e477cddd4 100644 --- a/ironic/tests/drivers/test_seamicro.py +++ b/ironic/tests/drivers/test_seamicro.py @@ -129,6 +129,7 @@ class SeaMicroValidateParametersTestCase(db_base.DbTestCase): node) +@mock.patch('eventlet.greenthread.sleep', lambda n: None) class SeaMicroPrivateMethodsTestCase(db_base.DbTestCase): def setUp(self): @@ -144,8 +145,6 @@ class SeaMicroPrivateMethodsTestCase(db_base.DbTestCase): self.config(action_timeout=0, group='seamicro') self.config(max_retry=2, group='seamicro') - self.patcher = mock.patch('eventlet.greenthread.sleep') - self.mock_sleep = self.patcher.start() self.info = seamicro._parse_driver_info(self.node) @mock.patch.object(seamicro_client, "Client") diff --git a/ironic/tests/drivers/test_ssh.py b/ironic/tests/drivers/test_ssh.py index f70e21309..76e023675 100644 --- a/ironic/tests/drivers/test_ssh.py +++ b/ironic/tests/drivers/test_ssh.py @@ -283,7 +283,7 @@ class SSHPrivateMethodsTestCase(db_base.DbTestCase): info) get_hosts_name_mock.assert_called_once_with(self.sshclient, info) - exec_ssh_mock.assert_not_called() + self.assertFalse(exec_ssh_mock.called) @mock.patch.object(processutils, 'ssh_execute') def test__get_power_status_exception(self, exec_ssh_mock): diff --git a/ironic/tests/drivers/test_utils.py b/ironic/tests/drivers/test_utils.py index 1ca6e90d4..cd14b464f 100644 --- a/ironic/tests/drivers/test_utils.py +++ b/ironic/tests/drivers/test_utils.py @@ -112,27 +112,6 @@ class UtilsTestCase(db_base.DbTestCase): self.assertEqual('a:b,c:d,a:b', task.node.properties['capabilities']) - def test_rm_node_capability(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.properties['capabilities'] = 'a:b' - driver_utils.rm_node_capability(task, 'a') - self.assertIsNone(task.node.properties['capabilities']) - - def test_rm_node_capability_exists(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.properties['capabilities'] = 'a:b,c:d,x:y' - self.assertIsNone(driver_utils.rm_node_capability(task, 'c')) - self.assertEqual('a:b,x:y', task.node.properties['capabilities']) - - def test_rm_node_capability_non_existent(self): - with task_manager.acquire(self.context, self.node.uuid, - shared=False) as task: - task.node.properties['capabilities'] = 'a:b' - self.assertIsNone(driver_utils.rm_node_capability(task, 'x')) - self.assertEqual('a:b', task.node.properties['capabilities']) - def test_validate_capability(self): properties = {'capabilities': 'cat:meow,cap2:value2'} self.node.properties = properties diff --git a/ironic/tests/objects/utils.py b/ironic/tests/objects/utils.py index 1eead9e8b..aa21c91fd 100644 --- a/ironic/tests/objects/utils.py +++ b/ironic/tests/objects/utils.py @@ -79,6 +79,9 @@ def get_test_chassis(ctxt, **kw): that a create() could be used to commit it to the DB. """ db_chassis = db_utils.get_test_chassis(**kw) + # Let DB generate ID if it isn't specified explicitly + if 'id' not in kw: + del db_chassis['id'] chassis = objects.Chassis(ctxt) for key in db_chassis: setattr(chassis, key, db_chassis[key]) diff --git a/ironic/tests/stubs.py b/ironic/tests/stubs.py index 7d43d2676..d20c1fd8a 100644 --- a/ironic/tests/stubs.py +++ b/ironic/tests/stubs.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -from ironic.common import exception +from glanceclient import exc as glance_exc NOW_GLANCE_FORMAT = "2010-10-11T10:30:22" @@ -40,7 +40,7 @@ class StubGlanceClient(object): index += 1 break else: - raise exception.BadRequest('Marker not found') + raise glance_exc.BadRequest('Marker not found') return self._images[index:index + limit] @@ -48,7 +48,7 @@ class StubGlanceClient(object): for image in self._images: if image.id == str(image_id): return image - raise exception.ImageNotFound(image_id) + raise glance_exc.NotFound(image_id) def data(self, image_id): self.get(image_id) @@ -76,7 +76,7 @@ class StubGlanceClient(object): for k, v in metadata.items(): setattr(self._images[i], k, v) return self._images[i] - raise exception.NotFound(image_id) + raise glance_exc.NotFound(image_id) def delete(self, image_id): for i, image in enumerate(self._images): @@ -86,10 +86,10 @@ class StubGlanceClient(object): # HTTPForbidden. image_data = self._images[i] if image_data.deleted: - raise exception.Forbidden() + raise glance_exc.Forbidden() image_data.deleted = True return - raise exception.NotFound(image_id) + raise glance_exc.NotFound(image_id) class FakeImage(object): diff --git a/ironic/tests/test_disk_partitioner.py b/ironic/tests/test_disk_partitioner.py index 00cbef5e4..941c92b3c 100644 --- a/ironic/tests/test_disk_partitioner.py +++ b/ironic/tests/test_disk_partitioner.py @@ -47,8 +47,9 @@ class DiskPartitionerTestCase(base.TestCase): self.assertThat(partitions, HasLength(3)) self.assertEqual(expected, partitions) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec') - @mock.patch.object(utils, 'execute') + @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', + autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) def test_commit(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') fake_parts = [(1, {'bootable': False, @@ -59,20 +60,22 @@ class DiskPartitionerTestCase(base.TestCase): 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1})] - with mock.patch.object(dp, 'get_partitions') as mock_gp: + with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = (None, None) dp.commit() - mock_disk_partitioner_exec.assert_called_once_with('mklabel', 'msdos', + mock_disk_partitioner_exec.assert_called_once_with( + mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') mock_utils_exc.assert_called_once_with('fuser', '/dev/fake', run_as_root=True, check_exit_code=[0, 1]) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec') - @mock.patch.object(utils, 'execute') + @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', + autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) def test_commit_with_device_is_busy_once(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') @@ -84,14 +87,15 @@ class DiskPartitionerTestCase(base.TestCase): 'fs_type': 'fake-fs-type', 'type': 'fake-type', 'size': 1})] - fuser_outputs = [("/dev/fake: 10000 10001", None), (None, None)] + fuser_outputs = iter([("/dev/fake: 10000 10001", None), (None, None)]) - with mock.patch.object(dp, 'get_partitions') as mock_gp: + with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.side_effect = fuser_outputs dp.commit() - mock_disk_partitioner_exec.assert_called_once_with('mklabel', 'msdos', + mock_disk_partitioner_exec.assert_called_once_with( + mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') @@ -99,8 +103,9 @@ class DiskPartitionerTestCase(base.TestCase): run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(2, mock_utils_exc.call_count) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec') - @mock.patch.object(utils, 'execute') + @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', + autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) def test_commit_with_device_is_always_busy(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') @@ -113,12 +118,13 @@ class DiskPartitionerTestCase(base.TestCase): 'type': 'fake-type', 'size': 1})] - with mock.patch.object(dp, 'get_partitions') as mock_gp: + with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = ("/dev/fake: 10000 10001", None) self.assertRaises(exception.InstanceDeployFailure, dp.commit) - mock_disk_partitioner_exec.assert_called_once_with('mklabel', 'msdos', + mock_disk_partitioner_exec.assert_called_once_with( + mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') @@ -126,8 +132,9 @@ class DiskPartitionerTestCase(base.TestCase): run_as_root=True, check_exit_code=[0, 1]) self.assertEqual(20, mock_utils_exc.call_count) - @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec') - @mock.patch.object(utils, 'execute') + @mock.patch.object(disk_partitioner.DiskPartitioner, '_exec', + autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) def test_commit_with_device_disconnected(self, mock_utils_exc, mock_disk_partitioner_exec): dp = disk_partitioner.DiskPartitioner('/dev/fake') @@ -140,13 +147,14 @@ class DiskPartitionerTestCase(base.TestCase): 'type': 'fake-type', 'size': 1})] - with mock.patch.object(dp, 'get_partitions') as mock_gp: + with mock.patch.object(dp, 'get_partitions', autospec=True) as mock_gp: mock_gp.return_value = fake_parts mock_utils_exc.return_value = (None, "Specified filename /dev/fake" " does not exist.") self.assertRaises(exception.InstanceDeployFailure, dp.commit) - mock_disk_partitioner_exec.assert_called_once_with('mklabel', 'msdos', + mock_disk_partitioner_exec.assert_called_once_with( + mock.ANY, 'mklabel', 'msdos', 'mkpart', 'fake-type', 'fake-fs-type', '1', '2', 'mkpart', 'fake-type', 'fake-fs-type', '2', '3', 'set', '2', 'boot', 'on') @@ -155,7 +163,7 @@ class DiskPartitionerTestCase(base.TestCase): self.assertEqual(20, mock_utils_exc.call_count) -@mock.patch.object(utils, 'execute') +@mock.patch.object(utils, 'execute', autospec=True) class ListPartitionsTestCase(base.TestCase): def test_correct(self, execute_mock): @@ -178,7 +186,7 @@ BYT; 'parted', '-s', '-m', '/dev/fake', 'unit', 'MiB', 'print', use_standard_locale=True) - @mock.patch.object(disk_partitioner.LOG, 'warn') + @mock.patch.object(disk_partitioner.LOG, 'warn', autospec=True) def test_incorrect(self, log_mock, execute_mock): output = """ BYT; diff --git a/ironic/tests/test_driver_factory.py b/ironic/tests/test_driver_factory.py index 6f5be3c09..1dee9d0de 100644 --- a/ironic/tests/test_driver_factory.py +++ b/ironic/tests/test_driver_factory.py @@ -52,7 +52,8 @@ class DriverLoadTestCase(base.TestCase): self.assertRaises(exception.DriverLoadError, driver_factory.DriverFactory._init_extension_manager) - @mock.patch.object(dispatch.NameDispatchExtensionManager, 'names') + @mock.patch.object(dispatch.NameDispatchExtensionManager, 'names', + autospec=True) def test_no_driver_load_error_if_driver_disabled(self, mock_em): self.config(enabled_drivers=[]) with mock.patch.object(dispatch.NameDispatchExtensionManager, diff --git a/ironic/tests/test_exception.py b/ironic/tests/test_exception.py new file mode 100644 index 000000000..75e8c7cfb --- /dev/null +++ b/ironic/tests/test_exception.py @@ -0,0 +1,29 @@ +# Copyright (c) 2015 IBM, Corp. +# +# 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 six + +from ironic.common import exception +from ironic.tests import base + + +class TestIronicException(base.TestCase): + def test____init__(self): + expected = '\xc3\xa9\xe0\xaf\xb2\xe0\xbe\x84' + if six.PY3: + message = chr(233) + chr(0x0bf2) + chr(3972) + else: + message = unichr(233) + unichr(0x0bf2) + unichr(3972) + exc = exception.IronicException(message) + self.assertEqual(expected, exc.__str__()) diff --git a/ironic/tests/test_glance_service.py b/ironic/tests/test_glance_service.py index dfe6d90b3..6c3276da6 100644 --- a/ironic/tests/test_glance_service.py +++ b/ironic/tests/test_glance_service.py @@ -18,9 +18,13 @@ import datetime import filecmp import os import tempfile +import time +from glanceclient import exc as glance_exc import mock +from oslo_config import cfg from oslo_context import context +from oslo_serialization import jsonutils import testtools @@ -32,8 +36,6 @@ from ironic.tests import base from ironic.tests import matchers from ironic.tests import stubs -from oslo_config import cfg -from oslo_serialization import jsonutils CONF = cfg.CONF @@ -458,7 +460,8 @@ class TestGlanceImageService(base.TestCase): self.assertEqual(self.NOW_DATETIME, image_meta['created_at']) self.assertEqual(self.NOW_DATETIME, image_meta['updated_at']) - def test_download_with_retries(self): + @mock.patch.object(time, 'sleep', autospec=True) + def test_download_with_retries(self, mock_sleep): tries = [0] class MyGlanceStubClient(stubs.StubGlanceClient): @@ -466,7 +469,7 @@ class TestGlanceImageService(base.TestCase): def get(self, image_id): if tries[0] == 0: tries[0] = 1 - raise exception.ServiceUnavailable('') + raise glance_exc.ServiceUnavailable('') else: return {} @@ -487,6 +490,7 @@ class TestGlanceImageService(base.TestCase): tries = [0] self.config(glance_num_retries=1, group='glance') stub_service.download(image_id, writer) + self.assertTrue(mock_sleep.called) def test_download_file_url(self): # NOTE: only in v2 API @@ -533,7 +537,7 @@ class TestGlanceImageService(base.TestCase): class MyGlanceStubClient(stubs.StubGlanceClient): """A client that raises a Forbidden exception.""" def get(self, image_id): - raise exception.Forbidden(image_id) + raise glance_exc.Forbidden(image_id) stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) @@ -549,7 +553,7 @@ class TestGlanceImageService(base.TestCase): class MyGlanceStubClient(stubs.StubGlanceClient): """A client that raises a HTTPForbidden exception.""" def get(self, image_id): - raise exception.HTTPForbidden(image_id) + raise glance_exc.HTTPForbidden(image_id) stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) @@ -565,7 +569,7 @@ class TestGlanceImageService(base.TestCase): class MyGlanceStubClient(stubs.StubGlanceClient): """A client that raises a NotFound exception.""" def get(self, image_id): - raise exception.NotFound(image_id) + raise glance_exc.NotFound(image_id) stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) @@ -581,7 +585,7 @@ class TestGlanceImageService(base.TestCase): class MyGlanceStubClient(stubs.StubGlanceClient): """A client that raises a HTTPNotFound exception.""" def get(self, image_id): - raise exception.HTTPNotFound(image_id) + raise glance_exc.HTTPNotFound(image_id) stub_client = MyGlanceStubClient() stub_context = context.RequestContext(auth_token=True) @@ -632,7 +636,7 @@ def _create_failing_glance_client(info): def get(self, image_id): info['num_calls'] += 1 if info['num_calls'] == 1: - raise exception.ServiceUnavailable('') + raise glance_exc.ServiceUnavailable('') return {} return MyGlanceStubClient() @@ -663,7 +667,7 @@ class TestGlanceSwiftTempURL(base.TestCase): 'id': '757274c4-2856-4bd2-bb20-9a4a231e187b' } - @mock.patch('swiftclient.utils.generate_temp_url') + @mock.patch('swiftclient.utils.generate_temp_url', autospec=True) def test_swift_temp_url(self, tempurl_mock): path = ('/v1/AUTH_a422b2-91f3-2f46-74b7-d7c9e8958f5d30' @@ -686,7 +690,7 @@ class TestGlanceSwiftTempURL(base.TestCase): key=CONF.glance.swift_temp_url_key, method='GET') - @mock.patch('swiftclient.utils.generate_temp_url') + @mock.patch('swiftclient.utils.generate_temp_url', autospec=True) def test_swift_temp_url_multiple_containers(self, tempurl_mock): self.config(swift_store_multiple_containers_seed=8, diff --git a/ironic/tests/test_hash_ring.py b/ironic/tests/test_hash_ring.py index 54a4ca0d3..8f483f774 100644 --- a/ironic/tests/test_hash_ring.py +++ b/ironic/tests/test_hash_ring.py @@ -36,7 +36,7 @@ class HashRingTestCase(base.TestCase): # fake -> foo, bar, baz # fake-again -> bar, baz, foo - @mock.patch.object(hashlib, 'md5') + @mock.patch.object(hashlib, 'md5', autospec=True) def test__hash2int_returns_int(self, mock_md5): CONF.set_override('hash_partition_exponent', 0) r1 = 32 * 'a' diff --git a/ironic/tests/test_image_service.py b/ironic/tests/test_image_service.py index c2e426f78..a5974d636 100644 --- a/ironic/tests/test_image_service.py +++ b/ironic/tests/test_image_service.py @@ -30,7 +30,7 @@ class HttpImageServiceTestCase(base.TestCase): self.service = image_service.HttpImageService() self.href = 'http://127.0.0.1:12345/fedora.qcow2' - @mock.patch.object(requests, 'head') + @mock.patch.object(requests, 'head', autospec=True) def test_validate_href(self, head_mock): response = head_mock.return_value response.status_code = 200 @@ -45,28 +45,28 @@ class HttpImageServiceTestCase(base.TestCase): self.service.validate_href, self.href) - @mock.patch.object(requests, 'head') + @mock.patch.object(requests, 'head', autospec=True) def test_validate_href_error_code(self, head_mock): head_mock.return_value.status_code = 400 self.assertRaises(exception.ImageRefValidationFailed, self.service.validate_href, self.href) head_mock.assert_called_once_with(self.href) - @mock.patch.object(requests, 'head') + @mock.patch.object(requests, 'head', autospec=True) def test_validate_href_error(self, head_mock): head_mock.side_effect = requests.ConnectionError() self.assertRaises(exception.ImageRefValidationFailed, self.service.validate_href, self.href) head_mock.assert_called_once_with(self.href) - @mock.patch.object(requests, 'head') + @mock.patch.object(requests, 'head', autospec=True) def test_show(self, head_mock): head_mock.return_value.status_code = 200 result = self.service.show(self.href) head_mock.assert_called_with(self.href) self.assertEqual({'size': 1, 'properties': {}}, result) - @mock.patch.object(requests, 'head') + @mock.patch.object(requests, 'head', autospec=True) def test_show_no_content_length(self, head_mock): head_mock.return_value.status_code = 200 head_mock.return_value.headers = {} @@ -74,8 +74,8 @@ class HttpImageServiceTestCase(base.TestCase): self.service.show, self.href) head_mock.assert_called_with(self.href) - @mock.patch.object(shutil, 'copyfileobj') - @mock.patch.object(requests, 'get') + @mock.patch.object(shutil, 'copyfileobj', autospec=True) + @mock.patch.object(requests, 'get', autospec=True) def test_download_success(self, req_get_mock, shutil_mock): response_mock = req_get_mock.return_value response_mock.status_code = 200 @@ -88,15 +88,15 @@ class HttpImageServiceTestCase(base.TestCase): ) req_get_mock.assert_called_once_with(self.href, stream=True) - @mock.patch.object(requests, 'get', + @mock.patch.object(requests, 'get', autospec=True, side_effect=requests.ConnectionError()) def test_download_fail_connerror(self, req_get_mock): file_mock = mock.Mock(spec=file) self.assertRaises(exception.ImageDownloadFailed, self.service.download, self.href, file_mock) - @mock.patch.object(shutil, 'copyfileobj') - @mock.patch.object(requests, 'get') + @mock.patch.object(shutil, 'copyfileobj', autospec=True) + @mock.patch.object(requests, 'get', autospec=True) def test_download_fail_ioerror(self, req_get_mock, shutil_mock): response_mock = req_get_mock.return_value response_mock.status_code = 200 @@ -115,31 +115,33 @@ class FileImageServiceTestCase(base.TestCase): self.href = 'file:///home/user/image.qcow2' self.href_path = '/home/user/image.qcow2' - @mock.patch.object(os.path, 'isfile', return_value=True) + @mock.patch.object(os.path, 'isfile', return_value=True, autospec=True) def test_validate_href(self, path_exists_mock): self.service.validate_href(self.href) path_exists_mock.assert_called_once_with(self.href_path) - @mock.patch.object(os.path, 'isfile', return_value=False) + @mock.patch.object(os.path, 'isfile', return_value=False, autospec=True) def test_validate_href_path_not_found_or_not_file(self, path_exists_mock): self.assertRaises(exception.ImageRefValidationFailed, self.service.validate_href, self.href) path_exists_mock.assert_called_once_with(self.href_path) - @mock.patch.object(os.path, 'getsize', return_value=42) - @mock.patch.object(image_service.FileImageService, 'validate_href') + @mock.patch.object(os.path, 'getsize', return_value=42, autospec=True) + @mock.patch.object(image_service.FileImageService, 'validate_href', + autospec=True) def test_show(self, _validate_mock, getsize_mock): _validate_mock.return_value = self.href_path result = self.service.show(self.href) getsize_mock.assert_called_once_with(self.href_path) - _validate_mock.assert_called_once_with(self.href) + _validate_mock.assert_called_once_with(mock.ANY, self.href) self.assertEqual({'size': 42, 'properties': {}}, result) - @mock.patch.object(os, 'link') - @mock.patch.object(os, 'remove') - @mock.patch.object(os, 'access', return_value=True) - @mock.patch.object(os, 'stat') - @mock.patch.object(image_service.FileImageService, 'validate_href') + @mock.patch.object(os, 'link', autospec=True) + @mock.patch.object(os, 'remove', autospec=True) + @mock.patch.object(os, 'access', return_value=True, autospec=True) + @mock.patch.object(os, 'stat', autospec=True) + @mock.patch.object(image_service.FileImageService, 'validate_href', + autospec=True) def test_download_hard_link(self, _validate_mock, stat_mock, access_mock, remove_mock, link_mock): _validate_mock.return_value = self.href_path @@ -147,18 +149,19 @@ class FileImageServiceTestCase(base.TestCase): file_mock = mock.Mock(spec=file) file_mock.name = 'file' self.service.download(self.href, file_mock) - _validate_mock.assert_called_once_with(self.href) + _validate_mock.assert_called_once_with(mock.ANY, self.href) self.assertEqual(2, stat_mock.call_count) access_mock.assert_called_once_with(self.href_path, os.R_OK | os.W_OK) remove_mock.assert_called_once_with('file') link_mock.assert_called_once_with(self.href_path, 'file') - @mock.patch.object(sendfile, 'sendfile') - @mock.patch.object(os.path, 'getsize', return_value=42) - @mock.patch.object(__builtin__, 'open') - @mock.patch.object(os, 'access', return_value=False) - @mock.patch.object(os, 'stat') - @mock.patch.object(image_service.FileImageService, 'validate_href') + @mock.patch.object(sendfile, 'sendfile', autospec=True) + @mock.patch.object(os.path, 'getsize', return_value=42, autospec=True) + @mock.patch.object(__builtin__, 'open', autospec=True) + @mock.patch.object(os, 'access', return_value=False, autospec=True) + @mock.patch.object(os, 'stat', autospec=True) + @mock.patch.object(image_service.FileImageService, 'validate_href', + autospec=True) def test_download_copy(self, _validate_mock, stat_mock, access_mock, open_mock, size_mock, copy_mock): _validate_mock.return_value = self.href_path @@ -167,7 +170,7 @@ class FileImageServiceTestCase(base.TestCase): input_mock = mock.MagicMock(spec=file) open_mock.return_value = input_mock self.service.download(self.href, file_mock) - _validate_mock.assert_called_once_with(self.href) + _validate_mock.assert_called_once_with(mock.ANY, self.href) self.assertEqual(2, stat_mock.call_count) access_mock.assert_called_once_with(self.href_path, os.R_OK | os.W_OK) copy_mock.assert_called_once_with(file_mock.fileno(), @@ -175,10 +178,11 @@ class FileImageServiceTestCase(base.TestCase): 0, 42) size_mock.assert_called_once_with(self.href_path) - @mock.patch.object(os, 'remove', side_effect=OSError) - @mock.patch.object(os, 'access', return_value=True) - @mock.patch.object(os, 'stat') - @mock.patch.object(image_service.FileImageService, 'validate_href') + @mock.patch.object(os, 'remove', side_effect=OSError, autospec=True) + @mock.patch.object(os, 'access', return_value=True, autospec=True) + @mock.patch.object(os, 'stat', autospec=True) + @mock.patch.object(image_service.FileImageService, 'validate_href', + autospec=True) def test_download_hard_link_fail(self, _validate_mock, stat_mock, access_mock, remove_mock): _validate_mock.return_value = self.href_path @@ -187,16 +191,18 @@ class FileImageServiceTestCase(base.TestCase): file_mock.name = 'file' self.assertRaises(exception.ImageDownloadFailed, self.service.download, self.href, file_mock) - _validate_mock.assert_called_once_with(self.href) + _validate_mock.assert_called_once_with(mock.ANY, self.href) self.assertEqual(2, stat_mock.call_count) access_mock.assert_called_once_with(self.href_path, os.R_OK | os.W_OK) - @mock.patch.object(sendfile, 'sendfile', side_effect=OSError) - @mock.patch.object(os.path, 'getsize', return_value=42) - @mock.patch.object(__builtin__, 'open') - @mock.patch.object(os, 'access', return_value=False) - @mock.patch.object(os, 'stat') - @mock.patch.object(image_service.FileImageService, 'validate_href') + @mock.patch.object(sendfile, 'sendfile', side_effect=OSError, + autospec=True) + @mock.patch.object(os.path, 'getsize', return_value=42, autospec=True) + @mock.patch.object(__builtin__, 'open', autospec=True) + @mock.patch.object(os, 'access', return_value=False, autospec=True) + @mock.patch.object(os, 'stat', autospec=True) + @mock.patch.object(image_service.FileImageService, 'validate_href', + autospec=True) def test_download_copy_fail(self, _validate_mock, stat_mock, access_mock, open_mock, size_mock, copy_mock): _validate_mock.return_value = self.href_path @@ -206,7 +212,7 @@ class FileImageServiceTestCase(base.TestCase): open_mock.return_value = input_mock self.assertRaises(exception.ImageDownloadFailed, self.service.download, self.href, file_mock) - _validate_mock.assert_called_once_with(self.href) + _validate_mock.assert_called_once_with(mock.ANY, self.href) self.assertEqual(2, stat_mock.call_count) access_mock.assert_called_once_with(self.href_path, os.R_OK | os.W_OK) size_mock.assert_called_once_with(self.href_path) @@ -215,35 +221,37 @@ class FileImageServiceTestCase(base.TestCase): class ServiceGetterTestCase(base.TestCase): @mock.patch.object(glance_v1_service.GlanceImageService, '__init__', - return_value=None) + return_value=None, autospec=True) def test_get_glance_image_service(self, glance_service_mock): image_href = 'image-uuid' image_service.get_image_service(image_href, context=self.context) - glance_service_mock.assert_called_once_with(None, 1, self.context) + glance_service_mock.assert_called_once_with(mock.ANY, None, 1, + self.context) @mock.patch.object(glance_v1_service.GlanceImageService, '__init__', - return_value=None) + return_value=None, autospec=True) def test_get_glance_image_service_url(self, glance_service_mock): image_href = 'glance://image-uuid' image_service.get_image_service(image_href, context=self.context) - glance_service_mock.assert_called_once_with(None, 1, self.context) + glance_service_mock.assert_called_once_with(mock.ANY, None, 1, + self.context) @mock.patch.object(image_service.HttpImageService, '__init__', - return_value=None) + return_value=None, autospec=True) def test_get_http_image_service(self, http_service_mock): image_href = 'http://127.0.0.1/image.qcow2' image_service.get_image_service(image_href) http_service_mock.assert_called_once_with() @mock.patch.object(image_service.HttpImageService, '__init__', - return_value=None) + return_value=None, autospec=True) def test_get_https_image_service(self, http_service_mock): image_href = 'https://127.0.0.1/image.qcow2' image_service.get_image_service(image_href) http_service_mock.assert_called_once_with() @mock.patch.object(image_service.FileImageService, '__init__', - return_value=None) + return_value=None, autospec=True) def test_get_file_image_service(self, local_service_mock): image_href = 'file:///home/user/image.qcow2' image_service.get_image_service(image_href) diff --git a/ironic/tests/test_images.py b/ironic/tests/test_images.py index 9ebd2d4b8..b610d4b0a 100644 --- a/ironic/tests/test_images.py +++ b/ironic/tests/test_images.py @@ -40,17 +40,18 @@ class IronicImagesTestCase(base.TestCase): class FakeImgInfo(object): pass - @mock.patch.object(imageutils, 'QemuImgInfo') - @mock.patch.object(os.path, 'exists', return_value=False) + @mock.patch.object(imageutils, 'QemuImgInfo', autospec=True) + @mock.patch.object(os.path, 'exists', return_value=False, autospec=True) def test_qemu_img_info_path_doesnt_exist(self, path_exists_mock, qemu_img_info_mock): images.qemu_img_info('noimg') path_exists_mock.assert_called_once_with('noimg') qemu_img_info_mock.assert_called_once_with() - @mock.patch.object(utils, 'execute', return_value=('out', 'err')) - @mock.patch.object(imageutils, 'QemuImgInfo') - @mock.patch.object(os.path, 'exists', return_value=True) + @mock.patch.object(utils, 'execute', return_value=('out', 'err'), + autospec=True) + @mock.patch.object(imageutils, 'QemuImgInfo', autospec=True) + @mock.patch.object(os.path, 'exists', return_value=True, autospec=True) def test_qemu_img_info_path_exists(self, path_exists_mock, qemu_img_info_mock, execute_mock): images.qemu_img_info('img') @@ -59,15 +60,15 @@ class IronicImagesTestCase(base.TestCase): 'qemu-img', 'info', 'img') qemu_img_info_mock.assert_called_once_with('out') - @mock.patch.object(utils, 'execute') + @mock.patch.object(utils, 'execute', autospec=True) def test_convert_image(self, execute_mock): images.convert_image('source', 'dest', 'out_format') execute_mock.assert_called_once_with('qemu-img', 'convert', '-O', 'out_format', 'source', 'dest', run_as_root=False) - @mock.patch.object(image_service, 'get_image_service') - @mock.patch.object(__builtin__, 'open') + @mock.patch.object(image_service, 'get_image_service', autospec=True) + @mock.patch.object(__builtin__, 'open', autospec=True) def test_fetch_no_image_service(self, open_mock, image_service_mock): mock_file_handle = mock.MagicMock(spec=file) mock_file_handle.__enter__.return_value = 'file' @@ -81,7 +82,7 @@ class IronicImagesTestCase(base.TestCase): image_service_mock.return_value.download.assert_called_once_with( 'image_href', 'file') - @mock.patch.object(__builtin__, 'open') + @mock.patch.object(__builtin__, 'open', autospec=True) def test_fetch_image_service(self, open_mock): mock_file_handle = mock.MagicMock(spec=file) mock_file_handle.__enter__.return_value = 'file' @@ -94,8 +95,8 @@ class IronicImagesTestCase(base.TestCase): image_service_mock.download.assert_called_once_with( 'image_href', 'file') - @mock.patch.object(images, 'image_to_raw') - @mock.patch.object(__builtin__, 'open') + @mock.patch.object(images, 'image_to_raw', autospec=True) + @mock.patch.object(__builtin__, 'open', autospec=True) def test_fetch_image_service_force_raw(self, open_mock, image_to_raw_mock): mock_file_handle = mock.MagicMock(spec=file) mock_file_handle.__enter__.return_value = 'file' @@ -111,7 +112,7 @@ class IronicImagesTestCase(base.TestCase): image_to_raw_mock.assert_called_once_with( 'image_href', 'path', 'path.part') - @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(images, 'qemu_img_info', autospec=True) def test_image_to_raw_no_file_format(self, qemu_img_info_mock): info = self.FakeImgInfo() info.file_format = None @@ -122,7 +123,7 @@ class IronicImagesTestCase(base.TestCase): qemu_img_info_mock.assert_called_once_with('path_tmp') self.assertIn("'qemu-img info' parsing failed.", str(e)) - @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(images, 'qemu_img_info', autospec=True) def test_image_to_raw_backing_file_present(self, qemu_img_info_mock): info = self.FakeImgInfo() info.file_format = 'raw' @@ -134,10 +135,10 @@ class IronicImagesTestCase(base.TestCase): qemu_img_info_mock.assert_called_once_with('path_tmp') self.assertIn("fmt=raw backed by: backing_file", str(e)) - @mock.patch.object(os, 'rename') - @mock.patch.object(os, 'unlink') - @mock.patch.object(images, 'convert_image') - @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(os, 'rename', autospec=True) + @mock.patch.object(os, 'unlink', autospec=True) + @mock.patch.object(images, 'convert_image', autospec=True) + @mock.patch.object(images, 'qemu_img_info', autospec=True) def test_image_to_raw(self, qemu_img_info_mock, convert_image_mock, unlink_mock, rename_mock): CONF.set_override('force_raw_images', True) @@ -159,9 +160,9 @@ class IronicImagesTestCase(base.TestCase): unlink_mock.assert_called_once_with('path_tmp') rename_mock.assert_called_once_with('path.converted', 'path') - @mock.patch.object(os, 'unlink') - @mock.patch.object(images, 'convert_image') - @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(os, 'unlink', autospec=True) + @mock.patch.object(images, 'convert_image', autospec=True) + @mock.patch.object(images, 'qemu_img_info', autospec=True) def test_image_to_raw_not_raw_after_conversion(self, qemu_img_info_mock, convert_image_mock, unlink_mock): @@ -179,8 +180,8 @@ class IronicImagesTestCase(base.TestCase): 'path.converted', 'raw') unlink_mock.assert_called_once_with('path_tmp') - @mock.patch.object(os, 'rename') - @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(os, 'rename', autospec=True) + @mock.patch.object(images, 'qemu_img_info', autospec=True) def test_image_to_raw_already_raw_format(self, qemu_img_info_mock, rename_mock): info = self.FakeImgInfo() @@ -193,7 +194,7 @@ class IronicImagesTestCase(base.TestCase): qemu_img_info_mock.assert_called_once_with('path_tmp') rename_mock.assert_called_once_with('path_tmp', 'path') - @mock.patch.object(image_service, 'get_image_service') + @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_download_size_no_image_service(self, image_service_mock): images.download_size('context', 'image_href') image_service_mock.assert_called_once_with('image_href', @@ -206,7 +207,7 @@ class IronicImagesTestCase(base.TestCase): images.download_size('context', 'image_href', image_service_mock) image_service_mock.show.assert_called_once_with('image_href') - @mock.patch.object(images, 'qemu_img_info') + @mock.patch.object(images, 'qemu_img_info', autospec=True) def test_converted_size(self, qemu_img_info_mock): info = self.FakeImgInfo() info.virtual_size = 1 @@ -215,8 +216,8 @@ class IronicImagesTestCase(base.TestCase): qemu_img_info_mock.assert_called_once_with('path') self.assertEqual(1, size) - @mock.patch.object(images, 'get_image_properties') - @mock.patch.object(glance_utils, 'is_glance_image') + @mock.patch.object(images, 'get_image_properties', autospec=True) + @mock.patch.object(glance_utils, 'is_glance_image', autospec=True) def test_is_whole_disk_image_no_img_src(self, mock_igi, mock_gip): instance_info = {'image_source': ''} iwdi = images.is_whole_disk_image('context', instance_info) @@ -224,8 +225,8 @@ class IronicImagesTestCase(base.TestCase): self.assertFalse(mock_igi.called) self.assertFalse(mock_gip.called) - @mock.patch.object(images, 'get_image_properties') - @mock.patch.object(glance_utils, 'is_glance_image') + @mock.patch.object(images, 'get_image_properties', autospec=True) + @mock.patch.object(glance_utils, 'is_glance_image', autospec=True) def test_is_whole_disk_image_partition_image(self, mock_igi, mock_gip): mock_igi.return_value = True mock_gip.return_value = {'kernel_id': 'kernel', @@ -238,8 +239,8 @@ class IronicImagesTestCase(base.TestCase): mock_igi.assert_called_once_with(image_source) mock_gip.assert_called_once_with('context', image_source) - @mock.patch.object(images, 'get_image_properties') - @mock.patch.object(glance_utils, 'is_glance_image') + @mock.patch.object(images, 'get_image_properties', autospec=True) + @mock.patch.object(glance_utils, 'is_glance_image', autospec=True) def test_is_whole_disk_image_whole_disk_image(self, mock_igi, mock_gip): mock_igi.return_value = True mock_gip.return_value = {} @@ -251,8 +252,8 @@ class IronicImagesTestCase(base.TestCase): mock_igi.assert_called_once_with(image_source) mock_gip.assert_called_once_with('context', image_source) - @mock.patch.object(images, 'get_image_properties') - @mock.patch.object(glance_utils, 'is_glance_image') + @mock.patch.object(images, 'get_image_properties', autospec=True) + @mock.patch.object(glance_utils, 'is_glance_image', autospec=True) def test_is_whole_disk_image_partition_non_glance(self, mock_igi, mock_gip): mock_igi.return_value = False @@ -265,8 +266,8 @@ class IronicImagesTestCase(base.TestCase): self.assertFalse(mock_gip.called) mock_igi.assert_called_once_with(instance_info['image_source']) - @mock.patch.object(images, 'get_image_properties') - @mock.patch.object(glance_utils, 'is_glance_image') + @mock.patch.object(images, 'get_image_properties', autospec=True) + @mock.patch.object(glance_utils, 'is_glance_image', autospec=True) def test_is_whole_disk_image_whole_disk_non_glance(self, mock_igi, mock_gip): mock_igi.return_value = False @@ -280,10 +281,10 @@ class IronicImagesTestCase(base.TestCase): class FsImageTestCase(base.TestCase): - @mock.patch.object(shutil, 'copyfile') - @mock.patch.object(os, 'makedirs') - @mock.patch.object(os.path, 'dirname') - @mock.patch.object(os.path, 'exists') + @mock.patch.object(shutil, 'copyfile', autospec=True) + @mock.patch.object(os, 'makedirs', autospec=True) + @mock.patch.object(os.path, 'dirname', autospec=True) + @mock.patch.object(os.path, 'exists', autospec=True) def test__create_root_fs(self, path_exists_mock, dirname_mock, mkdir_mock, cp_mock): @@ -295,8 +296,8 @@ class FsImageTestCase(base.TestCase): 'a3': 'sub_dir/b3'} path_exists_mock.side_effect = path_exists_mock_func - dirname_mock.side_effect = ['root_dir', 'root_dir', - 'root_dir/sub_dir', 'root_dir/sub_dir'] + dirname_mock.side_effect = iter( + ['root_dir', 'root_dir', 'root_dir/sub_dir', 'root_dir/sub_dir']) images._create_root_fs('root_dir', files_info) cp_mock.assert_any_call('a1', 'root_dir/b1') cp_mock.assert_any_call('a2', 'root_dir/b2') @@ -308,13 +309,13 @@ class FsImageTestCase(base.TestCase): dirname_mock.assert_any_call('root_dir/sub_dir/b3') mkdir_mock.assert_called_once_with('root_dir/sub_dir') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'write_to_file') - @mock.patch.object(utils, 'dd') - @mock.patch.object(utils, 'umount') - @mock.patch.object(utils, 'mount') - @mock.patch.object(utils, 'mkfs') + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'write_to_file', autospec=True) + @mock.patch.object(utils, 'dd', autospec=True) + @mock.patch.object(utils, 'umount', autospec=True) + @mock.patch.object(utils, 'mount', autospec=True) + @mock.patch.object(utils, 'mkfs', autospec=True) def test_create_vfat_image(self, mkfs_mock, mount_mock, umount_mock, dd_mock, write_mock, tempdir_mock, create_root_fs_mock): @@ -343,12 +344,12 @@ class FsImageTestCase(base.TestCase): create_root_fs_mock.assert_called_once_with('tempdir', files_info) umount_mock.assert_called_once_with('tempdir') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'dd') - @mock.patch.object(utils, 'umount') - @mock.patch.object(utils, 'mount') - @mock.patch.object(utils, 'mkfs') + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'dd', autospec=True) + @mock.patch.object(utils, 'umount', autospec=True) + @mock.patch.object(utils, 'mount', autospec=True) + @mock.patch.object(utils, 'mkfs', autospec=True) def test_create_vfat_image_always_umount(self, mkfs_mock, mount_mock, umount_mock, dd_mock, tempdir_mock, create_root_fs_mock): @@ -363,16 +364,16 @@ class FsImageTestCase(base.TestCase): umount_mock.assert_called_once_with('tempdir') - @mock.patch.object(utils, 'dd') + @mock.patch.object(utils, 'dd', autospec=True) def test_create_vfat_image_dd_fails(self, dd_mock): dd_mock.side_effect = processutils.ProcessExecutionError self.assertRaises(exception.ImageCreationFailed, images.create_vfat_image, 'tgt_file') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'dd') - @mock.patch.object(utils, 'mkfs') + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'dd', autospec=True) + @mock.patch.object(utils, 'mkfs', autospec=True) def test_create_vfat_image_mkfs_fails(self, mkfs_mock, dd_mock, tempdir_mock): @@ -384,12 +385,12 @@ class FsImageTestCase(base.TestCase): self.assertRaises(exception.ImageCreationFailed, images.create_vfat_image, 'tgt_file') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'dd') - @mock.patch.object(utils, 'umount') - @mock.patch.object(utils, 'mount') - @mock.patch.object(utils, 'mkfs') + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'dd', autospec=True) + @mock.patch.object(utils, 'umount', autospec=True) + @mock.patch.object(utils, 'mount', autospec=True) + @mock.patch.object(utils, 'mkfs', autospec=True) def test_create_vfat_image_umount_fails(self, mkfs_mock, mount_mock, umount_mock, dd_mock, tempdir_mock, create_root_fs_mock): @@ -401,7 +402,7 @@ class FsImageTestCase(base.TestCase): self.assertRaises(exception.ImageCreationFailed, images.create_vfat_image, 'tgt_file') - @mock.patch.object(utils, 'umount') + @mock.patch.object(utils, 'umount', autospec=True) def test__umount_without_raise(self, umount_mock): umount_mock.side_effect = processutils.ProcessExecutionError @@ -423,12 +424,15 @@ class FsImageTestCase(base.TestCase): self.assertEqual(expected_cfg, cfg) def test__generate_grub_cfg(self): - kernel_params = ['key1=value1', 'key2'] options = {'linux': '/vmlinuz', 'initrd': '/initrd'} - expected_cfg = ("menuentry \"install\" {\n" - "linux /vmlinuz key1=value1 key2 --\n" - "initrd /initrd\n" + expected_cfg = ("set default=0\n" + "set timeout=5\n" + "set hidden_timeout_quiet=false\n" + "\n" + "menuentry \"boot_partition\" {\n" + "linuxefi /vmlinuz key1=value1 key2 --\n" + "initrdefi /initrd\n" "}") cfg = images._generate_cfg(kernel_params, @@ -436,34 +440,34 @@ class FsImageTestCase(base.TestCase): options) self.assertEqual(expected_cfg, cfg) - @mock.patch.object(os.path, 'relpath') - @mock.patch.object(os, 'walk') - @mock.patch.object(utils, 'mount') + @mock.patch.object(os.path, 'relpath', autospec=True) + @mock.patch.object(os, 'walk', autospec=True) + @mock.patch.object(utils, 'mount', autospec=True) def test__mount_deploy_iso(self, mount_mock, walk_mock, relpath_mock): walk_mock.return_value = [('/tmpdir1/EFI/ubuntu', [], ['grub.cfg']), ('/tmpdir1/isolinux', [], ['efiboot.img', 'isolinux.bin', 'isolinux.cfg'])] - relpath_mock.side_effect = ['EFI/ubuntu/grub.cfg', - 'isolinux/efiboot.img'] + relpath_mock.side_effect = iter( + ['EFI/ubuntu/grub.cfg', 'isolinux/efiboot.img']) images._mount_deploy_iso('path/to/deployiso', 'tmpdir1') mount_mock.assert_called_once_with('path/to/deployiso', 'tmpdir1', '-o', 'loop') walk_mock.assert_called_once_with('tmpdir1') - @mock.patch.object(images, '_umount_without_raise') - @mock.patch.object(os.path, 'relpath') - @mock.patch.object(os, 'walk') - @mock.patch.object(utils, 'mount') + @mock.patch.object(images, '_umount_without_raise', autospec=True) + @mock.patch.object(os.path, 'relpath', autospec=True) + @mock.patch.object(os, 'walk', autospec=True) + @mock.patch.object(utils, 'mount', autospec=True) def test__mount_deploy_iso_fail_no_efibootimg(self, mount_mock, walk_mock, relpath_mock, umount_mock): walk_mock.return_value = [('/tmpdir1/EFI/ubuntu', [], ['grub.cfg']), ('/tmpdir1/isolinux', [], ['isolinux.bin', 'isolinux.cfg'])] - relpath_mock.side_effect = ['EFI/ubuntu/grub.cfg'] + relpath_mock.side_effect = iter(['EFI/ubuntu/grub.cfg']) self.assertRaises(exception.ImageCreationFailed, images._mount_deploy_iso, @@ -473,10 +477,10 @@ class FsImageTestCase(base.TestCase): walk_mock.assert_called_once_with('tmpdir1') umount_mock.assert_called_once_with('tmpdir1') - @mock.patch.object(images, '_umount_without_raise') - @mock.patch.object(os.path, 'relpath') - @mock.patch.object(os, 'walk') - @mock.patch.object(utils, 'mount') + @mock.patch.object(images, '_umount_without_raise', autospec=True) + @mock.patch.object(os.path, 'relpath', autospec=True) + @mock.patch.object(os, 'walk', autospec=True) + @mock.patch.object(utils, 'mount', autospec=True) def test__mount_deploy_iso_fails_no_grub_cfg(self, mount_mock, walk_mock, relpath_mock, umount_mock): @@ -484,7 +488,7 @@ class FsImageTestCase(base.TestCase): ('/tmpdir1/isolinux', '', ['efiboot.img', 'isolinux.bin', 'isolinux.cfg'])] - relpath_mock.side_effect = ['isolinux/efiboot.img'] + relpath_mock.side_effect = iter(['isolinux/efiboot.img']) self.assertRaises(exception.ImageCreationFailed, images._mount_deploy_iso, @@ -494,20 +498,20 @@ class FsImageTestCase(base.TestCase): walk_mock.assert_called_once_with('tmpdir1') umount_mock.assert_called_once_with('tmpdir1') - @mock.patch.object(utils, 'mount') + @mock.patch.object(utils, 'mount', autospec=True) def test__mount_deploy_iso_fail_with_ExecutionError(self, mount_mock): mount_mock.side_effect = processutils.ProcessExecutionError self.assertRaises(exception.ImageCreationFailed, images._mount_deploy_iso, 'path/to/deployiso', 'tmpdir1') - @mock.patch.object(images, '_umount_without_raise') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'write_to_file') - @mock.patch.object(utils, 'execute') - @mock.patch.object(images, '_mount_deploy_iso') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(images, '_generate_cfg') + @mock.patch.object(images, '_umount_without_raise', autospec=True) + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'write_to_file', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(images, '_mount_deploy_iso', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(images, '_generate_cfg', autospec=True) def test_create_isolinux_image_for_uefi(self, gen_cfg_mock, tempdir_mock, mount_mock, execute_mock, write_to_file_mock, @@ -524,7 +528,7 @@ class FsImageTestCase(base.TestCase): cfg_file = 'tmpdir/isolinux/isolinux.cfg' grubcfg = "grubcfg" grub_file = 'tmpdir/relpath/to/grub.cfg' - gen_cfg_mock.side_effect = [cfg, grubcfg] + gen_cfg_mock.side_effect = iter([cfg, grubcfg]) params = ['a=b', 'c'] isolinux_options = {'kernel': '/vmlinuz', @@ -541,8 +545,8 @@ class FsImageTestCase(base.TestCase): mock_file_handle.__enter__.return_value = 'tmpdir' mock_file_handle1 = mock.MagicMock(spec=file) mock_file_handle1.__enter__.return_value = 'mountdir' - tempdir_mock.side_effect = [mock_file_handle, - mock_file_handle1] + tempdir_mock.side_effect = iter( + [mock_file_handle, mock_file_handle1]) mount_mock.return_value = (uefi_path_info, e_img_rel_path, grub_rel_path) @@ -566,11 +570,11 @@ class FsImageTestCase(base.TestCase): '-no-emul-boot', '-o', 'tgt_file', 'tmpdir') umount_mock.assert_called_once_with('mountdir') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'write_to_file') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'execute') - @mock.patch.object(images, '_generate_cfg') + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'write_to_file', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(images, '_generate_cfg', autospec=True) def test_create_isolinux_image_for_bios(self, gen_cfg_mock, execute_mock, tempdir_mock, write_to_file_mock, @@ -609,11 +613,11 @@ class FsImageTestCase(base.TestCase): '4', '-boot-info-table', '-b', 'isolinux/isolinux.bin', '-o', 'tgt_file', 'tmpdir') - @mock.patch.object(images, '_umount_without_raise') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'execute') - @mock.patch.object(os, 'walk') + @mock.patch.object(images, '_umount_without_raise', autospec=True) + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(os, 'walk', autospec=True) def test_create_isolinux_image_uefi_rootfs_fails(self, walk_mock, utils_mock, tempdir_mock, @@ -624,8 +628,8 @@ class FsImageTestCase(base.TestCase): mock_file_handle.__enter__.return_value = 'tmpdir' mock_file_handle1 = mock.MagicMock(spec=file) mock_file_handle1.__enter__.return_value = 'mountdir' - tempdir_mock.side_effect = [mock_file_handle, - mock_file_handle1] + tempdir_mock.side_effect = iter( + [mock_file_handle, mock_file_handle1]) create_root_fs_mock.side_effect = IOError self.assertRaises(exception.ImageCreationFailed, @@ -635,10 +639,10 @@ class FsImageTestCase(base.TestCase): 'path/to/ramdisk') umount_mock.assert_called_once_with('mountdir') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'execute') - @mock.patch.object(os, 'walk') + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(os, 'walk', autospec=True) def test_create_isolinux_image_bios_rootfs_fails(self, walk_mock, utils_mock, tempdir_mock, @@ -650,13 +654,13 @@ class FsImageTestCase(base.TestCase): 'tgt_file', 'path/to/kernel', 'path/to/ramdisk') - @mock.patch.object(images, '_umount_without_raise') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'write_to_file') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'execute') - @mock.patch.object(images, '_mount_deploy_iso') - @mock.patch.object(images, '_generate_cfg') + @mock.patch.object(images, '_umount_without_raise', autospec=True) + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'write_to_file', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(images, '_mount_deploy_iso', autospec=True) + @mock.patch.object(images, '_generate_cfg', autospec=True) def test_create_isolinux_image_mkisofs_fails(self, gen_cfg_mock, mount_mock, @@ -669,8 +673,8 @@ class FsImageTestCase(base.TestCase): mock_file_handle.__enter__.return_value = 'tmpdir' mock_file_handle1 = mock.MagicMock(spec=file) mock_file_handle1.__enter__.return_value = 'mountdir' - tempdir_mock.side_effect = [mock_file_handle, - mock_file_handle1] + tempdir_mock.side_effect = iter( + [mock_file_handle, mock_file_handle1]) mount_mock.return_value = ({'a': 'a'}, 'b', 'c') utils_mock.side_effect = processutils.ProcessExecutionError @@ -679,13 +683,13 @@ class FsImageTestCase(base.TestCase): 'tgt_file', 'path/to/deployiso', 'path/to/kernel', 'path/to/ramdisk') - umount_mock.assert_called_once_wth('mountdir') + umount_mock.assert_called_once_with('mountdir') - @mock.patch.object(images, '_create_root_fs') - @mock.patch.object(utils, 'write_to_file') - @mock.patch.object(utils, 'tempdir') - @mock.patch.object(utils, 'execute') - @mock.patch.object(images, '_generate_cfg') + @mock.patch.object(images, '_create_root_fs', autospec=True) + @mock.patch.object(utils, 'write_to_file', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) + @mock.patch.object(utils, 'execute', autospec=True) + @mock.patch.object(images, '_generate_cfg', autospec=True) def test_create_isolinux_image_bios_mkisofs_fails(self, gen_cfg_mock, utils_mock, @@ -702,9 +706,9 @@ class FsImageTestCase(base.TestCase): 'tgt_file', 'path/to/kernel', 'path/to/ramdisk') - @mock.patch.object(images, 'create_isolinux_image_for_uefi') - @mock.patch.object(images, 'fetch') - @mock.patch.object(utils, 'tempdir') + @mock.patch.object(images, 'create_isolinux_image_for_uefi', autospec=True) + @mock.patch.object(images, 'fetch', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) def test_create_boot_iso_for_uefi(self, tempdir_mock, fetch_images_mock, create_isolinux_mock): mock_file_handle = mock.MagicMock(spec=file) @@ -727,9 +731,9 @@ class FsImageTestCase(base.TestCase): 'tmpdir/deploy_iso-uuid', 'tmpdir/kernel-uuid', 'tmpdir/ramdisk-uuid', params) - @mock.patch.object(images, 'create_isolinux_image_for_bios') - @mock.patch.object(images, 'fetch') - @mock.patch.object(utils, 'tempdir') + @mock.patch.object(images, 'create_isolinux_image_for_bios', autospec=True) + @mock.patch.object(images, 'fetch', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) def test_create_boot_iso_for_bios(self, tempdir_mock, fetch_images_mock, create_isolinux_mock): mock_file_handle = mock.MagicMock(spec=file) @@ -744,8 +748,12 @@ class FsImageTestCase(base.TestCase): 'tmpdir/kernel-uuid') fetch_images_mock.assert_any_call('ctx', 'ramdisk-uuid', 'tmpdir/ramdisk-uuid') - fetch_images_mock.assert_not_called_with('ctx', 'deploy_iso-uuid', - 'tmpdir/deploy_iso-uuid') + # Note (NobodyCam): the orginal assert asserted that fetch_images + # was not called with parameters, this did not + # work, So I instead assert that there were only + # Two calls to the mock validating the above + # asserts. + self.assertEqual(2, fetch_images_mock.call_count) params = ['root=UUID=root-uuid', 'kernel-params'] create_isolinux_mock.assert_called_once_with('output_file', @@ -753,9 +761,9 @@ class FsImageTestCase(base.TestCase): 'tmpdir/ramdisk-uuid', params) - @mock.patch.object(images, 'create_isolinux_image_for_bios') - @mock.patch.object(images, 'fetch') - @mock.patch.object(utils, 'tempdir') + @mock.patch.object(images, 'create_isolinux_image_for_bios', autospec=True) + @mock.patch.object(images, 'fetch', autospec=True) + @mock.patch.object(utils, 'tempdir', autospec=True) def test_create_boot_iso_for_bios_with_no_boot_mode(self, tempdir_mock, fetch_images_mock, create_isolinux_mock): @@ -778,7 +786,7 @@ class FsImageTestCase(base.TestCase): 'tmpdir/ramdisk-uuid', params) - @mock.patch.object(image_service, 'get_image_service') + @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_get_glance_image_properties_no_such_prop(self, image_service_mock): @@ -796,7 +804,7 @@ class FsImageTestCase(base.TestCase): 'p2': 'v2', 'p3': None}, ret_val) - @mock.patch.object(image_service, 'get_image_service') + @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_get_glance_image_properties_default_all( self, image_service_mock): @@ -812,7 +820,7 @@ class FsImageTestCase(base.TestCase): self.assertEqual({'p1': 'v1', 'p2': 'v2'}, ret_val) - @mock.patch.object(image_service, 'get_image_service') + @mock.patch.object(image_service, 'get_image_service', autospec=True) def test_get_glance_image_properties_with_prop_subset( self, image_service_mock): @@ -830,7 +838,7 @@ class FsImageTestCase(base.TestCase): self.assertEqual({'p1': 'v1', 'p3': 'v3'}, ret_val) - @mock.patch.object(image_service, 'GlanceImageService') + @mock.patch.object(image_service, 'GlanceImageService', autospec=True) def test_get_temp_url_for_glance_image(self, image_service_mock): direct_url = 'swift+http://host/v1/AUTH_xx/con/obj' diff --git a/ironic/tests/test_keystone.py b/ironic/tests/test_keystone.py index b41a52f1b..7933ffe7e 100644 --- a/ironic/tests/test_keystone.py +++ b/ironic/tests/test_keystone.py @@ -46,8 +46,8 @@ class KeystoneTestCase(base.TestCase): def test_failure_authorization(self): self.assertRaises(exception.KeystoneFailure, keystone.get_service_url) - @mock.patch.object(FakeCatalog, 'url_for') - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch.object(FakeCatalog, 'url_for', autospec=True) + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_get_url(self, mock_ks, mock_uf): fake_url = 'http://127.0.0.1:6385' mock_uf.return_value = fake_url @@ -55,21 +55,21 @@ class KeystoneTestCase(base.TestCase): res = keystone.get_service_url() self.assertEqual(fake_url, res) - @mock.patch.object(FakeCatalog, 'url_for') - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch.object(FakeCatalog, 'url_for', autospec=True) + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_url_not_found(self, mock_ks, mock_uf): mock_uf.side_effect = ksexception.EndpointNotFound mock_ks.return_value = FakeClient() self.assertRaises(exception.CatalogNotFound, keystone.get_service_url) - @mock.patch.object(FakeClient, 'has_service_catalog') - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch.object(FakeClient, 'has_service_catalog', autospec=True) + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_no_catalog(self, mock_ks, mock_hsc): mock_hsc.return_value = False mock_ks.return_value = FakeClient() self.assertRaises(exception.KeystoneFailure, keystone.get_service_url) - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_unauthorized(self, mock_ks): mock_ks.side_effect = ksexception.Unauthorized self.assertRaises(exception.KeystoneUnauthorized, @@ -80,7 +80,7 @@ class KeystoneTestCase(base.TestCase): self.assertRaises(exception.KeystoneFailure, keystone.get_service_url) - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_get_service_url_versionless_v2(self, mock_ks): mock_ks.return_value = FakeClient() self.config(group='keystone_authtoken', auth_uri='http://127.0.0.1') @@ -91,7 +91,7 @@ class KeystoneTestCase(base.TestCase): region_name='fake', auth_url=expected_url) - @mock.patch('keystoneclient.v3.client.Client') + @mock.patch('keystoneclient.v3.client.Client', autospec=True) def test_get_service_url_versionless_v3(self, mock_ks): mock_ks.return_value = FakeClient() self.config(group='keystone_authtoken', auth_version='v3.0', @@ -103,7 +103,7 @@ class KeystoneTestCase(base.TestCase): region_name='fake', auth_url=expected_url) - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_get_service_url_version_override(self, mock_ks): mock_ks.return_value = FakeClient() self.config(group='keystone_authtoken', @@ -115,14 +115,14 @@ class KeystoneTestCase(base.TestCase): region_name='fake', auth_url=expected_url) - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_get_admin_auth_token(self, mock_ks): fake_client = FakeClient() fake_client.auth_token = '123456' mock_ks.return_value = fake_client self.assertEqual('123456', keystone.get_admin_auth_token()) - @mock.patch('keystoneclient.v2_0.client.Client') + @mock.patch('keystoneclient.v2_0.client.Client', autospec=True) def test_get_region_name_v2(self, mock_ks): mock_ks.return_value = FakeClient() self.config(group='keystone', region_name='fake_region') @@ -134,7 +134,7 @@ class KeystoneTestCase(base.TestCase): region_name=expected_region, auth_url=expected_url) - @mock.patch('keystoneclient.v3.client.Client') + @mock.patch('keystoneclient.v3.client.Client', autospec=True) def test_get_region_name_v3(self, mock_ks): mock_ks.return_value = FakeClient() self.config(group='keystone', region_name='fake_region') diff --git a/ironic/tests/test_pxe_utils.py b/ironic/tests/test_pxe_utils.py index f00a26116..1792d840a 100644 --- a/ironic/tests/test_pxe_utils.py +++ b/ironic/tests/test_pxe_utils.py @@ -66,6 +66,14 @@ class TestPXEUtils(db_base.DbTestCase): } self.agent_pxe_options.update(common_pxe_options) + self.ipxe_options = self.pxe_options.copy() + self.ipxe_options.update({ + 'deployment_aki_path': 'http://1.2.3.4:1234/deploy_kernel', + 'deployment_ari_path': 'http://1.2.3.4:1234/deploy_ramdisk', + 'aki_path': 'http://1.2.3.4:1234/kernel', + 'ari_path': 'http://1.2.3.4:1234/ramdisk', + }) + self.node = object_utils.create_test_node(self.context) def test__build_pxe_config(self): @@ -88,9 +96,39 @@ class TestPXEUtils(db_base.DbTestCase): self.assertEqual(unicode(expected_template), rendered_template) - @mock.patch('ironic.common.utils.create_link_without_raise') - @mock.patch('ironic.common.utils.unlink_without_raise') - @mock.patch('ironic.drivers.utils.get_node_mac_addresses') + def test__build_ipxe_config(self): + # NOTE(lucasagomes): iPXE is just an extension of the PXE driver, + # it doesn't have it's own configuration option for template. + # More info: + # http://docs.openstack.org/developer/ironic/deploy/install-guide.html + self.config( + pxe_config_template='ironic/drivers/modules/ipxe_config.template', + group='pxe' + ) + self.config(http_url='http://1.2.3.4:1234', group='pxe') + rendered_template = pxe_utils._build_pxe_config( + self.ipxe_options, CONF.pxe.pxe_config_template) + + expected_template = open( + 'ironic/tests/drivers/ipxe_config.template').read().rstrip() + + self.assertEqual(unicode(expected_template), rendered_template) + + def test__build_elilo_config(self): + pxe_opts = self.pxe_options + pxe_opts['boot_mode'] = 'uefi' + rendered_template = pxe_utils._build_pxe_config( + pxe_opts, CONF.pxe.uefi_pxe_config_template) + + expected_template = open( + 'ironic/tests/drivers/elilo_efi_pxe_config.template' + ).read().rstrip() + + self.assertEqual(unicode(expected_template), rendered_template) + + @mock.patch('ironic.common.utils.create_link_without_raise', autospec=True) + @mock.patch('ironic.common.utils.unlink_without_raise', autospec=True) + @mock.patch('ironic.drivers.utils.get_node_mac_addresses', autospec=True) def test__write_mac_pxe_configs(self, get_macs_mock, unlink_mock, create_link_mock): macs = [ @@ -106,7 +144,7 @@ class TestPXEUtils(db_base.DbTestCase): ] unlink_calls = [ mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-66'), - mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-67') + mock.call('/tftpboot/pxelinux.cfg/01-00-11-22-33-44-55-67'), ] with task_manager.acquire(self.context, self.node.uuid) as task: pxe_utils._link_mac_pxe_configs(task) @@ -114,9 +152,43 @@ class TestPXEUtils(db_base.DbTestCase): unlink_mock.assert_has_calls(unlink_calls) create_link_mock.assert_has_calls(create_link_calls) - @mock.patch('ironic.common.utils.create_link_without_raise') - @mock.patch('ironic.common.utils.unlink_without_raise') - @mock.patch('ironic.common.dhcp_factory.DHCPFactory.provider') + @mock.patch('ironic.common.utils.create_link_without_raise', autospec=True) + @mock.patch('ironic.common.utils.unlink_without_raise', autospec=True) + @mock.patch('ironic.drivers.utils.get_node_mac_addresses', autospec=True) + def test__write_mac_ipxe_configs(self, get_macs_mock, unlink_mock, + create_link_mock): + self.config(ipxe_enabled=True, group='pxe') + macs = [ + '00:11:22:33:44:55:66', + '00:11:22:33:44:55:67' + ] + get_macs_mock.return_value = macs + create_link_calls = [ + mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config', + '/httpboot/pxelinux.cfg/00-11-22-33-44-55-66'), + mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config', + '/httpboot/pxelinux.cfg/00112233445566'), + mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config', + '/httpboot/pxelinux.cfg/00-11-22-33-44-55-67'), + mock.call(u'/httpboot/1be26c0b-03f2-4d2e-ae87-c02d7f33c123/config', + '/httpboot/pxelinux.cfg/00112233445567'), + ] + unlink_calls = [ + mock.call('/httpboot/pxelinux.cfg/00-11-22-33-44-55-66'), + mock.call('/httpboot/pxelinux.cfg/00112233445566'), + mock.call('/httpboot/pxelinux.cfg/00-11-22-33-44-55-67'), + mock.call('/httpboot/pxelinux.cfg/00112233445567'), + ] + with task_manager.acquire(self.context, self.node.uuid) as task: + pxe_utils._link_mac_pxe_configs(task) + + unlink_mock.assert_has_calls(unlink_calls) + create_link_mock.assert_has_calls(create_link_calls) + + @mock.patch('ironic.common.utils.create_link_without_raise', autospec=True) + @mock.patch('ironic.common.utils.unlink_without_raise', autospec=True) + @mock.patch('ironic.common.dhcp_factory.DHCPFactory.provider', + autospec=True) def test__link_ip_address_pxe_configs(self, provider_mock, unlink_mock, create_link_mock): ip_address = '10.10.0.1' @@ -135,9 +207,9 @@ class TestPXEUtils(db_base.DbTestCase): unlink_mock.assert_called_once_with('/tftpboot/0A0A0001.conf') create_link_mock.assert_has_calls(create_link_calls) - @mock.patch('ironic.common.utils.write_to_file') - @mock.patch.object(pxe_utils, '_build_pxe_config') - @mock.patch('ironic.openstack.common.fileutils.ensure_tree') + @mock.patch('ironic.common.utils.write_to_file', autospec=True) + @mock.patch.object(pxe_utils, '_build_pxe_config', autospec=True) + @mock.patch('ironic.openstack.common.fileutils.ensure_tree', autospec=True) def test_create_pxe_config(self, ensure_tree_mock, build_mock, write_mock): build_mock.return_value = self.pxe_options @@ -150,7 +222,7 @@ class TestPXEUtils(db_base.DbTestCase): mock.call(os.path.join(CONF.pxe.tftp_root, self.node.uuid)), mock.call(os.path.join(CONF.pxe.tftp_root, 'pxelinux.cfg')) ] - ensure_tree_mock.has_calls(ensure_calls) + ensure_tree_mock.assert_has_calls(ensure_calls) pxe_cfg_file_path = pxe_utils.get_pxe_config_file_path(self.node.uuid) write_mock.assert_called_with(pxe_cfg_file_path, self.pxe_options) @@ -179,7 +251,7 @@ class TestPXEUtils(db_base.DbTestCase): self.config(ipxe_enabled=True, group='pxe') self.config(http_root='/httpboot', group='pxe') mac = '00:11:22:33:AA:BB:CC' - self.assertEqual('/httpboot/pxelinux.cfg/00112233aabbcc', + self.assertEqual('/httpboot/pxelinux.cfg/00-11-22-33-aa-bb-cc', pxe_utils._get_pxe_mac_path(mac)) def test__get_pxe_ip_address_path(self): @@ -308,6 +380,27 @@ class TestPXEUtils(db_base.DbTestCase): task.node.properties = properties pxe_utils.clean_up_pxe_config(task) - unlink_mock.assert_called_once_with('/tftpboot/0A0A0001.conf') - rmtree_mock.assert_called_once_with( + unlink_mock.assert_called_once_with('/tftpboot/0A0A0001.conf') + rmtree_mock.assert_called_once_with( + os.path.join(CONF.pxe.tftp_root, self.node.uuid)) + + @mock.patch('ironic.common.utils.rmtree_without_raise') + @mock.patch('ironic.common.utils.unlink_without_raise') + @mock.patch('ironic.common.dhcp_factory.DHCPFactory.provider') + def test_clean_up_pxe_config_uefi_instance_info(self, + provider_mock, unlink_mock, + rmtree_mock): + ip_address = '10.10.0.1' + address = "aa:aa:aa:aa:aa:aa" + object_utils.create_test_port(self.context, node_id=self.node.id, + address=address) + + provider_mock.get_ip_addresses.return_value = [ip_address] + + with task_manager.acquire(self.context, self.node.uuid) as task: + task.node.instance_info['deploy_boot_mode'] = 'uefi' + pxe_utils.clean_up_pxe_config(task) + + unlink_mock.assert_called_once_with('/tftpboot/0A0A0001.conf') + rmtree_mock.assert_called_once_with( os.path.join(CONF.pxe.tftp_root, self.node.uuid)) diff --git a/ironic/tests/test_swift.py b/ironic/tests/test_swift.py index 07af10656..9daa06ead 100644 --- a/ironic/tests/test_swift.py +++ b/ironic/tests/test_swift.py @@ -28,7 +28,7 @@ from ironic.tests import base CONF = cfg.CONF -@mock.patch.object(swift_client, 'Connection') +@mock.patch.object(swift_client, 'Connection', autospec=True) class SwiftTestCase(base.TestCase): def setUp(self): @@ -59,7 +59,7 @@ class SwiftTestCase(base.TestCase): 'auth_version': '2'} connection_mock.assert_called_once_with(**params) - @mock.patch.object(__builtin__, 'open') + @mock.patch.object(__builtin__, 'open', autospec=True) def test_create_object(self, open_mock, connection_mock): swiftapi = swift.SwiftAPI() connection_obj_mock = connection_mock.return_value @@ -77,7 +77,7 @@ class SwiftTestCase(base.TestCase): 'object', 'file-object', headers=None) self.assertEqual('object-uuid', object_uuid) - @mock.patch.object(__builtin__, 'open') + @mock.patch.object(__builtin__, 'open', autospec=True) def test_create_object_create_container_fails(self, open_mock, connection_mock): swiftapi = swift.SwiftAPI() @@ -89,7 +89,7 @@ class SwiftTestCase(base.TestCase): connection_obj_mock.put_container.assert_called_once_with('container') self.assertFalse(connection_obj_mock.put_object.called) - @mock.patch.object(__builtin__, 'open') + @mock.patch.object(__builtin__, 'open', autospec=True) def test_create_object_put_object_fails(self, open_mock, connection_mock): swiftapi = swift.SwiftAPI() mock_file_handle = mock.MagicMock(spec=file) @@ -105,7 +105,7 @@ class SwiftTestCase(base.TestCase): connection_obj_mock.put_object.assert_called_once_with('container', 'object', 'file-object', headers=None) - @mock.patch.object(swift_utils, 'generate_temp_url') + @mock.patch.object(swift_utils, 'generate_temp_url', autospec=True) def test_get_temp_url(self, gen_temp_url_mock, connection_mock): swiftapi = swift.SwiftAPI() connection_obj_mock = connection_mock.return_value diff --git a/ironic/tests/test_utils.py b/ironic/tests/test_utils.py index 044f32bee..4ea1cb2af 100644 --- a/ironic/tests/test_utils.py +++ b/ironic/tests/test_utils.py @@ -43,25 +43,25 @@ class BareMetalUtilsTestCase(base.TestCase): self.assertEqual(100, len(s)) def test_unlink(self): - with mock.patch.object(os, "unlink") as unlink_mock: + with mock.patch.object(os, "unlink", autospec=True) as unlink_mock: unlink_mock.return_value = None utils.unlink_without_raise("/fake/path") unlink_mock.assert_called_once_with("/fake/path") def test_unlink_ENOENT(self): - with mock.patch.object(os, "unlink") as unlink_mock: + with mock.patch.object(os, "unlink", autospec=True) as unlink_mock: unlink_mock.side_effect = OSError(errno.ENOENT) utils.unlink_without_raise("/fake/path") unlink_mock.assert_called_once_with("/fake/path") def test_create_link(self): - with mock.patch.object(os, "symlink") as symlink_mock: + with mock.patch.object(os, "symlink", autospec=True) as symlink_mock: symlink_mock.return_value = None utils.create_link_without_raise("/fake/source", "/fake/link") symlink_mock.assert_called_once_with("/fake/source", "/fake/link") def test_create_link_EEXIST(self): - with mock.patch.object(os, "symlink") as symlink_mock: + with mock.patch.object(os, "symlink", autospec=True) as symlink_mock: symlink_mock.side_effect = OSError(errno.EEXIST) utils.create_link_without_raise("/fake/source", "/fake/link") symlink_mock.assert_called_once_with("/fake/source", "/fake/link") @@ -163,15 +163,15 @@ grep foo os.unlink(tmpfilename) os.unlink(tmpfilename2) - @mock.patch.object(processutils, 'execute') - @mock.patch.object(os.environ, 'copy', return_value={}) + @mock.patch.object(processutils, 'execute', autospec=True) + @mock.patch.object(os.environ, 'copy', return_value={}, autospec=True) def test_execute_use_standard_locale_no_env_variables(self, env_mock, execute_mock): utils.execute('foo', use_standard_locale=True) execute_mock.assert_called_once_with('foo', env_variables={'LC_ALL': 'C'}) - @mock.patch.object(processutils, 'execute') + @mock.patch.object(processutils, 'execute', autospec=True) def test_execute_use_standard_locale_with_env_variables(self, execute_mock): utils.execute('foo', use_standard_locale=True, @@ -180,7 +180,7 @@ grep foo env_variables={'LC_ALL': 'C', 'foo': 'bar'}) - @mock.patch.object(processutils, 'execute') + @mock.patch.object(processutils, 'execute', autospec=True) def test_execute_not_use_standard_locale(self, execute_mock): utils.execute('foo', use_standard_locale=False, env_variables={'foo': 'bar'}) @@ -188,14 +188,16 @@ grep foo env_variables={'foo': 'bar'}) def test_execute_get_root_helper(self): - with mock.patch.object(processutils, 'execute') as execute_mock: + with mock.patch.object( + processutils, 'execute', autospec=True) as execute_mock: helper = utils._get_root_helper() utils.execute('foo', run_as_root=True) execute_mock.assert_called_once_with('foo', run_as_root=True, root_helper=helper) def test_execute_without_root_helper(self): - with mock.patch.object(processutils, 'execute') as execute_mock: + with mock.patch.object( + processutils, 'execute', autospec=True) as execute_mock: utils.execute('foo', run_as_root=False) execute_mock.assert_called_once_with('foo', run_as_root=False) @@ -226,7 +228,8 @@ class GenericUtilsTestCase(base.TestCase): self.assertEqual("hello", utils.sanitize_hostname(hostname)) def test_read_cached_file(self): - with mock.patch.object(os.path, "getmtime") as getmtime_mock: + with mock.patch.object( + os.path, "getmtime", autospec=True) as getmtime_mock: getmtime_mock.return_value = 1 cache_data = {"data": 1123, "mtime": 1} @@ -235,8 +238,10 @@ class GenericUtilsTestCase(base.TestCase): getmtime_mock.assert_called_once_with(mock.ANY) def test_read_modified_cached_file(self): - with mock.patch.object(os.path, "getmtime") as getmtime_mock: - with mock.patch.object(__builtin__, 'open') as open_mock: + with mock.patch.object( + os.path, "getmtime", autospec=True) as getmtime_mock: + with mock.patch.object( + __builtin__, 'open', autospec=True) as open_mock: getmtime_mock.return_value = 2 fake_contents = "lorem ipsum" fake_file = mock.Mock() @@ -342,6 +347,7 @@ class GenericUtilsTestCase(base.TestCase): self.assertFalse(utils.is_hostname_safe('-spam')) self.assertFalse(utils.is_hostname_safe('spam-')) self.assertTrue(utils.is_hostname_safe('spam-eggs')) + self.assertFalse(utils.is_hostname_safe('spam_eggs')) self.assertFalse(utils.is_hostname_safe('spam eggs')) self.assertTrue(utils.is_hostname_safe('spam.eggs')) self.assertTrue(utils.is_hostname_safe('9spam')) @@ -360,16 +366,28 @@ class GenericUtilsTestCase(base.TestCase): # Need to ensure a binary response for success or fail self.assertIsNotNone(utils.is_hostname_safe('spam')) self.assertIsNotNone(utils.is_hostname_safe('-spam')) + self.assertTrue(utils.is_hostname_safe('www.rackspace.com')) + self.assertTrue(utils.is_hostname_safe('www.rackspace.com.')) + self.assertTrue(utils.is_hostname_safe('http._sctp.www.example.com')) + self.assertTrue(utils.is_hostname_safe('mail.pets_r_us.net')) + self.assertTrue(utils.is_hostname_safe('mail-server-15.my_host.org')) + self.assertFalse(utils.is_hostname_safe('www.nothere.com_')) + self.assertFalse(utils.is_hostname_safe('www.nothere_.com')) + self.assertFalse(utils.is_hostname_safe('www..nothere.com')) + long_str = 'a' * 63 + '.' + 'b' * 63 + '.' + 'c' * 63 + '.' + 'd' * 63 + self.assertTrue(utils.is_hostname_safe(long_str)) + self.assertFalse(utils.is_hostname_safe(long_str + '.')) + self.assertFalse(utils.is_hostname_safe('a' * 255)) def test_validate_and_normalize_mac(self): mac = 'AA:BB:CC:DD:EE:FF' - with mock.patch.object(utils, 'is_valid_mac') as m_mock: + with mock.patch.object(utils, 'is_valid_mac', autospec=True) as m_mock: m_mock.return_value = True self.assertEqual(mac.lower(), utils.validate_and_normalize_mac(mac)) def test_validate_and_normalize_mac_invalid_format(self): - with mock.patch.object(utils, 'is_valid_mac') as m_mock: + with mock.patch.object(utils, 'is_valid_mac', autospec=True) as m_mock: m_mock.return_value = False self.assertRaises(exception.InvalidMAC, utils.validate_and_normalize_mac, 'invalid-mac') @@ -394,7 +412,7 @@ class GenericUtilsTestCase(base.TestCase): class MkfsTestCase(base.TestCase): - @mock.patch.object(utils, 'execute') + @mock.patch.object(utils, 'execute', autospec=True) def test_mkfs(self, execute_mock): utils.mkfs('ext4', '/my/block/dev') utils.mkfs('msdos', '/my/msdos/block/dev') @@ -411,7 +429,7 @@ class MkfsTestCase(base.TestCase): use_standard_locale=True)] self.assertEqual(expected, execute_mock.call_args_list) - @mock.patch.object(utils, 'execute') + @mock.patch.object(utils, 'execute', autospec=True) def test_mkfs_with_label(self, execute_mock): utils.mkfs('ext4', '/my/block/dev', 'ext4-vol') utils.mkfs('msdos', '/my/msdos/block/dev', 'msdos-vol') @@ -428,14 +446,14 @@ class MkfsTestCase(base.TestCase): use_standard_locale=True)] self.assertEqual(expected, execute_mock.call_args_list) - @mock.patch.object(utils, 'execute', + @mock.patch.object(utils, 'execute', autospec=True, side_effect=processutils.ProcessExecutionError( stderr=os.strerror(errno.ENOENT))) def test_mkfs_with_unsupported_fs(self, execute_mock): self.assertRaises(exception.FileSystemNotSupported, utils.mkfs, 'foo', '/my/block/dev') - @mock.patch.object(utils, 'execute', + @mock.patch.object(utils, 'execute', autospec=True, side_effect=processutils.ProcessExecutionError( stderr='fake')) def test_mkfs_with_unexpected_error(self, execute_mock): @@ -453,13 +471,13 @@ class TempFilesTestCase(base.TestCase): dirname = tempdir self.assertFalse(os.path.exists(dirname)) - @mock.patch.object(shutil, 'rmtree') - @mock.patch.object(tempfile, 'mkdtemp') + @mock.patch.object(shutil, 'rmtree', autospec=True) + @mock.patch.object(tempfile, 'mkdtemp', autospec=True) def test_tempdir_mocked(self, mkdtemp_mock, rmtree_mock): self.config(tempdir='abc') mkdtemp_mock.return_value = 'temp-dir' - kwargs = {'a': 'b'} + kwargs = {'dir': 'b'} with utils.tempdir(**kwargs) as tempdir: self.assertEqual('temp-dir', tempdir) @@ -468,9 +486,9 @@ class TempFilesTestCase(base.TestCase): mkdtemp_mock.assert_called_once_with(**kwargs) rmtree_mock.assert_called_once_with(tempdir_created) - @mock.patch.object(utils, 'LOG') - @mock.patch.object(shutil, 'rmtree') - @mock.patch.object(tempfile, 'mkdtemp') + @mock.patch.object(utils, 'LOG', autospec=True) + @mock.patch.object(shutil, 'rmtree', autospec=True) + @mock.patch.object(tempfile, 'mkdtemp', autospec=True) def test_tempdir_mocked_error_on_rmtree(self, mkdtemp_mock, rmtree_mock, log_mock): |