A utility to run diskimage-builder undercloud elements on a running host
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.

1794 lines
85KB

  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. import collections
  15. import io
  16. import json
  17. import os
  18. import subprocess
  19. import tempfile
  20. import time
  21. import fixtures
  22. from keystoneauth1 import exceptions as ks_exceptions
  23. import mock
  24. from novaclient import exceptions
  25. from oslo_config import cfg
  26. from oslo_config import fixture as config_fixture
  27. from oslotest import base
  28. from oslotest import log
  29. from six.moves import configparser
  30. from instack_undercloud import undercloud
  31. from instack_undercloud import validator
  32. undercloud._configure_logging(undercloud.DEFAULT_LOG_LEVEL, None)
  33. class BaseTestCase(base.BaseTestCase):
  34. def setUp(self):
  35. super(BaseTestCase, self).setUp()
  36. self.logger = self.useFixture(log.ConfigureLogging()).logger
  37. self.conf = self.useFixture(config_fixture.Config())
  38. self.conf.config(enable_routed_networks=True)
  39. # ctlplane-subnet - config group options
  40. self.grp0 = cfg.OptGroup(name='ctlplane-subnet',
  41. title='ctlplane-subnet')
  42. self.opts = [cfg.StrOpt('cidr'),
  43. cfg.StrOpt('dhcp_start'),
  44. cfg.StrOpt('dhcp_end'),
  45. cfg.StrOpt('inspection_iprange'),
  46. cfg.StrOpt('gateway'),
  47. cfg.BoolOpt('masquerade')]
  48. self.conf.register_opts(self.opts, group=self.grp0)
  49. self.grp1 = cfg.OptGroup(name='subnet1', title='subnet1')
  50. self.gtp2 = cfg.OptGroup(name='subnet2', title='subnet2')
  51. self.conf.config(cidr='192.168.24.0/24',
  52. dhcp_start='192.168.24.5', dhcp_end='192.168.24.24',
  53. inspection_iprange='192.168.24.100,192.168.24.120',
  54. gateway='192.168.24.1', masquerade=True,
  55. group='ctlplane-subnet')
  56. class TestUndercloud(BaseTestCase):
  57. @mock.patch(
  58. 'instack_undercloud.undercloud._load_subnets_config_groups')
  59. @mock.patch('instack_undercloud.undercloud._handle_upgrade_fact')
  60. @mock.patch('instack_undercloud.undercloud._configure_logging')
  61. @mock.patch('instack_undercloud.undercloud._validate_configuration')
  62. @mock.patch('instack_undercloud.undercloud._run_command')
  63. @mock.patch('instack_undercloud.undercloud._post_config')
  64. @mock.patch('instack_undercloud.undercloud._run_orc')
  65. @mock.patch('instack_undercloud.undercloud._run_yum_update')
  66. @mock.patch('instack_undercloud.undercloud._run_yum_clean_all')
  67. @mock.patch('instack_undercloud.undercloud._run_instack')
  68. @mock.patch('instack_undercloud.undercloud._generate_environment')
  69. @mock.patch('instack_undercloud.undercloud._load_config')
  70. @mock.patch('instack_undercloud.undercloud._die_tuskar_die')
  71. @mock.patch('instack_undercloud.undercloud._run_validation_groups')
  72. def test_install(self, mock_run_validation_groups,
  73. mock_die_tuskar_die, mock_load_config,
  74. mock_generate_environment, mock_run_instack,
  75. mock_run_clean_all, mock_run_yum_update, mock_run_orc,
  76. mock_post_config, mock_run_command,
  77. mock_validate_configuration, mock_configure_logging,
  78. mock_upgrade_fact, mock_load_subnets_config_groups):
  79. fake_env = mock.MagicMock()
  80. mock_generate_environment.return_value = fake_env
  81. undercloud.install('.')
  82. self.assertTrue(mock_validate_configuration.called)
  83. mock_generate_environment.assert_called_with('.')
  84. mock_run_instack.assert_called_with(fake_env)
  85. mock_run_orc.assert_called_with(fake_env)
  86. mock_run_command.assert_called_with(
  87. ['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm')
  88. mock_upgrade_fact.assert_called_with(False)
  89. mock_die_tuskar_die.assert_not_called()
  90. mock_run_validation_groups.assert_not_called()
  91. @mock.patch(
  92. 'instack_undercloud.undercloud._load_subnets_config_groups')
  93. @mock.patch('instack_undercloud.undercloud._handle_upgrade_fact')
  94. @mock.patch('instack_undercloud.undercloud._configure_logging')
  95. @mock.patch('instack_undercloud.undercloud._validate_configuration')
  96. @mock.patch('instack_undercloud.undercloud._run_command')
  97. @mock.patch('instack_undercloud.undercloud._post_config')
  98. @mock.patch('instack_undercloud.undercloud._run_orc')
  99. @mock.patch('instack_undercloud.undercloud._run_yum_update')
  100. @mock.patch('instack_undercloud.undercloud._run_yum_clean_all')
  101. @mock.patch('instack_undercloud.undercloud._run_instack')
  102. @mock.patch('instack_undercloud.undercloud._generate_environment')
  103. @mock.patch('instack_undercloud.undercloud._load_config')
  104. @mock.patch('instack_undercloud.undercloud._die_tuskar_die')
  105. @mock.patch('instack_undercloud.undercloud._run_validation_groups')
  106. def test_install_upgrade(self, mock_run_validation_groups,
  107. mock_die_tuskar_die, mock_load_config,
  108. mock_generate_environment, mock_run_instack,
  109. mock_run_yum_clean_all, mock_run_yum_update,
  110. mock_run_orc, mock_post_config, mock_run_command,
  111. mock_validate_configuration,
  112. mock_configure_logging, mock_upgrade_fact,
  113. mock_load_subnets_config_groups):
  114. fake_env = mock.MagicMock()
  115. mock_generate_environment.return_value = fake_env
  116. undercloud.install('.', upgrade=True)
  117. self.assertTrue(mock_validate_configuration.called)
  118. mock_generate_environment.assert_called_with('.')
  119. mock_run_instack.assert_called_with(fake_env)
  120. mock_run_orc.assert_called_with(fake_env)
  121. mock_run_command.assert_called_with(
  122. ['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm')
  123. mock_upgrade_fact.assert_called_with(True)
  124. mock_die_tuskar_die.assert_called_once()
  125. mock_run_validation_groups.assert_called_once()
  126. @mock.patch(
  127. 'instack_undercloud.undercloud._load_subnets_config_groups')
  128. @mock.patch('instack_undercloud.undercloud._handle_upgrade_fact')
  129. @mock.patch('instack_undercloud.undercloud._configure_logging')
  130. @mock.patch('instack_undercloud.undercloud._validate_configuration')
  131. @mock.patch('instack_undercloud.undercloud._run_command')
  132. @mock.patch('instack_undercloud.undercloud._post_config')
  133. @mock.patch('instack_undercloud.undercloud._run_orc')
  134. @mock.patch('instack_undercloud.undercloud._run_yum_update')
  135. @mock.patch('instack_undercloud.undercloud._run_yum_clean_all')
  136. @mock.patch('instack_undercloud.undercloud._run_instack')
  137. @mock.patch('instack_undercloud.undercloud._generate_environment')
  138. @mock.patch('instack_undercloud.undercloud._load_config')
  139. @mock.patch('instack_undercloud.undercloud._die_tuskar_die')
  140. @mock.patch('instack_undercloud.undercloud._run_validation_groups')
  141. def test_install_upgrade_hieradata(self, mock_run_validation_groups,
  142. mock_die_tuskar_die, mock_load_config,
  143. mock_generate_environment,
  144. mock_run_instack,
  145. mock_run_yum_clean_all,
  146. mock_run_yum_update, mock_run_orc,
  147. mock_post_config, mock_run_command,
  148. mock_validate_configuration,
  149. mock_configure_logging,
  150. mock_upgrade_fact,
  151. mock_load_subnets_config_groups):
  152. self.conf.config(hieradata_override='override.yaml')
  153. with open(os.path.expanduser('~/override.yaml'), 'w') as f:
  154. f.write('Something\n')
  155. fake_env = mock.MagicMock()
  156. mock_generate_environment.return_value = fake_env
  157. undercloud.install('.', upgrade=True)
  158. self.assertTrue(mock_validate_configuration.called)
  159. mock_generate_environment.assert_called_with('.')
  160. mock_run_instack.assert_called_with(fake_env)
  161. mock_run_orc.assert_called_with(fake_env)
  162. mock_run_command.assert_called_with(
  163. ['sudo', 'rm', '-f', '/tmp/svc-map-services'], None, 'rm')
  164. self.assertNotIn(
  165. mock.call(
  166. ['sudo', 'cp', 'override.yaml',
  167. '/etc/puppet/hieradata/override.yaml']),
  168. mock_run_command.mock_calls)
  169. mock_upgrade_fact.assert_called_with(True)
  170. mock_die_tuskar_die.assert_called_once()
  171. mock_run_validation_groups.assert_called_once()
  172. @mock.patch('instack_undercloud.undercloud._configure_logging')
  173. def test_install_exception(self, mock_configure_logging):
  174. mock_configure_logging.side_effect = RuntimeError('foo')
  175. self.assertRaises(RuntimeError, undercloud.install, '.')
  176. log_dict = {'undercloud_operation': "install",
  177. 'exception': 'foo',
  178. 'log_file': undercloud.PATHS.LOG_FILE
  179. }
  180. self.assertIn(undercloud.FAILURE_MESSAGE % log_dict,
  181. self.logger.output)
  182. @mock.patch('sys.exit')
  183. @mock.patch('instack_undercloud.undercloud._configure_logging')
  184. def test_install_exception_no_debug(self, mock_configure_logging,
  185. mock_exit):
  186. mock_configure_logging.side_effect = RuntimeError('foo')
  187. self.conf.config(undercloud_debug=False)
  188. undercloud.install('.')
  189. log_dict = {'undercloud_operation': "install",
  190. 'exception': 'foo',
  191. 'log_file': undercloud.PATHS.LOG_FILE
  192. }
  193. self.assertIn(undercloud.FAILURE_MESSAGE % log_dict,
  194. self.logger.output)
  195. mock_exit.assert_called_with(1)
  196. def test_generate_password(self):
  197. first = undercloud._generate_password()
  198. second = undercloud._generate_password()
  199. self.assertNotEqual(first, second)
  200. def test_extract_from_stackrc(self):
  201. with open(os.path.expanduser('~/stackrc'), 'w') as f:
  202. f.write('OS_USERNAME=aturing\n')
  203. f.write('OS_AUTH_URL=http://bletchley:5000/\n')
  204. self.assertEqual('aturing',
  205. undercloud._extract_from_stackrc('OS_USERNAME'))
  206. self.assertEqual('http://bletchley:5000/',
  207. undercloud._extract_from_stackrc('OS_AUTH_URL'))
  208. @mock.patch('instack_undercloud.undercloud._check_hostname')
  209. @mock.patch('instack_undercloud.undercloud._check_memory')
  210. @mock.patch('instack_undercloud.undercloud._check_sysctl')
  211. @mock.patch('instack_undercloud.undercloud._validate_network')
  212. @mock.patch('instack_undercloud.undercloud._validate_no_ip_change')
  213. @mock.patch('instack_undercloud.undercloud._validate_passwords_file')
  214. def test_validate_configuration(self, mock_vpf, mock_vnic,
  215. mock_validate_network,
  216. mock_check_memory, mock_check_hostname,
  217. mock_check_sysctl):
  218. undercloud._validate_configuration()
  219. self.assertTrue(mock_vpf.called)
  220. self.assertTrue(mock_vnic.called)
  221. self.assertTrue(mock_validate_network.called)
  222. self.assertTrue(mock_check_memory.called)
  223. self.assertTrue(mock_check_hostname.called)
  224. self.assertTrue(mock_check_sysctl.called)
  225. class TestCheckHostname(BaseTestCase):
  226. @mock.patch('instack_undercloud.undercloud._run_command')
  227. def test_correct(self, mock_run_command):
  228. mock_run_command.side_effect = ['test-hostname', 'test-hostname']
  229. self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
  230. 'test-hostname'))
  231. fake_hosts = io.StringIO(u'127.0.0.1 test-hostname\n')
  232. with mock.patch('instack_undercloud.undercloud.open',
  233. return_value=fake_hosts, create=True):
  234. undercloud._check_hostname()
  235. @mock.patch('instack_undercloud.undercloud._run_command')
  236. def test_static_transient_mismatch(self, mock_run_command):
  237. mock_run_command.side_effect = ['test-hostname', 'other-hostname']
  238. self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
  239. 'test-hostname'))
  240. fake_hosts = io.StringIO(u'127.0.0.1 test-hostname\n')
  241. with mock.patch('instack_undercloud.undercloud.open',
  242. return_value=fake_hosts, create=True):
  243. self.assertRaises(RuntimeError, undercloud._check_hostname)
  244. @mock.patch('instack_undercloud.undercloud._run_command')
  245. def test_no_substring_match(self, mock_run_command):
  246. mock_run_command.side_effect = ['test.hostname', 'test.hostname',
  247. None]
  248. self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
  249. 'test.hostname'))
  250. fake_hosts = io.StringIO(u'127.0.0.1 test-hostname-bad\n')
  251. with mock.patch('instack_undercloud.undercloud.open',
  252. return_value=fake_hosts, create=True):
  253. undercloud._check_hostname()
  254. mock_run_command.assert_called_with([
  255. 'sudo', '/bin/bash', '-c',
  256. 'sed -i "s/127.0.0.1\\(\\s*\\)/'
  257. '127.0.0.1\\1test.hostname test /" '
  258. '/etc/hosts'],
  259. name='hostname-to-etc-hosts')
  260. @mock.patch('instack_undercloud.undercloud._run_command')
  261. def test_commented(self, mock_run_command):
  262. mock_run_command.side_effect = ['test.hostname', 'test.hostname',
  263. None]
  264. self.useFixture(fixtures.EnvironmentVariable('HOSTNAME',
  265. 'test.hostname'))
  266. fake_hosts = io.StringIO(u""" #127.0.0.1 test.hostname\n
  267. 127.0.0.1 other-hostname\n""")
  268. with mock.patch('instack_undercloud.undercloud.open',
  269. return_value=fake_hosts, create=True):
  270. undercloud._check_hostname()
  271. mock_run_command.assert_called_with([
  272. 'sudo', '/bin/bash', '-c',
  273. 'sed -i "s/127.0.0.1\\(\\s*\\)/'
  274. '127.0.0.1\\1test.hostname test /" '
  275. '/etc/hosts'],
  276. name='hostname-to-etc-hosts')
  277. @mock.patch('instack_undercloud.undercloud._run_command')
  278. def test_set_fqdn(self, mock_run_command):
  279. mock_run_command.side_effect = [None,
  280. 'test-hostname.domain',
  281. 'test-hostname.domain',
  282. None]
  283. self.conf.config(undercloud_hostname='test-hostname.domain')
  284. fake_hosts = io.StringIO(u'127.0.0.1 other-hostname\n')
  285. with mock.patch('instack_undercloud.undercloud.open',
  286. return_value=fake_hosts, create=True):
  287. undercloud._check_hostname()
  288. mock_run_command.assert_called_with([
  289. 'sudo', '/bin/bash', '-c',
  290. 'sed -i "s/127.0.0.1\\(\\s*\\)/'
  291. '127.0.0.1\\1test-hostname.domain test-hostname /" '
  292. '/etc/hosts'],
  293. name='hostname-to-etc-hosts')
  294. @mock.patch('instack_undercloud.undercloud._run_command')
  295. def test_set_not_fq(self, mock_run_command):
  296. mock_run_command.side_effect = [None,
  297. 'test-hostname',
  298. 'test-hostname',
  299. None]
  300. self.conf.config(undercloud_hostname='test-hostname')
  301. self.assertRaises(RuntimeError, undercloud._check_hostname)
  302. class TestCheckMemory(BaseTestCase):
  303. @mock.patch('psutil.swap_memory')
  304. @mock.patch('psutil.virtual_memory')
  305. def test_sufficient_memory(self, mock_vm, mock_sm):
  306. mock_vm.return_value = mock.Mock()
  307. mock_vm.return_value.total = 8589934592
  308. mock_sm.return_value = mock.Mock()
  309. mock_sm.return_value.total = 0
  310. undercloud._check_memory()
  311. @mock.patch('psutil.swap_memory')
  312. @mock.patch('psutil.virtual_memory')
  313. def test_insufficient_memory(self, mock_vm, mock_sm):
  314. mock_vm.return_value = mock.Mock()
  315. mock_vm.return_value.total = 2071963648
  316. mock_sm.return_value = mock.Mock()
  317. mock_sm.return_value.total = 0
  318. self.assertRaises(RuntimeError, undercloud._check_memory)
  319. @mock.patch('psutil.swap_memory')
  320. @mock.patch('psutil.virtual_memory')
  321. def test_sufficient_swap(self, mock_vm, mock_sm):
  322. mock_vm.return_value = mock.Mock()
  323. mock_vm.return_value.total = 6442450944
  324. mock_sm.return_value = mock.Mock()
  325. mock_sm.return_value.total = 2147483648
  326. undercloud._check_memory()
  327. class TestCheckSysctl(BaseTestCase):
  328. @mock.patch('os.path.isfile')
  329. def test_missing_options(self, mock_isfile):
  330. mock_isfile.return_value = False
  331. self.assertRaises(RuntimeError, undercloud._check_sysctl)
  332. @mock.patch('os.path.isfile')
  333. def test_available_option(self, mock_isfile):
  334. mock_isfile.return_value = True
  335. undercloud._check_sysctl()
  336. class TestNoIPChange(BaseTestCase):
  337. @mock.patch('os.path.isfile', return_value=False)
  338. def test_new_install(self, mock_isfile):
  339. undercloud._validate_no_ip_change()
  340. @mock.patch('instack_undercloud.undercloud.open')
  341. @mock.patch('json.loads')
  342. @mock.patch('os.path.isfile', return_value=True)
  343. def test_update_matches(self, mock_isfile, mock_loads, mock_open):
  344. mock_members = [{'name': 'eth0'},
  345. {'name': 'br-ctlplane',
  346. 'addresses': [{'ip_netmask': '192.168.24.1/24'}]
  347. }
  348. ]
  349. mock_config = {'network_config': mock_members}
  350. mock_loads.return_value = mock_config
  351. undercloud._validate_no_ip_change()
  352. @mock.patch('instack_undercloud.undercloud.open')
  353. @mock.patch('json.loads')
  354. @mock.patch('os.path.isfile', return_value=True)
  355. def test_update_mismatch(self, mock_isfile, mock_loads, mock_open):
  356. mock_members = [{'name': 'eth0'},
  357. {'name': 'br-ctlplane',
  358. 'addresses': [{'ip_netmask': '192.168.0.1/24'}]
  359. }
  360. ]
  361. mock_config = {'network_config': mock_members}
  362. mock_loads.return_value = mock_config
  363. self.assertRaises(validator.FailedValidation,
  364. undercloud._validate_no_ip_change)
  365. @mock.patch('instack_undercloud.undercloud.open')
  366. @mock.patch('json.loads')
  367. @mock.patch('os.path.isfile', return_value=True)
  368. def test_update_no_network(self, mock_isfile, mock_loads, mock_open):
  369. mock_members = [{'name': 'eth0'}]
  370. mock_config = {'network_config': mock_members}
  371. mock_loads.return_value = mock_config
  372. undercloud._validate_no_ip_change()
  373. @mock.patch('os.path.isfile')
  374. class TestPasswordsFileExists(BaseTestCase):
  375. def test_new_install(self, mock_isfile):
  376. mock_isfile.side_effect = [False]
  377. undercloud._validate_passwords_file()
  378. def test_update_exists(self, mock_isfile):
  379. mock_isfile.side_effect = [True, True]
  380. undercloud._validate_passwords_file()
  381. def test_update_missing(self, mock_isfile):
  382. mock_isfile.side_effect = [True, False]
  383. self.assertRaises(validator.FailedValidation,
  384. undercloud._validate_passwords_file)
  385. class TestGenerateEnvironment(BaseTestCase):
  386. def setUp(self):
  387. super(TestGenerateEnvironment, self).setUp()
  388. # Things that need to always be mocked out, but that the tests
  389. # don't want to care about.
  390. self.useFixture(fixtures.MockPatch(
  391. 'instack_undercloud.undercloud._write_password_file'))
  392. self.useFixture(fixtures.MockPatch(
  393. 'instack_undercloud.undercloud._load_config'))
  394. mock_isdir = fixtures.MockPatch('os.path.isdir')
  395. self.useFixture(mock_isdir)
  396. mock_isdir.mock.return_value = False
  397. # Some tests do care about this, but they can override the default
  398. # return value, and then the tests that don't care can ignore it.
  399. self.mock_distro = fixtures.MockPatch('platform.linux_distribution')
  400. self.useFixture(self.mock_distro)
  401. self.mock_distro.mock.return_value = [
  402. 'Red Hat Enterprise Linux Server 7.1']
  403. @mock.patch('socket.gethostname')
  404. def test_hostname_set(self, mock_gethostname):
  405. fake_hostname = 'crazy-test-hostname-!@#$%12345'
  406. mock_gethostname.return_value = fake_hostname
  407. env = undercloud._generate_environment('.')
  408. self.assertEqual(fake_hostname, env['HOSTNAME'])
  409. def test_elements_path_input(self):
  410. test_path = '/test/elements/path'
  411. self.useFixture(fixtures.EnvironmentVariable('ELEMENTS_PATH',
  412. test_path))
  413. env = undercloud._generate_environment('.')
  414. self.assertEqual(test_path, env['ELEMENTS_PATH'])
  415. def test_default_elements_path(self):
  416. env = undercloud._generate_environment('.')
  417. test_path = ('%s:%s:/usr/share/tripleo-image-elements:'
  418. '/usr/share/diskimage-builder/elements' %
  419. (os.path.join(os.getcwd(), 'tripleo-puppet-elements',
  420. 'elements'),
  421. './elements'))
  422. self.assertEqual(test_path, env['ELEMENTS_PATH'])
  423. def test_rhel7_distro(self):
  424. self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
  425. env = undercloud._generate_environment('.')
  426. self.assertEqual('rhel7', env['NODE_DIST'])
  427. self.assertEqual('./json-files/rhel-7-undercloud-packages.json',
  428. env['JSONFILE'])
  429. self.assertEqual('disable', env['REG_METHOD'])
  430. self.assertEqual('1', env['REG_HALT_UNREGISTER'])
  431. def test_centos7_distro(self):
  432. self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
  433. self.mock_distro.mock.return_value = ['CentOS Linux release 7.1']
  434. env = undercloud._generate_environment('.')
  435. self.assertEqual('centos7', env['NODE_DIST'])
  436. self.assertEqual('./json-files/centos-7-undercloud-packages.json',
  437. env['JSONFILE'])
  438. def test_fedora_distro(self):
  439. self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
  440. self.mock_distro.mock.return_value = ['Fedora Infinity + 1']
  441. self.assertRaises(RuntimeError, undercloud._generate_environment, '.')
  442. def test_other_distro(self):
  443. self.useFixture(fixtures.EnvironmentVariable('NODE_DIST', None))
  444. self.mock_distro.mock.return_value = ['Gentoo']
  445. self.assertRaises(RuntimeError, undercloud._generate_environment, '.')
  446. def test_opts_in_env(self):
  447. env = undercloud._generate_environment('.')
  448. # Just spot check, we don't want to replicate the entire opt list here
  449. self.assertEqual(env['INSPECTION_COLLECTORS'],
  450. 'default,extra-hardware,numa-topology,logs')
  451. self.assertEqual('192.168.24.1/24', env['PUBLIC_INTERFACE_IP'])
  452. self.assertEqual('192.168.24.1', env['LOCAL_IP'])
  453. # The list is generated from a set, so we can't rely on ordering.
  454. # Instead make sure that it looks like a valid list by parsing it.
  455. drivers = json.loads(env['ENABLED_DRIVERS'])
  456. self.assertEqual(sorted(drivers), ['pxe_drac', 'pxe_ilo',
  457. 'pxe_ipmitool'])
  458. hw_types = json.loads(env['ENABLED_HARDWARE_TYPES'])
  459. self.assertEqual(sorted(hw_types), ['idrac', 'ilo', 'ipmi', 'redfish'])
  460. self.assertEqual(
  461. sorted(json.loads(env['ENABLED_BOOT_INTERFACES'])),
  462. ['ilo-pxe', 'pxe'])
  463. self.assertEqual(
  464. sorted(json.loads(env['ENABLED_POWER_INTERFACES'])),
  465. ['fake', 'idrac', 'ilo', 'ipmitool', 'redfish'])
  466. self.assertEqual(
  467. sorted(json.loads(env['ENABLED_MANAGEMENT_INTERFACES'])),
  468. ['fake', 'idrac', 'ilo', 'ipmitool', 'redfish'])
  469. self.assertEqual(
  470. sorted(json.loads(env['ENABLED_RAID_INTERFACES'])),
  471. ['idrac', 'no-raid'])
  472. self.assertEqual(
  473. sorted(json.loads(env['ENABLED_VENDOR_INTERFACES'])),
  474. ['idrac', 'ipmitool', 'no-vendor'])
  475. self.assertEqual(env['INSPECTION_NODE_NOT_FOUND_HOOK'], '')
  476. def test_all_hardware_types(self):
  477. self.conf.config(enabled_hardware_types=['ipmi', 'redfish', 'ilo',
  478. 'idrac', 'irmc', 'snmp',
  479. 'cisco-ucs-managed',
  480. 'cisco-ucs-standalone'])
  481. env = undercloud._generate_environment('.')
  482. # The list is generated from a set, so we can't rely on ordering.
  483. # Instead make sure that it looks like a valid list by parsing it.
  484. hw_types = json.loads(env['ENABLED_HARDWARE_TYPES'])
  485. self.assertEqual(sorted(hw_types), ['cisco-ucs-managed',
  486. 'cisco-ucs-standalone',
  487. 'idrac', 'ilo', 'ipmi', 'irmc',
  488. 'redfish', 'snmp'])
  489. self.assertEqual(
  490. sorted(json.loads(env['ENABLED_BOOT_INTERFACES'])),
  491. ['ilo-pxe', 'irmc-pxe', 'pxe'])
  492. self.assertEqual(
  493. sorted(json.loads(env['ENABLED_POWER_INTERFACES'])),
  494. ['cimc', 'fake', 'idrac', 'ilo', 'ipmitool', 'irmc',
  495. 'redfish', 'snmp', 'ucsm'])
  496. self.assertEqual(
  497. sorted(json.loads(env['ENABLED_MANAGEMENT_INTERFACES'])),
  498. ['cimc', 'fake', 'idrac', 'ilo', 'ipmitool', 'irmc',
  499. 'redfish', 'ucsm'])
  500. self.assertEqual(
  501. sorted(json.loads(env['ENABLED_RAID_INTERFACES'])),
  502. ['idrac', 'no-raid'])
  503. self.assertEqual(
  504. sorted(json.loads(env['ENABLED_VENDOR_INTERFACES'])),
  505. ['idrac', 'ipmitool', 'no-vendor'])
  506. def test_enabled_discovery(self):
  507. self.conf.config(enable_node_discovery=True,
  508. discovery_default_driver='pxe_foobar')
  509. env = undercloud._generate_environment('.')
  510. # The list is generated from a set, so we can't rely on ordering.
  511. # Instead make sure that it looks like a valid list by parsing it.
  512. drivers = json.loads(env['ENABLED_DRIVERS'])
  513. # Discovery requires enabling the default driver. The pxe_ prefix
  514. # designates a classic driver.
  515. self.assertEqual(sorted(drivers), ['pxe_drac', 'pxe_foobar', 'pxe_ilo',
  516. 'pxe_ipmitool'])
  517. self.assertEqual(env['INSPECTION_NODE_NOT_FOUND_HOOK'], 'enroll')
  518. def test_enabled_hardware_types(self):
  519. self.conf.config(enable_node_discovery=True,
  520. discovery_default_driver='foobar',
  521. enabled_hardware_types=['ipmi', 'something'])
  522. env = undercloud._generate_environment('.')
  523. # The list is generated from a set, so we can't rely on ordering.
  524. # Instead make sure that it looks like a valid list by parsing it.
  525. drivers = json.loads(env['ENABLED_DRIVERS'])
  526. hw_types = json.loads(env['ENABLED_HARDWARE_TYPES'])
  527. self.assertEqual(sorted(drivers), ['pxe_drac', 'pxe_ilo',
  528. 'pxe_ipmitool'])
  529. self.assertEqual(sorted(hw_types), ['foobar', 'ipmi', 'something'])
  530. def test_docker_registry_mirror(self):
  531. self.conf.config(docker_registry_mirror='http://foo/bar')
  532. env = undercloud._generate_environment('.')
  533. # Spot check one service
  534. self.assertEqual('http://foo/bar',
  535. env['DOCKER_REGISTRY_MIRROR'])
  536. def test_docker_insecure_registries(self):
  537. self.conf.config(docker_insecure_registries=['http://foo/bar:8787'])
  538. env = undercloud._generate_environment('.')
  539. insecure_registries = json.loads(env['DOCKER_INSECURE_REGISTRIES'])
  540. # Spot check one service
  541. self.assertEqual(['192.168.24.1:8787', '192.168.24.3:8787',
  542. 'http://foo/bar:8787'], insecure_registries)
  543. def test_generate_endpoints(self):
  544. env = undercloud._generate_environment('.')
  545. endpoint_vars = {k: v for (k, v) in env.items()
  546. if k.startswith('UNDERCLOUD_ENDPOINT')}
  547. self.assertEqual(96, len(endpoint_vars))
  548. # Spot check one service
  549. self.assertEqual('http://192.168.24.1:5000',
  550. env['UNDERCLOUD_ENDPOINT_KEYSTONE_PUBLIC'])
  551. self.assertEqual('http://192.168.24.1:5000',
  552. env['UNDERCLOUD_ENDPOINT_KEYSTONE_INTERNAL'])
  553. self.assertEqual('http://192.168.24.1:35357',
  554. env['UNDERCLOUD_ENDPOINT_KEYSTONE_ADMIN'])
  555. # Also check that the tenant id part is preserved
  556. self.assertEqual('http://192.168.24.1:8080/v1/AUTH_%(tenant_id)s',
  557. env['UNDERCLOUD_ENDPOINT_SWIFT_PUBLIC'])
  558. def test_generate_endpoints_ssl_manual(self):
  559. self.conf.config(undercloud_service_certificate='test.pem')
  560. env = undercloud._generate_environment('.')
  561. # Spot check one service
  562. self.assertEqual('https://192.168.24.2:13000',
  563. env['UNDERCLOUD_ENDPOINT_KEYSTONE_PUBLIC'])
  564. self.assertEqual('http://192.168.24.3:5000',
  565. env['UNDERCLOUD_ENDPOINT_KEYSTONE_INTERNAL'])
  566. self.assertEqual('http://192.168.24.3:35357',
  567. env['UNDERCLOUD_ENDPOINT_KEYSTONE_ADMIN'])
  568. self.assertEqual('https://192.168.24.2:443/keystone/v3',
  569. env['UNDERCLOUD_ENDPOINT_KEYSTONE_UI_CONFIG_PUBLIC'])
  570. # Also check that the tenant id part is preserved
  571. self.assertEqual('https://192.168.24.2:13808/v1/AUTH_%(tenant_id)s',
  572. env['UNDERCLOUD_ENDPOINT_SWIFT_PUBLIC'])
  573. def test_generate_endpoints_ssl_auto(self):
  574. self.conf.config(generate_service_certificate=True)
  575. env = undercloud._generate_environment('.')
  576. # Spot check one service
  577. self.assertEqual('https://192.168.24.2:13000',
  578. env['UNDERCLOUD_ENDPOINT_KEYSTONE_PUBLIC'])
  579. self.assertEqual('http://192.168.24.3:5000',
  580. env['UNDERCLOUD_ENDPOINT_KEYSTONE_INTERNAL'])
  581. self.assertEqual('http://192.168.24.3:35357',
  582. env['UNDERCLOUD_ENDPOINT_KEYSTONE_ADMIN'])
  583. # Also check that the tenant id part is preserved
  584. self.assertEqual('https://192.168.24.2:13808/v1/AUTH_%(tenant_id)s',
  585. env['UNDERCLOUD_ENDPOINT_SWIFT_PUBLIC'])
  586. def test_absolute_cert_path(self):
  587. self.conf.config(undercloud_service_certificate='/home/stack/test.pem')
  588. env = undercloud._generate_environment('.')
  589. self.assertEqual('/home/stack/test.pem',
  590. env['UNDERCLOUD_SERVICE_CERTIFICATE'])
  591. def test_relative_cert_path(self):
  592. [cert] = self.create_tempfiles([('test', 'foo')], '.pem')
  593. rel_cert = os.path.basename(cert)
  594. cert_path = os.path.dirname(cert)
  595. cur_dir = os.getcwd()
  596. try:
  597. os.chdir(cert_path)
  598. self.conf.config(undercloud_service_certificate=rel_cert)
  599. env = undercloud._generate_environment('.')
  600. self.assertEqual(os.path.join(os.getcwd(), rel_cert),
  601. env['UNDERCLOUD_SERVICE_CERTIFICATE'])
  602. finally:
  603. os.chdir(cur_dir)
  604. def test_no_cert_path(self):
  605. env = undercloud._generate_environment('.')
  606. self.assertEqual('', env['UNDERCLOUD_SERVICE_CERTIFICATE'])
  607. def test_remove_dib_yum_repo_conf(self):
  608. self.useFixture(fixtures.EnvironmentVariable('DIB_YUM_REPO_CONF',
  609. 'rum_yepo.conf'))
  610. env = undercloud._generate_environment('.')
  611. self.assertNotIn(env, 'DIB_YUM_REPO_CONF')
  612. def test_inspection_ip_single_subnet(self):
  613. env = undercloud._generate_environment('.')
  614. reference = [{"tag": "ctlplane-subnet", "gateway": "192.168.24.1",
  615. "ip_range": "192.168.24.100,192.168.24.120",
  616. "netmask": "255.255.255.0", "mtu": 1500}]
  617. actual = json.loads(env['INSPECTION_SUBNETS'])
  618. self.assertEqual(reference, actual)
  619. def test_inspection_ip_multiple_subnets(self):
  620. self.conf.config(subnets=['subnet1', 'subnet2'])
  621. self.conf.config(local_subnet='subnet1')
  622. self.conf.register_opts(self.opts, group=self.grp1)
  623. self.conf.register_opts(self.opts, group=self.gtp2)
  624. self.conf.config(cidr='192.168.10.0/24', dhcp_start='192.168.10.10',
  625. dhcp_end='192.168.10.99',
  626. inspection_iprange='192.168.10.100,192.168.10.189',
  627. gateway='192.168.10.254', masquerade=True,
  628. group='subnet1')
  629. self.conf.config(cidr='192.168.20.0/24', dhcp_start='192.168.20.10',
  630. dhcp_end='192.168.20.99',
  631. inspection_iprange='192.168.20.100,192.168.20.189',
  632. gateway='192.168.20.254', masquerade=True,
  633. group='subnet2')
  634. env = undercloud._generate_environment('.')
  635. reference = [{"tag": "subnet1", "gateway": "192.168.10.254",
  636. "ip_range": "192.168.10.100,192.168.10.189",
  637. "netmask": "255.255.255.0", "mtu": 1500},
  638. {"tag": "subnet2", "gateway": "192.168.20.254",
  639. "ip_range": "192.168.20.100,192.168.20.189",
  640. "netmask": "255.255.255.0", "mtu": 1500}]
  641. actual = json.loads(env['INSPECTION_SUBNETS'])
  642. self.assertEqual(reference, actual)
  643. def test_subnets_static_routes(self):
  644. self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
  645. self.conf.register_opts(self.opts, group=self.grp1)
  646. self.conf.register_opts(self.opts, group=self.gtp2)
  647. self.conf.config(cidr='192.168.24.0/24',
  648. dhcp_start='192.168.24.5', dhcp_end='192.168.24.24',
  649. inspection_iprange='192.168.24.100,192.168.24.120',
  650. gateway='192.168.24.1', masquerade=True,
  651. group='ctlplane-subnet')
  652. self.conf.config(cidr='192.168.10.0/24', dhcp_start='192.168.10.10',
  653. dhcp_end='192.168.10.99',
  654. inspection_iprange='192.168.10.100,192.168.10.189',
  655. gateway='192.168.10.254', masquerade=True,
  656. group='subnet1')
  657. self.conf.config(cidr='192.168.20.0/24', dhcp_start='192.168.20.10',
  658. dhcp_end='192.168.20.99',
  659. inspection_iprange='192.168.20.100,192.168.20.189',
  660. gateway='192.168.20.254', masquerade=True,
  661. group='subnet2')
  662. env = undercloud._generate_environment('.')
  663. reference = [{"ip_netmask": "192.168.10.0/24",
  664. "next_hop": "192.168.24.1"},
  665. {"ip_netmask": "192.168.20.0/24",
  666. "next_hop": "192.168.24.1"}]
  667. actual = json.loads(env['SUBNETS_STATIC_ROUTES'])
  668. self.assertEqual(reference, actual)
  669. def test_subnets_subnets_cidr_nat_rules(self):
  670. self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
  671. self.conf.register_opts(self.opts, group=self.grp1)
  672. self.conf.register_opts(self.opts, group=self.gtp2)
  673. self.conf.config(cidr='192.168.24.0/24',
  674. dhcp_start='192.168.24.5', dhcp_end='192.168.24.24',
  675. inspection_iprange='192.168.24.100,192.168.24.120',
  676. gateway='192.168.24.1', group='ctlplane-subnet')
  677. self.conf.config(cidr='192.168.10.0/24', dhcp_start='192.168.10.10',
  678. dhcp_end='192.168.10.99',
  679. inspection_iprange='192.168.10.100,192.168.10.189',
  680. gateway='192.168.10.254', group='subnet1')
  681. self.conf.config(cidr='192.168.20.0/24', dhcp_start='192.168.20.10',
  682. dhcp_end='192.168.20.99',
  683. inspection_iprange='192.168.20.100,192.168.20.189',
  684. gateway='192.168.20.254', group='subnet2')
  685. env = undercloud._generate_environment('.')
  686. reference = ('"140 destination ctlplane-subnet cidr nat": '
  687. '{"chain": "FORWARD", "destination": "192.168.24.0/24", '
  688. '"proto": "all", "action": "accept"}'
  689. '\n "140 source ctlplane-subnet cidr nat": '
  690. '{"chain": "FORWARD", "source": "192.168.24.0/24", '
  691. '"proto": "all", "action": "accept"}'
  692. '\n "140 destination subnet1 cidr nat": '
  693. '{"chain": "FORWARD", "destination": "192.168.10.0/24", '
  694. '"proto": "all", "action": "accept"}'
  695. '\n "140 source subnet1 cidr nat": '
  696. '{"chain": "FORWARD", "source": "192.168.10.0/24", '
  697. '"proto": "all", "action": "accept"}'
  698. '\n "140 destination subnet2 cidr nat": '
  699. '{"chain": "FORWARD", "destination": "192.168.20.0/24", '
  700. '"proto": "all", "action": "accept"}'
  701. '\n "140 source subnet2 cidr nat": '
  702. '{"chain": "FORWARD", "source": "192.168.20.0/24", '
  703. '"proto": "all", "action": "accept"}')
  704. actual = env['SUBNETS_CIDR_NAT_RULES']
  705. self.assertEqual(reference, actual)
  706. def test_masquerade_networks(self):
  707. self.conf.config(subnets=['ctlplane-subnet', 'subnet1', 'subnet2'])
  708. self.conf.register_opts(self.opts, group=self.grp1)
  709. self.conf.register_opts(self.opts, group=self.gtp2)
  710. self.conf.config(cidr='192.168.24.0/24',
  711. dhcp_start='192.168.24.5', dhcp_end='192.168.24.24',
  712. inspection_iprange='192.168.24.100,192.168.24.120',
  713. gateway='192.168.24.1', masquerade=True,
  714. group='ctlplane-subnet')
  715. self.conf.config(cidr='192.168.10.0/24', dhcp_start='192.168.10.10',
  716. dhcp_end='192.168.10.99',
  717. inspection_iprange='192.168.10.100,192.168.10.189',
  718. gateway='192.168.10.254', masquerade=True,
  719. group='subnet1')
  720. self.conf.config(cidr='192.168.20.0/24', dhcp_start='192.168.20.10',
  721. dhcp_end='192.168.20.99',
  722. inspection_iprange='192.168.20.100,192.168.20.189',
  723. gateway='192.168.20.254', masquerade=True,
  724. group='subnet2')
  725. env = undercloud._generate_environment('.')
  726. reference = ['192.168.24.0/24', '192.168.10.0/24', '192.168.20.0/24']
  727. actual = json.loads(env['MASQUERADE_NETWORKS'])
  728. self.assertEqual(reference, actual)
  729. class TestWritePasswordFile(BaseTestCase):
  730. def test_normal(self):
  731. instack_env = {}
  732. undercloud._write_password_file(instack_env)
  733. test_parser = configparser.ConfigParser()
  734. test_parser.read(undercloud.PATHS.PASSWORD_PATH)
  735. self.assertTrue(test_parser.has_option('auth',
  736. 'undercloud_db_password'))
  737. self.assertIn('UNDERCLOUD_DB_PASSWORD', instack_env)
  738. self.assertEqual(32,
  739. len(instack_env['UNDERCLOUD_HEAT_ENCRYPTION_KEY']))
  740. def test_value_set(self):
  741. instack_env = {}
  742. self.conf.config(undercloud_db_password='test', group='auth')
  743. undercloud._write_password_file(instack_env)
  744. test_parser = configparser.ConfigParser()
  745. test_parser.read(undercloud.PATHS.PASSWORD_PATH)
  746. self.assertEqual(test_parser.get('auth', 'undercloud_db_password'),
  747. 'test')
  748. self.assertEqual(instack_env['UNDERCLOUD_DB_PASSWORD'], 'test')
  749. class TestRunCommand(BaseTestCase):
  750. def test_run_command(self):
  751. output = undercloud._run_command(['echo', 'foo'])
  752. self.assertEqual('foo\n', output)
  753. def test_run_live_command(self):
  754. undercloud._run_live_command(['echo', 'bar'])
  755. self.assertIn('bar\n', self.logger.output)
  756. @mock.patch('subprocess.check_output')
  757. def test_run_command_fails(self, mock_check_output):
  758. fake_exc = subprocess.CalledProcessError(1, 'nothing', 'fake failure')
  759. mock_check_output.side_effect = fake_exc
  760. self.assertRaises(subprocess.CalledProcessError,
  761. undercloud._run_command, ['nothing'])
  762. self.assertIn('nothing failed', self.logger.output)
  763. self.assertIn('fake failure', self.logger.output)
  764. @mock.patch('subprocess.check_output')
  765. def test_run_command_fails_with_name(self, mock_check_output):
  766. fake_exc = subprocess.CalledProcessError(1, 'nothing', 'fake failure')
  767. mock_check_output.side_effect = fake_exc
  768. self.assertRaises(subprocess.CalledProcessError,
  769. undercloud._run_command, ['nothing'],
  770. name='fake_name')
  771. self.assertIn('fake_name failed', self.logger.output)
  772. self.assertIn('fake failure', self.logger.output)
  773. def test_run_live_command_fails(self):
  774. exc = self.assertRaises(RuntimeError, undercloud._run_live_command,
  775. ['ls', '/nonexistent/path'])
  776. self.assertIn('ls failed', str(exc))
  777. self.assertIn('ls', self.logger.output)
  778. def test_run_live_command_fails_name(self):
  779. exc = self.assertRaises(RuntimeError, undercloud._run_live_command,
  780. ['ls', '/nonexistent/path'],
  781. name='fake_name')
  782. self.assertIn('fake_name failed', str(exc))
  783. def test_run_command_env(self):
  784. env = {'FOO': 'foo'}
  785. output = undercloud._run_command(['env'], env)
  786. self.assertIn('FOO=foo', output)
  787. def test_run_live_command_env(self):
  788. env = {'BAR': 'bar'}
  789. undercloud._run_live_command(['env'], env)
  790. self.assertIn('BAR=bar', self.logger.output)
  791. class TestRunTools(base.BaseTestCase):
  792. @mock.patch('instack_undercloud.undercloud._run_live_command')
  793. def test_run_instack(self, mock_run):
  794. instack_env = {'ELEMENTS_PATH': '.', 'JSONFILE': 'file.json'}
  795. args = ['sudo', '-E', 'instack', '-p', '.', '-j', 'file.json']
  796. undercloud._run_instack(instack_env)
  797. mock_run.assert_called_with(args, instack_env, 'instack')
  798. @mock.patch('instack_undercloud.undercloud._run_live_command')
  799. def test_run_os_refresh_config(self, mock_run):
  800. instack_env = {}
  801. args = ['sudo', 'os-refresh-config']
  802. undercloud._run_orc(instack_env)
  803. mock_run.assert_called_with(args, instack_env, 'os-refresh-config')
  804. @mock.patch('instack_undercloud.undercloud._run_command')
  805. class TestConfigureSshKeys(base.BaseTestCase):
  806. def test_ensure_user_identity(self, mock_run):
  807. id_path = os.path.expanduser('~/.ssh/id_rsa')
  808. undercloud._ensure_user_identity(id_path)
  809. mock_run.assert_called_with(['ssh-keygen', '-t', 'rsa', '-N', '',
  810. '-f', id_path])
  811. def _create_test_id(self):
  812. id_path = os.path.expanduser('~/.ssh/id_rsa')
  813. os.makedirs(os.path.expanduser('~/.ssh'))
  814. with open(id_path, 'w') as id_rsa:
  815. id_rsa.write('test private\n')
  816. with open(id_path + '.pub', 'w') as id_pub:
  817. id_pub.write('test public\n')
  818. return id_path
  819. def test_ensure_user_identity_exists(self, mock_run):
  820. id_path = self._create_test_id()
  821. undercloud._ensure_user_identity(id_path)
  822. self.assertFalse(mock_run.called)
  823. def _test_configure_ssh_keys(self, mock_eui, exists=True):
  824. id_path = self._create_test_id()
  825. mock_client_instance = mock.Mock()
  826. if not exists:
  827. get = mock_client_instance.keypairs.get
  828. get.side_effect = exceptions.NotFound('test')
  829. undercloud._configure_ssh_keys(mock_client_instance)
  830. mock_eui.assert_called_with(id_path)
  831. mock_client_instance.keypairs.get.assert_called_with('default')
  832. if not exists:
  833. mock_client_instance.keypairs.create.assert_called_with(
  834. 'default', 'test public')
  835. @mock.patch('instack_undercloud.undercloud._ensure_user_identity')
  836. def test_configure_ssh_keys_exists(self, mock_eui, _):
  837. self._test_configure_ssh_keys(mock_eui)
  838. @mock.patch('instack_undercloud.undercloud._ensure_user_identity')
  839. def test_configure_ssh_keys_missing(self, mock_eui, _):
  840. self._test_configure_ssh_keys(mock_eui, False)
  841. class TestPostConfig(BaseTestCase):
  842. @mock.patch('os_client_config.make_client')
  843. @mock.patch('instack_undercloud.undercloud._migrate_to_convergence')
  844. @mock.patch('instack_undercloud.undercloud._ensure_node_resource_classes')
  845. @mock.patch(
  846. 'instack_undercloud.undercloud._config_neutron_segments_and_subnets')
  847. @mock.patch('instack_undercloud.undercloud._ensure_neutron_network')
  848. @mock.patch('instack_undercloud.undercloud._member_role_exists')
  849. @mock.patch('instack_undercloud.undercloud._get_session')
  850. @mock.patch('ironicclient.client.get_client', autospec=True)
  851. @mock.patch('novaclient.client.Client', autospec=True)
  852. @mock.patch('swiftclient.client.Connection', autospec=True)
  853. @mock.patch('mistralclient.api.client.client', autospec=True)
  854. @mock.patch('instack_undercloud.undercloud._delete_default_flavors')
  855. @mock.patch('instack_undercloud.undercloud._copy_stackrc')
  856. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  857. @mock.patch('instack_undercloud.undercloud._delete_nova_cert_service')
  858. @mock.patch('instack_undercloud.undercloud._configure_ssh_keys')
  859. @mock.patch('instack_undercloud.undercloud._ensure_flavor')
  860. @mock.patch('instack_undercloud.undercloud._post_config_mistral')
  861. def test_post_config(self, mock_post_config_mistral, mock_ensure_flavor,
  862. mock_configure_ssh_keys, mock_delete_nova_cert,
  863. mock_get_auth_values, mock_copy_stackrc,
  864. mock_delete, mock_mistral_client, mock_swift_client,
  865. mock_nova_client, mock_ir_client,
  866. mock_get_session, mock_member_role_exists,
  867. mock_ensure_neutron_network,
  868. mock_config_neutron_segments_and_subnets,
  869. mock_resource_classes, mock_migrate_to_convergence,
  870. mock_make_client):
  871. instack_env = {
  872. 'UNDERCLOUD_ENDPOINT_MISTRAL_PUBLIC':
  873. 'http://192.168.24.1:8989/v2',
  874. }
  875. mock_get_auth_values.return_value = ('aturing', '3nigma', 'hut8',
  876. 'http://bletchley:5000/')
  877. mock_instance_nova = mock.Mock()
  878. mock_nova_client.return_value = mock_instance_nova
  879. mock_get_session.return_value = mock.MagicMock()
  880. mock_instance_swift = mock.Mock()
  881. mock_swift_client.return_value = mock_instance_swift
  882. mock_instance_mistral = mock.Mock()
  883. mock_mistral_client.return_value = mock_instance_mistral
  884. mock_instance_ironic = mock_ir_client.return_value
  885. flavors = [mock.Mock(spec=['name']),
  886. mock.Mock(spec=['name'])]
  887. # The mock library treats "name" attribute differently, and we cannot
  888. # pass it through __init__
  889. flavors[0].name = 'baremetal'
  890. flavors[1].name = 'ceph-storage'
  891. mock_instance_nova.flavors.list.return_value = flavors
  892. mock_heat = mock.Mock()
  893. mock_make_client.return_value = mock_heat
  894. undercloud._post_config(instack_env, True)
  895. mock_nova_client.assert_called_with(
  896. 2, session=mock_get_session.return_value)
  897. self.assertTrue(mock_copy_stackrc.called)
  898. mock_configure_ssh_keys.assert_called_with(mock_instance_nova)
  899. mock_delete_nova_cert.assert_called_with(mock_instance_nova)
  900. calls = [mock.call(mock_instance_nova, flavors[0], 'baremetal', None),
  901. mock.call(mock_instance_nova, None, 'control', 'control'),
  902. mock.call(mock_instance_nova, None, 'compute', 'compute'),
  903. mock.call(mock_instance_nova, flavors[1],
  904. 'ceph-storage', 'ceph-storage'),
  905. mock.call(mock_instance_nova, None,
  906. 'block-storage', 'block-storage'),
  907. mock.call(mock_instance_nova, None,
  908. 'swift-storage', 'swift-storage'),
  909. ]
  910. mock_ensure_flavor.assert_has_calls(calls)
  911. mock_resource_classes.assert_called_once_with(mock_instance_ironic)
  912. mock_post_config_mistral.assert_called_once_with(
  913. instack_env, mock_instance_mistral, mock_instance_swift)
  914. mock_migrate_to_convergence.assert_called_once_with(mock_heat)
  915. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  916. @mock.patch('instack_undercloud.undercloud._get_session')
  917. @mock.patch('mistralclient.api.client.client', autospec=True)
  918. @mock.patch('swiftclient.client.Connection', autospec=True)
  919. @mock.patch('os_client_config.make_client')
  920. def test_run_validation_groups_success(self,
  921. mock_make_client,
  922. mock_swift_client,
  923. mock_mistral_client,
  924. mock_get_session,
  925. mock_auth_values):
  926. mock_mistral = mock.Mock()
  927. mock_mistral_client.return_value = mock_mistral
  928. mock_mistral.environments.list.return_value = []
  929. mock_mistral.executions.get.return_value = mock.Mock(state="SUCCESS")
  930. mock_get_session.return_value = mock.MagicMock()
  931. mock_auth_values.return_value = ('aturing', '3nigma', 'hut8',
  932. 'http://bletchley:5000/')
  933. mock_instance_swift = mock.Mock()
  934. mock_instance_swift.get_account.return_value = [None,
  935. [{'name': 'hut8'},
  936. {'name': 'mystack'}]]
  937. mock_swift_client.return_value = mock_instance_swift
  938. mock_heat = mock.Mock()
  939. aux_stack = mock.Mock()
  940. aux_stack.stack_name = "mystack"
  941. mock_heat.stacks.list.return_value = [aux_stack]
  942. mock_make_client.return_value = mock_heat
  943. undercloud._run_validation_groups(["post-upgrade"])
  944. mock_mistral.executions.create.assert_called_once_with(
  945. 'tripleo.validations.v1.run_groups',
  946. workflow_input={
  947. 'group_names': ['post-upgrade'],
  948. 'plan': 'mystack',
  949. }
  950. )
  951. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  952. @mock.patch('instack_undercloud.undercloud._get_session')
  953. @mock.patch('mistralclient.api.client.client', autospec=True)
  954. @mock.patch('swiftclient.client.Connection', autospec=True)
  955. @mock.patch('os_client_config.make_client')
  956. def test_run_validation_groups_no_overcloud(self,
  957. mock_make_client,
  958. mock_swift_client,
  959. mock_mistral_client,
  960. mock_get_session,
  961. mock_auth_values):
  962. mock_mistral = mock.Mock()
  963. mock_mistral_client.return_value = mock_mistral
  964. mock_mistral.environments.list.return_value = []
  965. mock_mistral.executions.get.return_value = mock.Mock(state="SUCCESS")
  966. mock_get_session.return_value = mock.MagicMock()
  967. mock_auth_values.return_value = ('aturing', '3nigma', 'hut8',
  968. 'http://bletchley:5000/')
  969. mock_instance_swift = mock.Mock()
  970. mock_instance_swift.get_account.return_value = [None,
  971. [{'name': 'hut8'},
  972. {'name': 'mystack'}]]
  973. mock_swift_client.return_value = mock_instance_swift
  974. mock_heat = mock.Mock()
  975. aux_stack = mock.Mock()
  976. aux_stack.stack_name = "mystackooo"
  977. mock_heat.stacks.list.return_value = [aux_stack]
  978. mock_make_client.return_value = mock_heat
  979. undercloud._run_validation_groups(["post-upgrade"])
  980. mock_mistral.executions.create.assert_not_called()
  981. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  982. @mock.patch('instack_undercloud.undercloud._get_session')
  983. @mock.patch('mistralclient.api.client.client', autospec=True)
  984. @mock.patch('time.strptime')
  985. @mock.patch('swiftclient.client.Connection', autospec=True)
  986. @mock.patch('os_client_config.make_client')
  987. def test_run_validation_groups_fail(self,
  988. mock_make_client,
  989. mock_swift_client,
  990. mock_strptime,
  991. mock_mistral_client, mock_get_session,
  992. mock_auth_values):
  993. mock_mistral = mock.Mock()
  994. mock_mistral_client.return_value = mock_mistral
  995. mock_mistral.environments.list.return_value = []
  996. mock_mistral.executions.get.return_value = mock.Mock(state="FAIL")
  997. mock_mistral.executions.get_output.return_value = "ERROR!"
  998. mock_mistral.executions.get.id = "1234"
  999. mock_mistral.action_executions.find.return_value = []
  1000. mock_auth_values.return_value = ('aturing', '3nigma', 'hut8',
  1001. 'http://bletchley:5000/')
  1002. mock_instance_swift = mock.Mock()
  1003. mock_instance_swift.get_account.return_value = [None,
  1004. [{'name': 'hut8'},
  1005. {'name': 'mystack'}]]
  1006. mock_swift_client.return_value = mock_instance_swift
  1007. mock_heat = mock.Mock()
  1008. aux_stack = mock.Mock()
  1009. aux_stack.stack_name = "mystack"
  1010. mock_heat.stacks.list.return_value = [aux_stack]
  1011. mock_make_client.return_value = mock_heat
  1012. mock_strptime.return_value = time.mktime(time.localtime())
  1013. mock_get_session.return_value = mock.MagicMock()
  1014. self.assertRaises(
  1015. RuntimeError, undercloud._run_validation_groups, ["post-upgrade"],
  1016. "", 360, True)
  1017. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  1018. @mock.patch('instack_undercloud.undercloud._get_session')
  1019. @mock.patch('mistralclient.api.client.client', autospec=True)
  1020. @mock.patch('time.strptime')
  1021. @mock.patch('swiftclient.client.Connection', autospec=True)
  1022. @mock.patch('os_client_config.make_client')
  1023. def test_run_validation_groups_timeout(self,
  1024. mock_make_client,
  1025. mock_swift_client,
  1026. mock_strptime,
  1027. mock_mistral_client,
  1028. mock_get_session, mock_auth_values):
  1029. mock_mistral = mock.Mock()
  1030. mock_mistral_client.return_value = mock_mistral
  1031. mock_mistral.environments.list.return_value = []
  1032. mock_mistral.executions.get.id = "1234"
  1033. mock_mistral.action_executions.find.return_value = []
  1034. mock_instance_swift = mock.Mock()
  1035. mock_auth_values.return_value = ('aturing', '3nigma', 'hut8',
  1036. 'http://bletchley:5000/')
  1037. mock_instance_swift = mock.Mock()
  1038. mock_instance_swift.get_account.return_value = [None,
  1039. [{'name': 'hut8'},
  1040. {'name': 'mystack'}]]
  1041. mock_swift_client.return_value = mock_instance_swift
  1042. mock_heat = mock.Mock()
  1043. aux_stack = mock.Mock()
  1044. aux_stack.stack_name = "mystack"
  1045. mock_heat.stacks.list.return_value = [aux_stack]
  1046. mock_make_client.return_value = mock_heat
  1047. mock_time = mock.MagicMock()
  1048. mock_time.return_value = time.mktime(time.localtime())
  1049. mock_strptime.return_value = time.mktime(time.localtime())
  1050. with mock.patch('time.time', mock_time):
  1051. self.assertRaisesRegexp(RuntimeError, ("TIMEOUT waiting for "
  1052. "execution"),
  1053. undercloud._run_validation_groups,
  1054. ["post-upgrade"], "", -1, True)
  1055. def test_create_default_plan(self):
  1056. mock_mistral = mock.Mock()
  1057. mock_mistral.environments.list.return_value = []
  1058. mock_mistral.executions.get.return_value = mock.Mock(state="SUCCESS")
  1059. undercloud._create_default_plan(mock_mistral, [])
  1060. mock_mistral.executions.create.assert_called_once_with(
  1061. 'tripleo.plan_management.v1.create_deployment_plan',
  1062. workflow_input={
  1063. 'container': 'overcloud',
  1064. 'queue_name': mock.ANY,
  1065. 'use_default_templates': True,
  1066. }
  1067. )
  1068. def test_create_default_plan_existing(self):
  1069. mock_mistral = mock.Mock()
  1070. undercloud._create_default_plan(mock_mistral, ['overcloud'])
  1071. mock_mistral.executions.create.assert_not_called()
  1072. def test_create_config_environment(self):
  1073. mock_mistral = mock.Mock()
  1074. mock_mistral.environments.get.side_effect = (
  1075. ks_exceptions.NotFound)
  1076. env = {
  1077. "UNDERCLOUD_DB_PASSWORD": "root-db-pass",
  1078. "UNDERCLOUD_CEILOMETER_SNMPD_PASSWORD": "snmpd-pass"
  1079. }
  1080. json_string = {
  1081. "undercloud_db_password": "root-db-pass",
  1082. "undercloud_ceilometer_snmpd_password": "snmpd-pass"
  1083. }
  1084. undercloud._create_mistral_config_environment(json.loads(
  1085. json.dumps(env, sort_keys=True)), mock_mistral)
  1086. mock_mistral.environments.create.assert_called_once_with(
  1087. name='tripleo.undercloud-config',
  1088. description='Undercloud configuration parameters',
  1089. variables=json.dumps(json_string, sort_keys=True))
  1090. def test_create_config_environment_existing(self):
  1091. mock_mistral = mock.Mock()
  1092. environment = collections.namedtuple('environment',
  1093. ['name', 'variables'])
  1094. json_string = {
  1095. "undercloud_db_password": "root-db-pass",
  1096. "undercloud_ceilometer_snmpd_password": "snmpd-pass"
  1097. }
  1098. mock_mistral.environments.get.return_value = environment(
  1099. name='tripleo.undercloud-config',
  1100. variables=json.loads(json.dumps(json_string, sort_keys=True))
  1101. )
  1102. env = {
  1103. "UNDERCLOUD_CEILOMETER_SNMPD_PASSWORD": "snmpd-pass",
  1104. "UNDERCLOUD_DB_PASSWORD": "root-db-pass"
  1105. }
  1106. undercloud._create_mistral_config_environment(json.loads(
  1107. json.dumps(env, sort_keys=True)), mock_mistral)
  1108. mock_mistral.executions.create.assert_not_called()
  1109. def test_prepare_ssh_environment(self):
  1110. mock_mistral = mock.Mock()
  1111. undercloud._prepare_ssh_environment(mock_mistral)
  1112. mock_mistral.executions.create.assert_called_once_with(
  1113. 'tripleo.validations.v1.copy_ssh_key')
  1114. @mock.patch('time.sleep')
  1115. def test_create_default_plan_timeout(self, mock_sleep):
  1116. mock_mistral = mock.Mock()
  1117. mock_mistral.executions.get.return_value = mock.Mock(state="RUNNING")
  1118. self.assertRaises(
  1119. RuntimeError,
  1120. undercloud._create_default_plan, mock_mistral, [], timeout=0)
  1121. @mock.patch('time.strptime')
  1122. def test_create_default_plan_failed(self, mock_strptime):
  1123. mock_mistral = mock.Mock()
  1124. mock_mistral.executions.get.return_value = mock.Mock(state="ERROR")
  1125. mock_mistral.action_executions.find.return_value = []
  1126. mock_strptime.return_value = time.mktime(time.localtime())
  1127. self.assertRaises(
  1128. RuntimeError,
  1129. undercloud._create_default_plan, mock_mistral, [])
  1130. @mock.patch('instack_undercloud.undercloud._run_command')
  1131. def test_copy_stackrc(self, mock_run):
  1132. undercloud._copy_stackrc()
  1133. calls = [mock.call(['sudo', 'cp', '/root/stackrc', mock.ANY],
  1134. name='Copy stackrc'),
  1135. mock.call(['sudo', 'chown', mock.ANY, mock.ANY],
  1136. name='Chown stackrc'),
  1137. ]
  1138. mock_run.assert_has_calls(calls)
  1139. def _mock_ksclient_roles(self, mock_auth_values, mock_ksdiscover, roles):
  1140. mock_auth_values.return_value = ('user', 'password',
  1141. 'project', 'http://test:123')
  1142. mock_discover = mock.Mock()
  1143. mock_ksdiscover.return_value = mock_discover
  1144. mock_client = mock.Mock()
  1145. mock_roles = mock.Mock()
  1146. mock_role_list = []
  1147. for role in roles:
  1148. mock_role = mock.Mock()
  1149. mock_role.name = role
  1150. mock_role_list.append(mock_role)
  1151. mock_roles.list.return_value = mock_role_list
  1152. mock_client.roles = mock_roles
  1153. mock_discover.create_client.return_value = mock_client
  1154. mock_client.version = 'v3'
  1155. mock_project_list = [mock.Mock(), mock.Mock()]
  1156. mock_project_list[0].name = 'admin'
  1157. mock_project_list[0].id = 'admin-id'
  1158. mock_project_list[1].name = 'service'
  1159. mock_project_list[1].id = 'service-id'
  1160. mock_client.projects.list.return_value = mock_project_list
  1161. mock_user_list = [mock.Mock(), mock.Mock()]
  1162. mock_user_list[0].name = 'admin'
  1163. mock_user_list[1].name = 'nova'
  1164. mock_client.users.list.return_value = mock_user_list
  1165. return mock_client
  1166. @mock.patch('keystoneclient.discover.Discover')
  1167. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  1168. @mock.patch('os.path.isfile')
  1169. def test_member_role_exists(self, mock_isfile, mock_auth_values,
  1170. mock_ksdiscover):
  1171. mock_isfile.return_value = True
  1172. mock_client = self._mock_ksclient_roles(mock_auth_values,
  1173. mock_ksdiscover,
  1174. ['admin'])
  1175. undercloud._member_role_exists()
  1176. self.assertFalse(mock_client.projects.list.called)
  1177. @mock.patch('keystoneclient.discover.Discover')
  1178. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  1179. @mock.patch('os.path.isfile')
  1180. def test_member_role_exists_true(self, mock_isfile,
  1181. mock_auth_values, mock_ksdiscover):
  1182. mock_isfile.return_value = True
  1183. mock_client = self._mock_ksclient_roles(mock_auth_values,
  1184. mock_ksdiscover,
  1185. ['admin', '_member_'])
  1186. undercloud._member_role_exists()
  1187. mock_user = mock_client.users.list.return_value[0]
  1188. mock_role = mock_client.roles.list.return_value[1]
  1189. mock_client.roles.grant.assert_called_once_with(
  1190. mock_role, user=mock_user, project='admin-id')
  1191. @mock.patch('keystoneclient.discover.Discover')
  1192. @mock.patch('instack_undercloud.undercloud._get_auth_values')
  1193. @mock.patch('os.path.isfile')
  1194. def test_has_member_role(self, mock_isfile, mock_auth_values,
  1195. mock_ksdiscover):
  1196. mock_isfile.return_value = True
  1197. mock_client = self._mock_ksclient_roles(mock_auth_values,
  1198. mock_ksdiscover,
  1199. ['admin', '_member_'])
  1200. fake_exception = ks_exceptions.http.Conflict('test')
  1201. mock_client.roles.grant.side_effect = fake_exception
  1202. undercloud._member_role_exists()
  1203. mock_user = mock_client.users.list.return_value[0]
  1204. mock_role = mock_client.roles.list.return_value[1]
  1205. mock_client.roles.grant.assert_called_once_with(
  1206. mock_role, user=mock_user, project='admin-id')
  1207. def _create_flavor_mocks(self):
  1208. mock_nova = mock.Mock()
  1209. mock_nova.flavors.create = mock.Mock()
  1210. mock_flavor = mock.Mock()
  1211. mock_nova.flavors.create.return_value = mock_flavor
  1212. mock_flavor.set_keys = mock.Mock()
  1213. return mock_nova, mock_flavor
  1214. def test_ensure_flavor_no_profile(self):
  1215. mock_nova, mock_flavor = self._create_flavor_mocks()
  1216. undercloud._ensure_flavor(mock_nova, None, 'test')
  1217. mock_nova.flavors.create.assert_called_with('test', 4096, 1, 40)
  1218. keys = {'capabilities:boot_option': 'local',
  1219. 'resources:CUSTOM_BAREMETAL': '1',
  1220. 'resources:DISK_GB': '0',
  1221. 'resources:MEMORY_MB': '0',
  1222. 'resources:VCPU': '0'}
  1223. mock_flavor.set_keys.assert_called_with(keys)
  1224. def test_ensure_flavor_profile(self):
  1225. mock_nova, mock_flavor = self._create_flavor_mocks()
  1226. undercloud._ensure_flavor(mock_nova, None, 'test', 'test')
  1227. mock_nova.flavors.create.assert_called_with('test', 4096, 1, 40)
  1228. keys = {'capabilities:boot_option': 'local',
  1229. 'capabilities:profile': 'test',
  1230. 'resources:CUSTOM_BAREMETAL': '1',
  1231. 'resources:DISK_GB': '0',
  1232. 'resources:MEMORY_MB': '0',
  1233. 'resources:VCPU': '0'}
  1234. mock_flavor.set_keys.assert_called_with(keys)
  1235. def test_ensure_flavor_exists(self):
  1236. mock_nova, mock_flavor = self._create_flavor_mocks()
  1237. mock_nova.flavors.create.side_effect = exceptions.Conflict(None)
  1238. flavor = mock.Mock(spec=['name', 'get_keys', 'set_keys'])
  1239. flavor.get_keys.return_value = {'foo': 'bar'}
  1240. undercloud._ensure_flavor(mock_nova, flavor, 'test')
  1241. keys = {'foo': 'bar',
  1242. 'resources:CUSTOM_BAREMETAL': '1',
  1243. 'resources:DISK_GB': '0',
  1244. 'resources:MEMORY_MB': '0',
  1245. 'resources:VCPU': '0'}
  1246. flavor.set_keys.assert_called_with(keys)
  1247. mock_nova.flavors.create.assert_not_called()
  1248. @mock.patch.object(undercloud.LOG, 'warning', autospec=True)
  1249. def test_ensure_flavor_exists_conflicting_rc(self, mock_warn):
  1250. mock_nova, mock_flavor = self._create_flavor_mocks()
  1251. mock_nova.flavors.create.side_effect = exceptions.Conflict(None)
  1252. flavor = mock.Mock(spec=['name', 'get_keys', 'set_keys'])
  1253. flavor.get_keys.return_value = {'foo': 'bar',
  1254. 'resources:CUSTOM_FOO': '42'}
  1255. undercloud._ensure_flavor(mock_nova, flavor, 'test')
  1256. flavor.set_keys.assert_not_called()
  1257. mock_warn.assert_called_once_with(mock.ANY, flavor.name,
  1258. 'resources:CUSTOM_FOO')
  1259. mock_nova.flavors.create.assert_not_called()
  1260. def test_ensure_node_resource_classes(self):
  1261. nodes = [mock.Mock(uuid='1', resource_class=None),
  1262. mock.Mock(uuid='2', resource_class='foobar')]
  1263. ironic_mock = mock.Mock()
  1264. ironic_mock.node.list.return_value = nodes
  1265. undercloud._ensure_node_resource_classes(ironic_mock)
  1266. ironic_mock.node.update.assert_called_once_with(
  1267. '1', [{'path': '/resource_class', 'op': 'add',
  1268. 'value': 'baremetal'}])
  1269. @mock.patch('instack_undercloud.undercloud._run_command')
  1270. def test_migrate_to_convergence(self, mock_run_command):
  1271. stacks = [mock.Mock(id='1'), mock.Mock(id='2')]
  1272. mock_heat = mock.Mock()
  1273. mock_heat.stacks.list.return_value = stacks
  1274. undercloud._migrate_to_convergence(mock_heat)
  1275. self.assertEqual([mock.call(['sudo', '-E', 'heat-manage',
  1276. 'migrate_convergence_1', '1'],
  1277. name='heat-manage'),
  1278. mock.call(['sudo', '-E', 'heat-manage',
  1279. 'migrate_convergence_1', '2'],
  1280. name='heat-manage')],
  1281. mock_run_command.mock_calls)
  1282. @mock.patch('instack_undercloud.undercloud._run_command')
  1283. def test_migrate_to_convergence_no_stacks(self, mock_run_command):
  1284. stacks = []
  1285. mock_heat = mock.Mock()
  1286. mock_heat.stacks.list.return_value = stacks
  1287. undercloud._migrate_to_convergence(mock_heat)
  1288. mock_run_command.assert_not_called()
  1289. @mock.patch('instack_undercloud.undercloud._extract_from_stackrc')
  1290. @mock.patch('instack_undercloud.undercloud._run_command')
  1291. def test_get_auth_values(self, mock_run, mock_extract):
  1292. mock_run.return_value = '3nigma'
  1293. mock_extract.side_effect = ['aturing', 'hut8',
  1294. 'http://bletchley:5000/v2.0']
  1295. values = undercloud._get_auth_values()
  1296. expected = ('aturing', '3nigma', 'hut8', 'http://bletchley:5000/v2.0')
  1297. self.assertEqual(expected, values)
  1298. def test_delete_default_flavors(self):
  1299. class FakeFlavor(object):
  1300. def __init__(self, id_, name):
  1301. self.id = id_
  1302. self.name = name
  1303. mock_instance = mock.Mock()
  1304. mock_flavors = [FakeFlavor('f00', 'foo'),
  1305. FakeFlavor('8ar', 'm1.large')]
  1306. mock_instance.flavors.list.return_value = mock_flavors
  1307. undercloud._delete_default_flavors(mock_instance)
  1308. mock_instance.flavors.delete.assert_called_once_with('8ar')
  1309. @mock.patch('os.path.isfile', return_value=True)
  1310. @mock.patch('os.listdir')
  1311. @mock.patch('instack_undercloud.undercloud._create_mistral_config_'
  1312. 'environment')
  1313. @mock.patch('instack_undercloud.undercloud._create_default_plan')
  1314. def test_post_config_mistral(self, mock_create, mock_cmce, mock_listdir,
  1315. mock_isfile):
  1316. instack_env = {}
  1317. mock_mistral = mock.Mock()
  1318. mock_swift = mock.Mock()
  1319. mock_swift.get_account.return_value = [None, [{'name': 'hut8'}]]
  1320. mock_workbooks = [mock.Mock() for m in range(2)]
  1321. mock_workbooks[0].name = 'foo'
  1322. mock_workbooks[1].name = 'tripleo.bar'
  1323. mock_mistral.workbooks.list.return_value = mock_workbooks
  1324. mock_triggers = [mock.Mock() for m in range(2)]
  1325. mock_triggers[0].name = 'foobar'
  1326. mock_triggers[0].workflow_name = 'foo'
  1327. mock_triggers[1].name = 'delete_me'
  1328. mock_triggers[1].workflow_name = 'tripleo.bar'
  1329. mock_mistral.cron_triggers.list.return_value = mock_triggers
  1330. mock_workflows = [mock.Mock() for m in range(2)]
  1331. mock_workflows[0].name = 'foo'
  1332. mock_workflows[1].name = 'tripleo.bar'
  1333. mock_workflows[0].tags = []
  1334. mock_workflows[1].tags = []
  1335. mock_mistral.workflows.list.return_value = mock_workflows
  1336. mock_listdir.return_value = ['foo.yaml', 'bar.yaml']
  1337. undercloud._post_config_mistral(instack_env, mock_mistral, mock_swift)
  1338. self.assertEqual([mock.call('tripleo.bar')],
  1339. mock_mistral.workbooks.delete.mock_calls)
  1340. self.assertEqual([mock.call('tripleo.bar')],
  1341. mock_mistral.workflows.delete.mock_calls)
  1342. self.assertEqual([mock.call('delete_me')],
  1343. mock_mistral.cron_triggers.delete.mock_calls)
  1344. self.assertEqual([mock.call(undercloud.PATHS.WORKBOOK_PATH +
  1345. '/foo.yaml'),
  1346. mock.call(undercloud.PATHS.WORKBOOK_PATH +
  1347. '/bar.yaml')],
  1348. mock_mistral.workbooks.create.mock_calls)
  1349. mock_cmce.assert_called_once_with(instack_env, mock_mistral)
  1350. mock_create.assert_called_once_with(mock_mistral, ['hut8'],
  1351. timeout=1200)
  1352. @mock.patch('os.path.isfile', return_value=True)
  1353. @mock.patch('os.listdir')
  1354. @mock.patch('instack_undercloud.undercloud._create_mistral_config_'
  1355. 'environment')
  1356. @mock.patch('instack_undercloud.undercloud._create_default_plan')
  1357. def test_post_config_mistral_with_tags(self, mock_create, mock_cmce,
  1358. mock_listdir, mock_isfile):
  1359. instack_env = {}
  1360. mock_mistral = mock.Mock()
  1361. mock_swift = mock.Mock()
  1362. mock_swift.get_account.return_value = [None, [{'name': 'hut8'}]]
  1363. mock_workbooks = [mock.Mock() for m in range(2)]
  1364. mock_workbooks[0].name = 'foo'
  1365. mock_workbooks[1].name = 'tripleo.bar'
  1366. mock_mistral.workbooks.list.return_value = mock_workbooks
  1367. mock_triggers = [mock.Mock() for m in range(2)]
  1368. mock_triggers[0].name = 'dont_delete_me'
  1369. mock_triggers[0].workflow_name = 'tripleo.foo'
  1370. mock_triggers[1].name = 'delete_me'
  1371. mock_triggers[1].workflow_name = 'tripleo.bar'
  1372. mock_mistral.cron_triggers.list.return_value = mock_triggers
  1373. mock_workflows = [mock.Mock() for m in range(2)]
  1374. mock_workflows[0].name = 'tripleo.foo'
  1375. mock_workflows[1].name = 'tripleo.bar'
  1376. mock_workflows[0].tags = []
  1377. mock_workflows[1].tags = ['tripleo-common-managed', ]
  1378. mock_mistral.workflows.list.return_value = mock_workflows
  1379. mock_listdir.return_value = ['foo.yaml', 'bar.yaml']
  1380. undercloud._post_config_mistral(instack_env, mock_mistral, mock_swift)
  1381. self.assertEqual([mock.call('tripleo.bar')],
  1382. mock_mistral.workbooks.delete.mock_calls)
  1383. self.assertEqual([mock.call('tripleo.bar')],
  1384. mock_mistral.workflows.delete.mock_calls)
  1385. self.assertEqual([mock.call('delete_me')],
  1386. mock_mistral.cron_triggers.delete.mock_calls)
  1387. self.assertEqual([mock.call(undercloud.PATHS.WORKBOOK_PATH +
  1388. '/foo.yaml'),
  1389. mock.call(undercloud.PATHS.WORKBOOK_PATH +
  1390. '/bar.yaml')],
  1391. mock_mistral.workbooks.create.mock_calls)
  1392. mock_cmce.assert_called_once_with(instack_env, mock_mistral)
  1393. mock_create.assert_called_once_with(mock_mistral, ['hut8'],
  1394. timeout=1200)
  1395. def _neutron_mocks(self):
  1396. mock_sdk = mock.MagicMock()
  1397. mock_sdk.network.create_network = mock.Mock()
  1398. mock_sdk.network.create_segment = mock.Mock()
  1399. mock_sdk.network.update_segment = mock.Mock()
  1400. mock_sdk.network.delete_segment = mock.Mock()
  1401. mock_sdk.network.create_subnet = mock.Mock()
  1402. mock_sdk.network.update_subnet = mock.Mock()
  1403. return mock_sdk
  1404. def test_network_create(self):
  1405. mock_sdk = self._neutron_mocks()
  1406. mock_sdk.network.networks.return_value = iter([])
  1407. segment_mock = mock.Mock()
  1408. mock_sdk.network.segments.return_value = iter([segment_mock])
  1409. undercloud._ensure_neutron_network(mock_sdk)
  1410. mock_sdk.network.create_network.assert_called_with(
  1411. name='ctlplane', provider_network_type='flat',
  1412. provider_physical_network='ctlplane', mtu=1500)
  1413. def test_delete_default_segment(self):
  1414. mock_sdk = self._neutron_mocks()
  1415. mock_sdk.network.networks.return_value = iter([])
  1416. segment_mock = mock.Mock()
  1417. mock_sdk.network.segments.return_value = iter([segment_mock])
  1418. undercloud._ensure_neutron_network(mock_sdk)
  1419. mock_sdk.network.delete_segment.assert_called_with(
  1420. segment_mock.id)
  1421. def test_network_exists(self):
  1422. mock_sdk = self._neutron_mocks()
  1423. mock_sdk.network.networks.return_value = iter(['ctlplane'])
  1424. undercloud._ensure_neutron_network(mock_sdk)
  1425. mock_sdk.network.create_network.assert_not_called()
  1426. def test_segment_create(self):
  1427. mock_sdk = self._neutron_mocks()
  1428. undercloud._neutron_segment_create(mock_sdk, 'ctlplane-subnet',
  1429. 'network_id', 'ctlplane')
  1430. mock_sdk.network.create_segment.assert_called_with(
  1431. name='ctlplane-subnet', network_id='network_id',
  1432. physical_network='ctlplane', network_type='flat')
  1433. def test_segment_update(self):
  1434. mock_sdk = self._neutron_mocks()
  1435. undercloud._neutron_segment_update(mock_sdk,
  1436. 'network_id', 'ctlplane-subnet')
  1437. mock_sdk.network.update_segment.assert_called_with(
  1438. 'network_id', name='ctlplane-subnet')
  1439. def test_subnet_create(self):
  1440. mock_sdk = self._neutron_mocks()
  1441. host_routes = [{'destination': '169.254.169.254/32',
  1442. 'nexthop': '192.168.24.1'}]
  1443. allocation_pool = [{'start': '192.168.24.5', 'end': '192.168.24.24'}]
  1444. undercloud._neutron_subnet_create(mock_sdk, 'network_id',
  1445. '192.168.24.0/24', '192.168.24.1',
  1446. host_routes, allocation_pool,
  1447. 'ctlplane-subnet', 'segment_id')
  1448. mock_sdk.network.create_subnet.assert_called_with(
  1449. name='ctlplane-subnet', cidr='192.168.24.0/24',
  1450. gateway_ip='192.168.24.1', host_routes=host_routes, enable_dhcp=True,
  1451. ip_version='4', allocation_pools=allocation_pool,
  1452. network_id='network_id', segment_id='segment_id')
  1453. def test_subnet_update(self):
  1454. mock_sdk = self._neutron_mocks()
  1455. host_routes = [{'destination': '169.254.169.254/32',
  1456. 'nexthop': '192.168.24.1'}]
  1457. allocation_pool = [{'start': '192.168.24.5', 'end': '192.168.24.24'}]
  1458. undercloud._neutron_subnet_update(mock_sdk, 'subnet_id',
  1459. '192.168.24.1', host_routes,
  1460. allocation_pool, 'ctlplane-subnet')
  1461. mock_sdk.network.update_subnet.assert_called_with(
  1462. 'subnet_id', name='ctlplane-subnet', gateway_ip='192.168.24.1',
  1463. host_routes=host_routes, allocation_pools=allocation_pool)
  1464. @mock.patch('instack_undercloud.undercloud._neutron_subnet_update')
  1465. @mock.patch('instack_undercloud.undercloud._get_subnet')
  1466. def test_no_neutron_segments_if_pre_segments_undercloud(
  1467. self, mock_get_subnet, mock_neutron_subnet_update):
  1468. mock_sdk = self._neutron_mocks()
  1469. mock_subnet = mock.Mock()
  1470. mock_subnet.segment_id = None
  1471. mock_get_subnet.return_value = mock_subnet
  1472. undercloud._config_neutron_segments_and_subnets(mock_sdk,
  1473. 'ctlplane_id')
  1474. mock_sdk.network.create_segment.assert_not_called()
  1475. mock_sdk.network.update_segment.assert_not_called()
  1476. mock_neutron_subnet_update.called_once()
  1477. @mock.patch('instack_undercloud.undercloud._neutron_segment_create')
  1478. @mock.patch('instack_undercloud.undercloud._neutron_subnet_create')
  1479. @mock.patch('instack_undercloud.undercloud._get_segment')
  1480. @mock.patch('instack_undercloud.undercloud._get_subnet')
  1481. def test_segment_and_subnet_create(self, mock_get_subnet, mock_get_segment,
  1482. mock_neutron_subnet_create,
  1483. mock_neutron_segment_create):
  1484. mock_sdk = self._neutron_mocks()
  1485. mock_get_subnet.return_value = None
  1486. mock_get_segment.return_value = None
  1487. undercloud._config_neutron_segments_and_subnets(mock_sdk,
  1488. 'ctlplane_id')
  1489. mock_neutron_segment_create.assert_called_with(
  1490. mock_sdk, 'ctlplane-subnet', 'ctlplane_id', 'ctlplane')
  1491. host_routes = [{'destination': '169.254.169.254/32',
  1492. 'nexthop': '192.168.24.1'}]
  1493. allocation_pool = [{'start': '192.168.24.5', 'end': '192.168.24.24'}]
  1494. mock_neutron_subnet_create.assert_called_with(
  1495. mock_sdk, 'ctlplane_id', '192.168.24.0/24', '192.168.24.1',
  1496. host_routes, allocation_pool, 'ctlplane-subnet',
  1497. mock_neutron_segment_create().id)
  1498. @mock.patch('instack_undercloud.undercloud._neutron_segment_update')
  1499. @mock.patch('instack_undercloud.undercloud._neutron_subnet_update')
  1500. @mock.patch('instack_undercloud.undercloud._get_segment')
  1501. @mock.patch('instack_undercloud.undercloud._get_subnet')
  1502. def test_segment_and_subnet_update(self, mock_get_subnet, mock_get_segment,
  1503. mock_neutron_subnet_update,
  1504. mock_neutron_segment_update):
  1505. mock_sdk = self._neutron_mocks()
  1506. mock_subnet = mock.Mock()
  1507. mock_subnet.id = 'subnet_id'
  1508. mock_subnet.segment_id = 'segment_id'
  1509. mock_get_subnet.return_value = mock_subnet
  1510. mock_segment = mock.Mock()
  1511. mock_get_segment.return_value = mock_segment
  1512. mock_segment.id = 'segment_id'
  1513. undercloud._config_neutron_segments_and_subnets(mock_sdk,
  1514. 'ctlplane_id')
  1515. mock_neutron_segment_update.assert_called_with(
  1516. mock_sdk, mock_subnet.segment_id, 'ctlplane-subnet')
  1517. host_routes = [{'destination': '169.254.169.254/32',
  1518. 'nexthop': '192.168.24.1'}]
  1519. allocation_pool = [{'start': '192.168.24.5', 'end': '192.168.24.24'}]
  1520. mock_neutron_subnet_update.assert_called_with(
  1521. mock_sdk, 'subnet_id', '192.168.24.1', host_routes,
  1522. allocation_pool, 'ctlplane-subnet')
  1523. @mock.patch('instack_undercloud.undercloud._get_segment')
  1524. @mock.patch('instack_undercloud.undercloud._get_subnet')
  1525. def test_local_subnet_cidr_conflict(self, mock_get_subnet,
  1526. mock_get_segment):
  1527. mock_sdk = self._neutron_mocks()
  1528. mock_sdk = self._neutron_mocks()
  1529. mock_subnet = mock.Mock()
  1530. mock_subnet.id = 'subnet_id'
  1531. mock_subnet.segment_id = 'existing_segment_id'
  1532. mock_get_subnet.return_value = mock_subnet
  1533. mock_segment = mock.Mock()
  1534. mock_get_segment.return_value = mock_segment
  1535. mock_segment.id = 'segment_id'
  1536. self.assertRaises(
  1537. RuntimeError,
  1538. undercloud._config_neutron_segments_and_subnets, [mock_sdk],
  1539. ['ctlplane_id'])
  1540. class TestUpgradeFact(base.BaseTestCase):
  1541. @mock.patch('instack_undercloud.undercloud._run_command')
  1542. @mock.patch('os.path.dirname')
  1543. @mock.patch('os.path.exists')
  1544. @mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file'))
  1545. def test_upgrade_fact(self, mock_mkstemp, mock_exists, mock_dirname,
  1546. mock_run):
  1547. fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt'
  1548. mock_dirname.return_value = '/etc/facter/facts.d'
  1549. mock_exists.side_effect = [False, True]
  1550. with mock.patch('instack_undercloud.undercloud.open') as mock_open:
  1551. undercloud._handle_upgrade_fact(True)
  1552. mock_open.assert_called_with('/tmp/file', 'w')
  1553. run_calls = [
  1554. mock.call(['sudo', 'mkdir', '-p', '/etc/facter/facts.d']),
  1555. mock.call(['sudo', 'mv', '/tmp/file', fact_path]),
  1556. mock.call(['sudo', 'chmod', '0644', fact_path])
  1557. ]
  1558. mock_run.assert_has_calls(run_calls)
  1559. self.assertEqual(mock_run.call_count, 3)
  1560. @mock.patch('instack_undercloud.undercloud._run_command')
  1561. @mock.patch('os.path.dirname')
  1562. @mock.patch('os.path.exists')
  1563. @mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file'))
  1564. def test_upgrade_fact_install(self, mock_mkstemp, mock_exists,
  1565. mock_dirname, mock_run):
  1566. mock_dirname.return_value = '/etc/facter/facts.d'
  1567. mock_exists.return_value = False
  1568. with mock.patch('instack_undercloud.undercloud.open') as mock_open:
  1569. undercloud._handle_upgrade_fact(False)
  1570. mock_open.assert_not_called()
  1571. mock_run.assert_not_called()
  1572. @mock.patch('instack_undercloud.undercloud._run_command')
  1573. @mock.patch('os.path.dirname')
  1574. @mock.patch('os.path.exists')
  1575. @mock.patch.object(tempfile, 'mkstemp', return_value=(1, '/tmp/file'))
  1576. def test_upgrade_fact_upgrade_after_install(self, mock_mkstemp,
  1577. mock_exists, mock_dirname,
  1578. mock_run):
  1579. fact_path = '/etc/facter/facts.d/undercloud_upgrade.txt'
  1580. mock_dirname.return_value = '/etc/facter/facts.d'
  1581. mock_exists.return_value = True
  1582. with mock.patch('instack_undercloud.undercloud.open') as open_m:
  1583. undercloud._handle_upgrade_fact(True)
  1584. open_m.assert_called_with('/tmp/file', 'w')
  1585. run_calls = [
  1586. mock.call(['sudo', 'mv', '/tmp/file', fact_path]),
  1587. mock.call(['sudo', 'chmod', '0644', fact_path])
  1588. ]
  1589. mock_run.assert_has_calls(run_calls)
  1590. self.assertEqual(mock_run.call_count, 2)
  1591. class TestInstackEnvironment(BaseTestCase):
  1592. def test_set_allowed_keys(self):
  1593. env = undercloud.InstackEnvironment()
  1594. env['HOSTNAME'] = 'localhost1'
  1595. env['INSPECTION_COLLECTORS'] = 'a,b,c'
  1596. def test_set_unknown_keys(self):
  1597. env = undercloud.InstackEnvironment()
  1598. def _set():
  1599. env['CATS_AND_DOGS_PATH'] = '/home'
  1600. self.assertRaisesRegex(KeyError, 'CATS_AND_DOGS_PATH', _set)
  1601. def test_get_always_allowed(self):
  1602. env = undercloud.InstackEnvironment()
  1603. env.get('HOSTNAME')
  1604. env.get('CATS_AND_DOGS_PATH')