python utility to manage a tripleo based cloud
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1212 lines
48KB

  1. # Copyright 2015 Red Hat, Inc.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  4. # not use this file except in compliance with the License. You may obtain
  5. # a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  11. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing permissions and limitations
  13. # under the License.
  14. #
  15. import argparse
  16. import datetime
  17. import logging
  18. import mock
  19. import os.path
  20. import subprocess
  21. import tempfile
  22. from uuid import uuid4
  23. import sys
  24. from testscenarios import TestWithScenarios
  25. from unittest import TestCase
  26. import yaml
  27. from heatclient import exc as hc_exc
  28. from tripleoclient import exceptions
  29. from tripleoclient import utils
  30. class TestRunAnsiblePlaybook(TestCase):
  31. def setUp(self):
  32. self.unlink_patch = mock.patch('os.unlink')
  33. self.addCleanup(self.unlink_patch.stop)
  34. self.unlink_patch.start()
  35. self.mock_log = mock.Mock('logging.getLogger')
  36. python_version = sys.version_info[0]
  37. self.ansible_playbook_cmd = "ansible-playbook-%s" % (python_version)
  38. @mock.patch('os.path.exists', return_value=False)
  39. @mock.patch('tripleoclient.utils.run_command_and_log')
  40. def test_no_playbook(self, mock_run, mock_exists):
  41. self.assertRaises(RuntimeError,
  42. utils.run_ansible_playbook,
  43. self.mock_log,
  44. '/tmp',
  45. 'non-existing.yaml',
  46. 'localhost,'
  47. )
  48. mock_exists.assert_called_once_with('/tmp/non-existing.yaml')
  49. mock_run.assert_not_called()
  50. @mock.patch('tempfile.mkstemp', return_value=('foo', '/tmp/fooBar.cfg'))
  51. @mock.patch('os.path.exists', return_value=True)
  52. @mock.patch('tripleoclient.utils.run_command_and_log')
  53. def test_subprocess_error(self, mock_run, mock_exists, mock_mkstemp):
  54. mock_process = mock.Mock()
  55. mock_process.returncode = 1
  56. mock_process.stdout.read.side_effect = ["Error\n"]
  57. mock_run.return_value = mock_process
  58. env = os.environ.copy()
  59. env['ANSIBLE_LIBRARY'] = \
  60. ('/root/.ansible/plugins/modules:'
  61. '/usr/share/ansible/plugins/modules:'
  62. '/usr/share/openstack-tripleo-validations/library')
  63. env['ANSIBLE_LOOKUP_PLUGINS'] = \
  64. ('root/.ansible/plugins/lookup:'
  65. '/usr/share/ansible/plugins/lookup:'
  66. '/usr/share/openstack-tripleo-validations/lookup_plugins')
  67. env['ANSIBLE_CALLBACK_PLUGINS'] = \
  68. ('~/.ansible/plugins/callback:'
  69. '/usr/share/ansible/plugins/callback:'
  70. '/usr/share/openstack-tripleo-validations/callback_plugins')
  71. env['ANSIBLE_ROLES_PATH'] = \
  72. ('/root/.ansible/roles:'
  73. '/usr/share/ansible/roles:'
  74. '/etc/ansible/roles:'
  75. '/usr/share/openstack-tripleo-validations/roles')
  76. env['ANSIBLE_CONFIG'] = '/tmp/fooBar.cfg'
  77. env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
  78. env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
  79. self.assertRaises(RuntimeError,
  80. utils.run_ansible_playbook,
  81. self.mock_log,
  82. '/tmp',
  83. 'existing.yaml',
  84. 'localhost,'
  85. )
  86. mock_run.assert_called_once_with(self.mock_log,
  87. [self.ansible_playbook_cmd,
  88. '-u', 'root',
  89. '-i', 'localhost,', '-v',
  90. '-c', 'smart',
  91. '/tmp/existing.yaml'],
  92. env=env, retcode_only=False)
  93. @mock.patch('os.path.isabs')
  94. @mock.patch('os.path.exists', return_value=False)
  95. @mock.patch('tripleoclient.utils.run_command_and_log')
  96. def test_non_existing_config(self, mock_run, mock_exists, mock_isabs):
  97. self.assertRaises(RuntimeError,
  98. utils.run_ansible_playbook, self.mock_log,
  99. '/tmp', 'existing.yaml', 'localhost,',
  100. '/tmp/foo.cfg'
  101. )
  102. mock_exists.assert_called_once_with('/tmp/foo.cfg')
  103. mock_isabs.assert_called_once_with('/tmp/foo.cfg')
  104. mock_run.assert_not_called()
  105. @mock.patch('tempfile.mkstemp', return_value=('foo', '/tmp/fooBar.cfg'))
  106. @mock.patch('os.path.exists', return_value=True)
  107. @mock.patch('tripleoclient.utils.run_command_and_log')
  108. def test_run_success_default(self, mock_run, mock_exists, mock_mkstemp):
  109. mock_process = mock.Mock()
  110. mock_process.returncode = 0
  111. mock_run.return_value = mock_process
  112. retcode = utils.run_ansible_playbook(self.mock_log,
  113. '/tmp',
  114. 'existing.yaml',
  115. 'localhost,')
  116. self.assertEqual(retcode, 0)
  117. mock_exists.assert_called_once_with('/tmp/existing.yaml')
  118. env = os.environ.copy()
  119. env['ANSIBLE_LIBRARY'] = \
  120. ('/root/.ansible/plugins/modules:'
  121. '/usr/share/ansible/plugins/modules:'
  122. '/usr/share/openstack-tripleo-validations/library')
  123. env['ANSIBLE_LOOKUP_PLUGINS'] = \
  124. ('root/.ansible/plugins/lookup:'
  125. '/usr/share/ansible/plugins/lookup:'
  126. '/usr/share/openstack-tripleo-validations/lookup_plugins')
  127. env['ANSIBLE_CALLBACK_PLUGINS'] = \
  128. ('~/.ansible/plugins/callback:'
  129. '/usr/share/ansible/plugins/callback:'
  130. '/usr/share/openstack-tripleo-validations/callback_plugins')
  131. env['ANSIBLE_ROLES_PATH'] = \
  132. ('/root/.ansible/roles:'
  133. '/usr/share/ansible/roles:'
  134. '/etc/ansible/roles:'
  135. '/usr/share/openstack-tripleo-validations/roles')
  136. env['ANSIBLE_CONFIG'] = '/tmp/fooBar.cfg'
  137. env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
  138. env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
  139. mock_run.assert_called_once_with(self.mock_log,
  140. [self.ansible_playbook_cmd,
  141. '-u', 'root',
  142. '-i', 'localhost,', '-v',
  143. '-c', 'smart',
  144. '/tmp/existing.yaml'],
  145. env=env, retcode_only=False)
  146. @mock.patch('os.path.isabs')
  147. @mock.patch('os.path.exists', return_value=True)
  148. @mock.patch('tripleoclient.utils.run_command_and_log')
  149. def test_run_success_ansible_cfg(self, mock_run, mock_exists, mock_isabs):
  150. mock_process = mock.Mock()
  151. mock_process.returncode = 0
  152. mock_run.return_value = mock_process
  153. retcode = utils.run_ansible_playbook(self.mock_log, '/tmp',
  154. 'existing.yaml', 'localhost,',
  155. ansible_config='/tmp/foo.cfg')
  156. self.assertEqual(retcode, 0)
  157. mock_isabs.assert_called_once_with('/tmp/foo.cfg')
  158. exist_calls = [mock.call('/tmp/foo.cfg'),
  159. mock.call('/tmp/existing.yaml')]
  160. mock_exists.assert_has_calls(exist_calls, any_order=False)
  161. env = os.environ.copy()
  162. env['ANSIBLE_LIBRARY'] = \
  163. ('/root/.ansible/plugins/modules:'
  164. '/usr/share/ansible/plugins/modules:'
  165. '/usr/share/openstack-tripleo-validations/library')
  166. env['ANSIBLE_LOOKUP_PLUGINS'] = \
  167. ('root/.ansible/plugins/lookup:'
  168. '/usr/share/ansible/plugins/lookup:'
  169. '/usr/share/openstack-tripleo-validations/lookup_plugins')
  170. env['ANSIBLE_CALLBACK_PLUGINS'] = \
  171. ('~/.ansible/plugins/callback:'
  172. '/usr/share/ansible/plugins/callback:'
  173. '/usr/share/openstack-tripleo-validations/callback_plugins')
  174. env['ANSIBLE_ROLES_PATH'] = \
  175. ('/root/.ansible/roles:'
  176. '/usr/share/ansible/roles:'
  177. '/etc/ansible/roles:'
  178. '/usr/share/openstack-tripleo-validations/roles')
  179. env['ANSIBLE_CONFIG'] = '/tmp/foo.cfg'
  180. env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
  181. env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
  182. mock_run.assert_called_once_with(self.mock_log,
  183. [self.ansible_playbook_cmd,
  184. '-u', 'root',
  185. '-i', 'localhost,', '-v',
  186. '-c', 'smart',
  187. '/tmp/existing.yaml'],
  188. env=env, retcode_only=False)
  189. @mock.patch('tempfile.mkstemp', return_value=('foo', '/tmp/fooBar.cfg'))
  190. @mock.patch('os.path.exists', return_value=True)
  191. @mock.patch('tripleoclient.utils.run_command_and_log')
  192. def test_run_success_connection_local(self, mock_run, mock_exists,
  193. mok_mkstemp):
  194. mock_process = mock.Mock()
  195. mock_process.returncode = 0
  196. mock_run.return_value = mock_process
  197. retcode = utils.run_ansible_playbook(self.mock_log, '/tmp',
  198. 'existing.yaml',
  199. 'localhost,',
  200. connection='local')
  201. self.assertEqual(retcode, 0)
  202. mock_exists.assert_called_once_with('/tmp/existing.yaml')
  203. env = os.environ.copy()
  204. env['ANSIBLE_LIBRARY'] = \
  205. ('/root/.ansible/plugins/modules:'
  206. '/usr/share/ansible/plugins/modules:'
  207. '/usr/share/openstack-tripleo-validations/library')
  208. env['ANSIBLE_LOOKUP_PLUGINS'] = \
  209. ('root/.ansible/plugins/lookup:'
  210. '/usr/share/ansible/plugins/lookup:'
  211. '/usr/share/openstack-tripleo-validations/lookup_plugins')
  212. env['ANSIBLE_CALLBACK_PLUGINS'] = \
  213. ('~/.ansible/plugins/callback:'
  214. '/usr/share/ansible/plugins/callback:'
  215. '/usr/share/openstack-tripleo-validations/callback_plugins')
  216. env['ANSIBLE_ROLES_PATH'] = \
  217. ('/root/.ansible/roles:'
  218. '/usr/share/ansible/roles:'
  219. '/etc/ansible/roles:'
  220. '/usr/share/openstack-tripleo-validations/roles')
  221. env['ANSIBLE_CONFIG'] = '/tmp/fooBar.cfg'
  222. env['ANSIBLE_HOST_KEY_CHECKING'] = 'False'
  223. env['ANSIBLE_LOG_PATH'] = '/tmp/ansible.log'
  224. mock_run.assert_called_once_with(self.mock_log,
  225. [self.ansible_playbook_cmd,
  226. '-u', 'root',
  227. '-i', 'localhost,', '-v',
  228. '-c', 'local',
  229. '/tmp/existing.yaml'],
  230. env=env, retcode_only=False)
  231. class TestRunCommandAndLog(TestCase):
  232. def setUp(self):
  233. self.mock_logger = mock.Mock(spec=logging.Logger)
  234. self.mock_process = mock.Mock()
  235. self.mock_process.stdout.readline.side_effect = ['foo\n', 'bar\n']
  236. self.mock_process.wait.side_effect = [0]
  237. self.mock_process.returncode = 0
  238. mock_sub = mock.patch('subprocess.Popen',
  239. return_value=self.mock_process)
  240. self.mock_popen = mock_sub.start()
  241. self.addCleanup(mock_sub.stop)
  242. self.cmd = ['exit', '0']
  243. self.e_cmd = ['exit', '1']
  244. self.log_calls = [mock.call('foo'),
  245. mock.call('bar')]
  246. def test_success_default(self):
  247. retcode = utils.run_command_and_log(self.mock_logger, self.cmd)
  248. self.mock_popen.assert_called_once_with(self.cmd,
  249. stdout=subprocess.PIPE,
  250. stderr=subprocess.STDOUT,
  251. shell=False,
  252. cwd=None, env=None)
  253. self.assertEqual(retcode, 0)
  254. self.mock_logger.warning.assert_has_calls(self.log_calls,
  255. any_order=False)
  256. @mock.patch('subprocess.Popen')
  257. def test_error_subprocess(self, mock_popen):
  258. mock_process = mock.Mock()
  259. mock_process.stdout.readline.side_effect = ['Error\n']
  260. mock_process.wait.side_effect = [1]
  261. mock_process.returncode = 1
  262. mock_popen.return_value = mock_process
  263. retcode = utils.run_command_and_log(self.mock_logger, self.e_cmd)
  264. mock_popen.assert_called_once_with(self.e_cmd, stdout=subprocess.PIPE,
  265. stderr=subprocess.STDOUT,
  266. shell=False, cwd=None,
  267. env=None)
  268. self.assertEqual(retcode, 1)
  269. self.mock_logger.warning.assert_called_once_with('Error')
  270. def test_success_env(self):
  271. test_env = os.environ.copy()
  272. retcode = utils.run_command_and_log(self.mock_logger, self.cmd,
  273. env=test_env)
  274. self.mock_popen.assert_called_once_with(self.cmd,
  275. stdout=subprocess.PIPE,
  276. stderr=subprocess.STDOUT,
  277. shell=False,
  278. cwd=None, env=test_env)
  279. self.assertEqual(retcode, 0)
  280. self.mock_logger.warning.assert_has_calls(self.log_calls,
  281. any_order=False)
  282. def test_success_cwd(self):
  283. test_cwd = '/usr/local/bin'
  284. retcode = utils.run_command_and_log(self.mock_logger, self.cmd,
  285. cwd=test_cwd)
  286. self.mock_popen.assert_called_once_with(self.cmd,
  287. stdout=subprocess.PIPE,
  288. stderr=subprocess.STDOUT,
  289. shell=False,
  290. cwd=test_cwd, env=None)
  291. self.assertEqual(retcode, 0)
  292. self.mock_logger.warning.assert_has_calls(self.log_calls,
  293. any_order=False)
  294. def test_success_no_retcode(self):
  295. run = utils.run_command_and_log(self.mock_logger, self.cmd,
  296. retcode_only=False)
  297. self.mock_popen.assert_called_once_with(self.cmd,
  298. stdout=subprocess.PIPE,
  299. stderr=subprocess.STDOUT,
  300. shell=False,
  301. cwd=None, env=None)
  302. self.assertEqual(run, self.mock_process)
  303. self.mock_logger.warning.assert_not_called()
  304. class TestWaitForStackUtil(TestCase):
  305. def setUp(self):
  306. self.mock_orchestration = mock.Mock()
  307. sleep_patch = mock.patch('time.sleep')
  308. self.addCleanup(sleep_patch.stop)
  309. sleep_patch.start()
  310. def mock_event(self, resource_name, id, resource_status_reason,
  311. resource_status, event_time):
  312. e = mock.Mock()
  313. e.resource_name = resource_name
  314. e.id = id
  315. e.resource_status_reason = resource_status_reason
  316. e.resource_status = resource_status
  317. e.event_time = event_time
  318. return e
  319. @mock.patch("heatclient.common.event_utils.get_events")
  320. def test_wait_for_stack_ready(self, mock_el):
  321. stack = mock.Mock()
  322. stack.stack_name = 'stack'
  323. stack.stack_status = "CREATE_COMPLETE"
  324. self.mock_orchestration.stacks.get.return_value = stack
  325. complete = utils.wait_for_stack_ready(self.mock_orchestration, 'stack')
  326. self.assertTrue(complete)
  327. @mock.patch("time.sleep")
  328. @mock.patch("heatclient.common.event_utils.poll_for_events")
  329. @mock.patch("tripleoclient.utils.get_stack")
  330. def test_wait_for_stack_ready_retry(self, mock_get_stack, mock_poll,
  331. mock_time):
  332. stack = mock.Mock()
  333. stack.stack_name = 'stack'
  334. stack.stack_id = 'id'
  335. stack.stack_status = "CREATE_COMPLETE"
  336. mock_get_stack.return_value = stack
  337. mock_poll.side_effect = [hc_exc.HTTPException(code=504),
  338. ("CREATE_COMPLETE", "ready retry message")]
  339. complete = utils.wait_for_stack_ready(self.mock_orchestration, 'stack')
  340. self.assertTrue(complete)
  341. @mock.patch("time.sleep")
  342. @mock.patch("heatclient.common.event_utils.poll_for_events")
  343. @mock.patch("tripleoclient.utils.get_stack")
  344. def test_wait_for_stack_ready_retry_fail(self, mock_get_stack, mock_poll,
  345. mock_time):
  346. stack = mock.Mock()
  347. stack.stack_name = 'stack'
  348. stack.stack_id = 'id'
  349. stack.stack_status = "CREATE_COMPLETE"
  350. mock_get_stack.return_value = stack
  351. mock_poll.side_effect = hc_exc.HTTPException(code=504)
  352. self.assertRaises(RuntimeError,
  353. utils.wait_for_stack_ready,
  354. self.mock_orchestration, 'stack')
  355. @mock.patch("time.sleep")
  356. @mock.patch("heatclient.common.event_utils.poll_for_events")
  357. @mock.patch("tripleoclient.utils.get_stack")
  358. def test_wait_for_stack_ready_server_fail(self, mock_get_stack, mock_poll,
  359. mock_time):
  360. stack = mock.Mock()
  361. stack.stack_name = 'stack'
  362. stack.stack_id = 'id'
  363. stack.stack_status = "CREATE_COMPLETE"
  364. mock_get_stack.return_value = stack
  365. mock_poll.side_effect = hc_exc.HTTPException(code=500)
  366. self.assertRaises(RuntimeError,
  367. utils.wait_for_stack_ready,
  368. self.mock_orchestration, 'stack')
  369. def test_wait_for_stack_ready_no_stack(self):
  370. self.mock_orchestration.stacks.get.return_value = None
  371. complete = utils.wait_for_stack_ready(self.mock_orchestration, 'stack')
  372. self.assertFalse(complete)
  373. @mock.patch("heatclient.common.event_utils.get_events")
  374. def test_wait_for_stack_ready_failed(self, mock_el):
  375. stack = mock.Mock()
  376. stack.stack_name = 'stack'
  377. stack.stack_status = "CREATE_FAILED"
  378. self.mock_orchestration.stacks.get.return_value = stack
  379. complete = utils.wait_for_stack_ready(self.mock_orchestration, 'stack')
  380. self.assertFalse(complete)
  381. @mock.patch("heatclient.common.event_utils.poll_for_events")
  382. def test_wait_for_stack_in_progress(self, mock_poll_for_events):
  383. mock_poll_for_events.return_value = ("CREATE_IN_PROGRESS", "MESSAGE")
  384. stack = mock.Mock()
  385. stack.stack_name = 'stack'
  386. stack.stack_status = 'CREATE_IN_PROGRESS'
  387. self.mock_orchestration.stacks.get.return_value = stack
  388. result = utils.wait_for_stack_ready(self.mock_orchestration, 'stack')
  389. self.assertEqual(False, result)
  390. @mock.patch('tripleoclient.utils.wait_for_provision_state')
  391. def test_set_nodes_state(self, wait_for_state_mock):
  392. wait_for_state_mock.return_value = True
  393. bm_client = mock.Mock()
  394. # One node already deployed, one in the manageable state after
  395. # introspection.
  396. nodes = [
  397. mock.Mock(uuid="ABCDEFGH", provision_state="active"),
  398. mock.Mock(uuid="IJKLMNOP", provision_state="manageable")
  399. ]
  400. skipped_states = ('active', 'available')
  401. uuids = list(utils.set_nodes_state(bm_client, nodes, 'provide',
  402. 'available', skipped_states))
  403. bm_client.node.set_provision_state.assert_has_calls([
  404. mock.call('IJKLMNOP', 'provide'),
  405. ])
  406. self.assertEqual(uuids, ['IJKLMNOP', ])
  407. def test_wait_for_provision_state(self):
  408. baremetal_client = mock.Mock()
  409. baremetal_client.node.get.return_value = mock.Mock(
  410. provision_state="available", last_error=None)
  411. utils.wait_for_provision_state(baremetal_client, 'UUID', "available")
  412. def test_wait_for_provision_state_not_found(self):
  413. baremetal_client = mock.Mock()
  414. baremetal_client.node.get.return_value = None
  415. utils.wait_for_provision_state(baremetal_client, 'UUID', "available")
  416. def test_wait_for_provision_state_timeout(self):
  417. baremetal_client = mock.Mock()
  418. baremetal_client.node.get.return_value = mock.Mock(
  419. provision_state="not what we want", last_error=None)
  420. with self.assertRaises(exceptions.Timeout):
  421. utils.wait_for_provision_state(baremetal_client, 'UUID',
  422. "available", loops=1, sleep=0.01)
  423. def test_wait_for_provision_state_fail(self):
  424. baremetal_client = mock.Mock()
  425. baremetal_client.node.get.return_value = mock.Mock(
  426. provision_state="enroll",
  427. last_error="node on fire; returning to previous state.")
  428. with self.assertRaises(exceptions.StateTransitionFailed):
  429. utils.wait_for_provision_state(baremetal_client, 'UUID',
  430. "available", loops=1, sleep=0.01)
  431. def test_check_stack_network_matches_env_files(self):
  432. stack_reg = {
  433. 'OS::TripleO::Hosts::SoftwareConfig': 'val',
  434. 'OS::TripleO::Network': 'val',
  435. 'OS::TripleO::Network::External': 'val',
  436. 'OS::TripleO::Network::ExtraConfig': 'OS::Heat::None',
  437. 'OS::TripleO::Network::InternalApi': 'val',
  438. 'OS::TripleO::Network::Port::InternalApi': 'val',
  439. 'OS::TripleO::Network::Management': 'val',
  440. 'OS::TripleO::Network::Storage': 'val',
  441. 'OS::TripleO::Network::StorageMgmt': 'val',
  442. 'OS::TripleO::Network::Tenant': 'val'
  443. }
  444. env_reg = {
  445. 'OS::TripleO::Hosts::SoftwareConfig': 'newval',
  446. 'OS::TripleO::Network': 'newval',
  447. 'OS::TripleO::Network::External': 'newval',
  448. 'OS::TripleO::Network::ExtraConfig': 'OS::Heat::None',
  449. 'OS::TripleO::Network::InternalApi': 'newval',
  450. 'OS::TripleO::Network::Management': 'newval',
  451. 'OS::TripleO::Network::Storage': 'val',
  452. 'OS::TripleO::Network::StorageMgmt': 'val',
  453. 'OS::TripleO::Network::Tenant': 'val'
  454. }
  455. mock_stack = mock.MagicMock()
  456. mock_stack.environment = mock.MagicMock()
  457. mock_stack.environment.return_value = {
  458. 'resource_registry': stack_reg
  459. }
  460. env = {
  461. 'resource_registry': env_reg
  462. }
  463. utils.check_stack_network_matches_env_files(mock_stack, env)
  464. def test_check_stack_network_matches_env_files_fail(self):
  465. stack_reg = {
  466. 'OS::TripleO::Hosts::SoftwareConfig': 'val',
  467. 'OS::TripleO::LoggingConfiguration': 'val',
  468. 'OS::TripleO::Network': 'val',
  469. 'OS::TripleO::Network::External': 'val',
  470. 'OS::TripleO::Network::ExtraConfig': 'OS::Heat::None',
  471. 'OS::TripleO::Network::InternalApi': 'val',
  472. 'OS::TripleO::Network::Port::InternalApi': 'val',
  473. 'OS::TripleO::Network::Management': 'val',
  474. 'OS::TripleO::Network::Storage': 'val',
  475. 'OS::TripleO::Network::StorageMgmt': 'val',
  476. 'OS::TripleO::Network::Tenant': 'val'
  477. }
  478. env_reg = {
  479. 'OS::TripleO::Hosts::SoftwareConfig': 'newval',
  480. 'OS::TripleO::LoggingConfiguration': 'newval',
  481. 'OS::TripleO::Network': 'newval',
  482. 'OS::TripleO::Network::InternalApi': 'newval'
  483. }
  484. mock_stack = mock.MagicMock()
  485. mock_stack.environment = mock.MagicMock()
  486. mock_stack.environment.return_value = {
  487. 'resource_registry': stack_reg
  488. }
  489. env = {
  490. 'resource_registry': env_reg
  491. }
  492. with self.assertRaises(exceptions.InvalidConfiguration):
  493. utils.check_stack_network_matches_env_files(mock_stack, env)
  494. @mock.patch('subprocess.check_call')
  495. @mock.patch('os.path.exists')
  496. def test_remove_known_hosts(self, mock_exists, mock_check_call):
  497. mock_exists.return_value = True
  498. utils.remove_known_hosts('192.168.0.1')
  499. known_hosts = os.path.expanduser("~/.ssh/known_hosts")
  500. mock_check_call.assert_called_with(
  501. ['ssh-keygen', '-R', '192.168.0.1', '-f', known_hosts])
  502. @mock.patch('subprocess.check_call')
  503. @mock.patch('os.path.exists')
  504. def test_remove_known_hosts_no_file(self, mock_exists, mock_check_call):
  505. mock_exists.return_value = False
  506. utils.remove_known_hosts('192.168.0.1')
  507. mock_check_call.assert_not_called()
  508. def test_empty_file_checksum(self):
  509. # Used a NamedTemporaryFile since it's deleted when the file is closed.
  510. with tempfile.NamedTemporaryFile() as empty_temp_file:
  511. self.assertEqual(utils.file_checksum(empty_temp_file.name),
  512. 'd41d8cd98f00b204e9800998ecf8427e')
  513. def test_non_empty_file_checksum(self):
  514. # Used a NamedTemporaryFile since it's deleted when the file is closed.
  515. with tempfile.NamedTemporaryFile() as temp_file:
  516. temp_file.write(b'foo')
  517. temp_file.flush()
  518. self.assertEqual(utils.file_checksum(temp_file.name),
  519. 'acbd18db4cc2f85cedef654fccc4a4d8')
  520. def test_shouldnt_checksum_open_special_files(self):
  521. self.assertRaises(ValueError, utils.file_checksum, '/dev/random')
  522. self.assertRaises(ValueError, utils.file_checksum, '/dev/zero')
  523. class TestEnsureRunAsNormalUser(TestCase):
  524. @mock.patch('os.geteuid')
  525. def test_ensure_run_as_normal_user(self, os_geteuid_mock):
  526. os_geteuid_mock.return_value = 1000
  527. self.assertIsNone(utils.ensure_run_as_normal_user())
  528. @mock.patch('os.geteuid')
  529. def test_ensure_run_as_normal_user_root(self, os_geteuid_mock):
  530. os_geteuid_mock.return_value = 0
  531. self.assertRaises(exceptions.RootUserExecution,
  532. utils.ensure_run_as_normal_user)
  533. class TestCreateOvercloudRC(TestCase):
  534. def test_write_overcloudrc(self):
  535. stack_name = 'teststack'
  536. tempdir = tempfile.mkdtemp()
  537. rcfile = os.path.join(tempdir, 'teststackrc')
  538. overcloudrcs = {
  539. "overcloudrc": "overcloudrc v3 is the only version",
  540. }
  541. try:
  542. utils.write_overcloudrc(stack_name, overcloudrcs,
  543. config_directory=tempdir)
  544. rc = open(rcfile, 'rt').read()
  545. self.assertIn('overcloudrc v3', rc)
  546. finally:
  547. if os.path.exists(rcfile):
  548. os.unlink(rcfile)
  549. os.rmdir(tempdir)
  550. class TestCreateTempestDeployerInput(TestCase):
  551. def test_create_tempest_deployer_input(self):
  552. with tempfile.NamedTemporaryFile() as cfgfile:
  553. filepath = cfgfile.name
  554. utils.create_tempest_deployer_input(filepath)
  555. cfg = open(filepath, 'rt').read()
  556. # Just make a simple test, to make sure it created a proper file:
  557. self.assertIn(
  558. '[volume-feature-enabled]\nbootable = true', cfg)
  559. class TestGetEndpointMap(TestCase):
  560. def test_get_endpoint_map(self):
  561. stack = mock.MagicMock()
  562. emap = {'KeystonePublic': {'uri': 'http://foo:8000/'}}
  563. stack.to_dict.return_value = {
  564. 'outputs': [{'output_key': 'EndpointMap',
  565. 'output_value': emap}]
  566. }
  567. endpoint_map = utils.get_endpoint_map(stack)
  568. self.assertEqual(endpoint_map,
  569. {'KeystonePublic': {'uri': 'http://foo:8000/'}})
  570. class TestNodeGetCapabilities(TestCase):
  571. def test_with_capabilities(self):
  572. node = mock.Mock(properties={'capabilities': 'x:y,foo:bar'})
  573. self.assertEqual({'x': 'y', 'foo': 'bar'},
  574. utils.node_get_capabilities(node))
  575. def test_no_capabilities(self):
  576. node = mock.Mock(properties={})
  577. self.assertEqual({}, utils.node_get_capabilities(node))
  578. class TestNodeAddCapabilities(TestCase):
  579. def test_add(self):
  580. bm_client = mock.Mock()
  581. node = mock.Mock(uuid='uuid1', properties={})
  582. new_caps = utils.node_add_capabilities(bm_client, node, x='y')
  583. bm_client.node.update.assert_called_once_with(
  584. 'uuid1', [{'op': 'add', 'path': '/properties/capabilities',
  585. 'value': 'x:y'}])
  586. self.assertEqual('x:y', node.properties['capabilities'])
  587. self.assertEqual({'x': 'y'}, new_caps)
  588. class FakeFlavor(object):
  589. def __init__(self, name, profile=''):
  590. self.name = name
  591. self.profile = name
  592. if profile != '':
  593. self.profile = profile
  594. def get_keys(self):
  595. return {
  596. 'capabilities:boot_option': 'local',
  597. 'capabilities:profile': self.profile
  598. }
  599. class TestAssignVerifyProfiles(TestCase):
  600. def setUp(self):
  601. super(TestAssignVerifyProfiles, self).setUp()
  602. self.bm_client = mock.Mock(spec=['node'],
  603. node=mock.Mock(spec=['list', 'update']))
  604. self.nodes = []
  605. self.bm_client.node.list.return_value = self.nodes
  606. self.flavors = {name: (FakeFlavor(name), 1)
  607. for name in ('compute', 'control')}
  608. def _get_fake_node(self, profile=None, possible_profiles=[],
  609. provision_state='available'):
  610. caps = {'%s_profile' % p: '1'
  611. for p in possible_profiles}
  612. if profile is not None:
  613. caps['profile'] = profile
  614. caps = utils.dict_to_capabilities(caps)
  615. return mock.Mock(uuid=str(uuid4()),
  616. properties={'capabilities': caps},
  617. provision_state=provision_state,
  618. spec=['uuid', 'properties', 'provision_state'])
  619. def _test(self, expected_errors, expected_warnings,
  620. assign_profiles=True, dry_run=False):
  621. errors, warnings = utils.assign_and_verify_profiles(self.bm_client,
  622. self.flavors,
  623. assign_profiles,
  624. dry_run)
  625. self.assertEqual(errors, expected_errors)
  626. self.assertEqual(warnings, expected_warnings)
  627. def test_no_matching_without_scale(self):
  628. self.flavors = {name: (object(), 0)
  629. for name in self.flavors}
  630. self.nodes[:] = [self._get_fake_node(profile='fake'),
  631. self._get_fake_node(profile='fake')]
  632. self._test(0, 0)
  633. self.assertFalse(self.bm_client.node.update.called)
  634. def test_exact_match(self):
  635. self.nodes[:] = [self._get_fake_node(profile='compute'),
  636. self._get_fake_node(profile='control')]
  637. self._test(0, 0)
  638. self.assertFalse(self.bm_client.node.update.called)
  639. def test_nodes_with_no_profiles_present(self):
  640. self.nodes[:] = [self._get_fake_node(profile='compute'),
  641. self._get_fake_node(profile=None),
  642. self._get_fake_node(profile='foobar'),
  643. self._get_fake_node(profile='control')]
  644. self._test(0, 1)
  645. self.assertFalse(self.bm_client.node.update.called)
  646. def test_more_nodes_with_profiles_present(self):
  647. self.nodes[:] = [self._get_fake_node(profile='compute'),
  648. self._get_fake_node(profile='compute'),
  649. self._get_fake_node(profile='compute'),
  650. self._get_fake_node(profile='control')]
  651. self._test(0, 1)
  652. self.assertFalse(self.bm_client.node.update.called)
  653. def test_no_nodes(self):
  654. # One error per each flavor
  655. self._test(2, 0)
  656. self.assertFalse(self.bm_client.node.update.called)
  657. def test_not_enough_nodes(self):
  658. self.nodes[:] = [self._get_fake_node(profile='compute')]
  659. self._test(1, 0)
  660. self.assertFalse(self.bm_client.node.update.called)
  661. def test_assign_profiles(self):
  662. self.nodes[:] = [self._get_fake_node(possible_profiles=['compute']),
  663. self._get_fake_node(possible_profiles=['control']),
  664. self._get_fake_node(possible_profiles=['compute'])]
  665. # one warning for a redundant node
  666. self._test(0, 1, assign_profiles=True)
  667. self.assertEqual(2, self.bm_client.node.update.call_count)
  668. actual_profiles = [utils.node_get_capabilities(node).get('profile')
  669. for node in self.nodes]
  670. actual_profiles.sort(key=lambda x: str(x))
  671. self.assertEqual([None, 'compute', 'control'], actual_profiles)
  672. def test_assign_profiles_multiple_options(self):
  673. self.nodes[:] = [self._get_fake_node(possible_profiles=['compute',
  674. 'control']),
  675. self._get_fake_node(possible_profiles=['compute',
  676. 'control'])]
  677. self._test(0, 0, assign_profiles=True)
  678. self.assertEqual(2, self.bm_client.node.update.call_count)
  679. actual_profiles = [utils.node_get_capabilities(node).get('profile')
  680. for node in self.nodes]
  681. actual_profiles.sort(key=lambda x: str(x))
  682. self.assertEqual(['compute', 'control'], actual_profiles)
  683. def test_assign_profiles_not_enough(self):
  684. self.nodes[:] = [self._get_fake_node(possible_profiles=['compute']),
  685. self._get_fake_node(possible_profiles=['compute']),
  686. self._get_fake_node(possible_profiles=['compute'])]
  687. self._test(1, 1, assign_profiles=True)
  688. # no node update for failed flavor
  689. self.assertEqual(1, self.bm_client.node.update.call_count)
  690. actual_profiles = [utils.node_get_capabilities(node).get('profile')
  691. for node in self.nodes]
  692. actual_profiles.sort(key=lambda x: str(x))
  693. self.assertEqual([None, None, 'compute'], actual_profiles)
  694. def test_assign_profiles_dry_run(self):
  695. self.nodes[:] = [self._get_fake_node(possible_profiles=['compute']),
  696. self._get_fake_node(possible_profiles=['control']),
  697. self._get_fake_node(possible_profiles=['compute'])]
  698. self._test(0, 1, dry_run=True)
  699. self.assertFalse(self.bm_client.node.update.called)
  700. actual_profiles = [utils.node_get_capabilities(node).get('profile')
  701. for node in self.nodes]
  702. self.assertEqual([None] * 3, actual_profiles)
  703. def test_scale(self):
  704. # active nodes with assigned profiles are fine
  705. self.nodes[:] = [self._get_fake_node(profile='compute',
  706. provision_state='active'),
  707. self._get_fake_node(profile='control')]
  708. self._test(0, 0, assign_profiles=True)
  709. self.assertFalse(self.bm_client.node.update.called)
  710. def test_assign_profiles_wrong_state(self):
  711. # active nodes are not considered for assigning profiles
  712. self.nodes[:] = [self._get_fake_node(possible_profiles=['compute'],
  713. provision_state='active'),
  714. self._get_fake_node(possible_profiles=['control'],
  715. provision_state='cleaning'),
  716. self._get_fake_node(profile='compute',
  717. provision_state='error')]
  718. self._test(2, 1, assign_profiles=True)
  719. self.assertFalse(self.bm_client.node.update.called)
  720. def test_no_spurious_warnings(self):
  721. self.nodes[:] = [self._get_fake_node(profile=None)]
  722. self.flavors = {'baremetal': (FakeFlavor('baremetal', None), 1)}
  723. self._test(0, 0)
  724. class TestPromptUser(TestCase):
  725. def setUp(self):
  726. super(TestPromptUser, self).setUp()
  727. self.logger = mock.MagicMock()
  728. self.logger.info = mock.MagicMock()
  729. @mock.patch('sys.stdin')
  730. def test_user_accepts(self, stdin_mock):
  731. stdin_mock.isatty.return_value = True
  732. stdin_mock.readline.return_value = "yes"
  733. result = utils.prompt_user_for_confirmation("[y/N]?", self.logger)
  734. self.assertTrue(result)
  735. @mock.patch('sys.stdin')
  736. def test_user_declines(self, stdin_mock):
  737. stdin_mock.isatty.return_value = True
  738. stdin_mock.readline.return_value = "no"
  739. result = utils.prompt_user_for_confirmation("[y/N]?", self.logger)
  740. self.assertFalse(result)
  741. @mock.patch('sys.stdin')
  742. def test_user_no_tty(self, stdin_mock):
  743. stdin_mock.isatty.return_value = False
  744. stdin_mock.readline.return_value = "yes"
  745. result = utils.prompt_user_for_confirmation("[y/N]?", self.logger)
  746. self.assertFalse(result)
  747. @mock.patch('sys.stdin')
  748. def test_user_aborts_control_c(self, stdin_mock):
  749. stdin_mock.isatty.return_value = False
  750. stdin_mock.readline.side_effect = KeyboardInterrupt()
  751. result = utils.prompt_user_for_confirmation("[y/N]?", self.logger)
  752. self.assertFalse(result)
  753. @mock.patch('sys.stdin')
  754. def test_user_aborts_with_control_d(self, stdin_mock):
  755. stdin_mock.isatty.return_value = False
  756. stdin_mock.readline.side_effect = EOFError()
  757. result = utils.prompt_user_for_confirmation("[y/N]?", self.logger)
  758. self.assertFalse(result)
  759. class TestReplaceLinks(TestCase):
  760. def setUp(self):
  761. super(TestReplaceLinks, self).setUp()
  762. self.link_replacement = {
  763. 'file:///home/stack/test.sh':
  764. 'user-files/home/stack/test.sh',
  765. 'file:///usr/share/extra-templates/my.yml':
  766. 'user-files/usr/share/extra-templates/my.yml',
  767. }
  768. def test_replace_links(self):
  769. source = (
  770. 'description: my template\n'
  771. 'heat_template_version: "2014-10-16"\n'
  772. 'resources:\n'
  773. ' test_config:\n'
  774. ' properties:\n'
  775. ' config: {get_file: "file:///home/stack/test.sh"}\n'
  776. ' type: OS::Heat::SoftwareConfig\n'
  777. )
  778. expected = (
  779. 'description: my template\n'
  780. 'heat_template_version: "2014-10-16"\n'
  781. 'resources:\n'
  782. ' test_config:\n'
  783. ' properties:\n'
  784. ' config: {get_file: user-files/home/stack/test.sh}\n'
  785. ' type: OS::Heat::SoftwareConfig\n'
  786. )
  787. # the yaml->string dumps aren't always character-precise, so
  788. # we need to parse them into dicts for comparison
  789. expected_dict = yaml.safe_load(expected)
  790. result_dict = yaml.safe_load(utils.replace_links_in_template_contents(
  791. source, self.link_replacement))
  792. self.assertEqual(expected_dict, result_dict)
  793. def test_replace_links_not_template(self):
  794. # valid JSON/YAML, but doesn't have heat_template_version
  795. source = '{"get_file": "file:///home/stack/test.sh"}'
  796. self.assertEqual(
  797. source,
  798. utils.replace_links_in_template_contents(
  799. source, self.link_replacement))
  800. def test_replace_links_not_yaml(self):
  801. # invalid JSON/YAML -- curly brace left open
  802. source = '{"invalid JSON"'
  803. self.assertEqual(
  804. source,
  805. utils.replace_links_in_template_contents(
  806. source, self.link_replacement))
  807. def test_relative_link_replacement(self):
  808. current_dir = 'user-files/home/stack'
  809. expected = {
  810. 'file:///home/stack/test.sh':
  811. 'test.sh',
  812. 'file:///usr/share/extra-templates/my.yml':
  813. '../../usr/share/extra-templates/my.yml',
  814. }
  815. self.assertEqual(expected, utils.relative_link_replacement(
  816. self.link_replacement, current_dir))
  817. class TestBracketIPV6(TestCase):
  818. def test_basic(self):
  819. result = utils.bracket_ipv6('::1')
  820. self.assertEqual('[::1]', result)
  821. def test_hostname(self):
  822. result = utils.bracket_ipv6('hostname')
  823. self.assertEqual('hostname', result)
  824. def test_already_bracketed(self):
  825. result = utils.bracket_ipv6('[::1]')
  826. self.assertEqual('[::1]', result)
  827. class TestStoreCliParam(TestCase):
  828. def setUp(self):
  829. self.args = argparse.ArgumentParser()
  830. @mock.patch('os.mkdir')
  831. @mock.patch('os.path.exists')
  832. def test_fail_to_create_file(self, mock_exists, mock_mkdir):
  833. mock_exists.return_value = False
  834. mock_mkdir.side_effect = OSError()
  835. command = "undercloud install"
  836. self.assertRaises(OSError, utils.store_cli_param, command, self.args)
  837. @mock.patch('os.path.isdir')
  838. @mock.patch('os.path.exists')
  839. def test_exists_but_not_dir(self, mock_exists, mock_isdir):
  840. mock_exists.return_value = True
  841. mock_isdir.return_value = False
  842. self.assertRaises(exceptions.InvalidConfiguration,
  843. utils.store_cli_param,
  844. "overcloud deploy", self.args)
  845. @mock.patch('os.path.isdir')
  846. @mock.patch('os.path.exists')
  847. def test_write_cli_param(self, mock_exists, mock_isdir):
  848. history_path = os.path.join(os.path.expanduser("~"), '.tripleo')
  849. mock_exists.return_value = True
  850. mock_isdir.return_value = True
  851. mock_file = mock.mock_open()
  852. class ArgsFake(object):
  853. def __init__(self):
  854. self.a = 1
  855. dt = datetime.datetime(2017, 11, 22)
  856. with mock.patch("six.moves.builtins.open", mock_file):
  857. with mock.patch('tripleoclient.utils.datetime') as mock_date:
  858. mock_date.datetime.now.return_value = dt
  859. utils.store_cli_param("overcloud plan list", ArgsFake())
  860. expected_call = [
  861. mock.call("%s/history" % history_path, 'a'),
  862. mock.call().write('2017-11-22 00:00:00 overcloud-plan-list a=1 \n')
  863. ]
  864. mock_file.assert_has_calls(expected_call, any_order=True)
  865. @mock.patch('six.moves.builtins.open')
  866. @mock.patch('os.path.isdir')
  867. @mock.patch('os.path.exists')
  868. def test_fail_to_write_data(self, mock_exists, mock_isdir, mock_open):
  869. mock_exists.return_value = True
  870. mock_isdir.return_value = True
  871. mock_open.side_effect = IOError()
  872. self.assertRaises(IOError, utils.store_cli_param, "command", self.args)
  873. class GetTripleoAnsibleInventory(TestCase):
  874. def setUp(self):
  875. super(GetTripleoAnsibleInventory, self).setUp()
  876. self.inventory_file = ''
  877. self.ssh_user = 'heat_admin'
  878. self.stack = 'foo-overcloud'
  879. @mock.patch('tripleoclient.utils.get_tripleo_ansible_inventory',
  880. autospec=True)
  881. def test_get_tripleo_ansible_inventory(self, mock_inventory):
  882. with mock.patch('os.path.exists') as mock_exists:
  883. mock_exists.return_value = True
  884. self.cmd = utils.get_tripleo_ansible_inventory(
  885. inventory_file=self.inventory_file,
  886. ssh_user=self.ssh_user,
  887. stack=self.stack)
  888. self.cmd.take_action()
  889. mock_inventory.assert_called_once_with(
  890. inventory_file='',
  891. ssh_user='heat_admin',
  892. stack='foo-overcloud'
  893. )
  894. class TestOvercloudNameScenarios(TestWithScenarios):
  895. scenarios = [
  896. ('kernel_default',
  897. dict(func=utils.overcloud_kernel,
  898. basename='overcloud-full',
  899. expected=('overcloud-full-vmlinuz', '.vmlinuz'))),
  900. ('kernel_arch',
  901. dict(func=utils.overcloud_kernel,
  902. basename='overcloud-full',
  903. arch='x86_64',
  904. expected=('x86_64-overcloud-full-vmlinuz', '.vmlinuz'))),
  905. ('kernel_arch_platform',
  906. dict(func=utils.overcloud_kernel,
  907. basename='overcloud-full',
  908. arch='x86_64',
  909. platform='SNB',
  910. expected=('SNB-x86_64-overcloud-full-vmlinuz', '.vmlinuz'))),
  911. ('kernel_platform',
  912. dict(func=utils.overcloud_kernel,
  913. basename='overcloud-full',
  914. platform='SNB',
  915. expected=('overcloud-full-vmlinuz', '.vmlinuz'))),
  916. ('ramdisk_default',
  917. dict(func=utils.overcloud_ramdisk,
  918. basename='overcloud-full',
  919. expected=('overcloud-full-initrd', '.initrd'))),
  920. ('ramdisk_arch',
  921. dict(func=utils.overcloud_ramdisk,
  922. basename='overcloud-full',
  923. arch='x86_64',
  924. expected=('x86_64-overcloud-full-initrd', '.initrd'))),
  925. ('ramdisk_arch_platform',
  926. dict(func=utils.overcloud_ramdisk,
  927. basename='overcloud-full',
  928. arch='x86_64',
  929. platform='SNB',
  930. expected=('SNB-x86_64-overcloud-full-initrd', '.initrd'))),
  931. ('ramdisk_platform',
  932. dict(func=utils.overcloud_ramdisk,
  933. basename='overcloud-full',
  934. platform='SNB',
  935. expected=('overcloud-full-initrd', '.initrd'))),
  936. ('image_default',
  937. dict(func=utils.overcloud_image,
  938. basename='overcloud-full',
  939. expected=('overcloud-full', '.qcow2'))),
  940. ('image_arch',
  941. dict(func=utils.overcloud_image,
  942. basename='overcloud-full',
  943. arch='x86_64',
  944. expected=('x86_64-overcloud-full', '.qcow2'))),
  945. ('image_arch_platform',
  946. dict(func=utils.overcloud_image,
  947. basename='overcloud-full',
  948. arch='x86_64',
  949. platform='SNB',
  950. expected=('SNB-x86_64-overcloud-full', '.qcow2'))),
  951. ('image_platform',
  952. dict(func=utils.overcloud_image,
  953. basename='overcloud-full',
  954. platform='SNB',
  955. expected=('overcloud-full', '.qcow2'))),
  956. ]
  957. def test_overcloud_params(self):
  958. kwargs = dict()
  959. for attr in ['arch', 'platform']:
  960. if hasattr(self, attr):
  961. kwargs[attr] = getattr(self, attr)
  962. if kwargs:
  963. observed = self.func(self.basename, **kwargs)
  964. else:
  965. observed = self.func(self.basename)
  966. self.assertEqual(self.expected, observed)
  967. class TestDeployNameScenarios(TestWithScenarios):
  968. scenarios = [
  969. ('kernel_default',
  970. dict(func=utils.deploy_kernel,
  971. expected=('bm-deploy-kernel', '.kernel'))),
  972. ('kernel_arch',
  973. dict(func=utils.deploy_kernel,
  974. arch='x86_64',
  975. expected=('x86_64-bm-deploy-kernel', '.kernel'))),
  976. ('kernel_arch_platform',
  977. dict(func=utils.deploy_kernel,
  978. arch='x86_64',
  979. platform='SNB',
  980. expected=('SNB-x86_64-bm-deploy-kernel', '.kernel'))),
  981. ('kernel_platform',
  982. dict(func=utils.deploy_kernel,
  983. platform='SNB',
  984. expected=('bm-deploy-kernel', '.kernel'))),
  985. ('ramdisk_default',
  986. dict(func=utils.deploy_ramdisk,
  987. expected=('bm-deploy-ramdisk', '.initramfs'))),
  988. ('ramdisk_arch',
  989. dict(func=utils.deploy_ramdisk,
  990. arch='x86_64',
  991. expected=('x86_64-bm-deploy-ramdisk', '.initramfs'))),
  992. ('ramdisk_arch_platform',
  993. dict(func=utils.deploy_ramdisk,
  994. arch='x86_64',
  995. platform='SNB',
  996. expected=('SNB-x86_64-bm-deploy-ramdisk', '.initramfs'))),
  997. ('ramdisk_platform',
  998. dict(func=utils.deploy_ramdisk,
  999. platform='SNB',
  1000. expected=('bm-deploy-ramdisk', '.initramfs'))),
  1001. ]
  1002. def test_deploy_params(self):
  1003. kwargs = {}
  1004. for attr in ['arch', 'platform']:
  1005. if hasattr(self, attr):
  1006. kwargs[attr] = getattr(self, attr)
  1007. if kwargs:
  1008. observed = self.func(**kwargs)
  1009. else:
  1010. observed = self.func()
  1011. self.assertEqual(self.expected, observed)