Juju Charm - Nova Cloud Controller
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.

test_nova_cc_hooks.py 50KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  1. # Copyright 2016 Canonical Ltd
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain 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,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import os
  15. import tempfile
  16. import yaml
  17. from mock import MagicMock, patch, call
  18. from test_utils import CharmTestCase
  19. with patch('charmhelpers.core.hookenv.config') as config:
  20. with patch('charmhelpers.contrib.openstack.utils.get_os_codename_package'):
  21. config.return_value = 'neutron'
  22. import nova_cc_utils as utils
  23. _reg = utils.register_configs
  24. _map = utils.restart_map
  25. utils.register_configs = MagicMock()
  26. utils.restart_map = MagicMock()
  27. with patch('charmhelpers.contrib.hardening.harden.harden') as mock_dec:
  28. mock_dec.side_effect = (lambda *dargs, **dkwargs: lambda f:
  29. lambda *args, **kwargs: f(*args, **kwargs))
  30. with patch('nova_cc_utils.guard_map') as gmap:
  31. with patch('charmhelpers.core.hookenv.config') as config:
  32. config.return_value = False
  33. gmap.return_value = {}
  34. import nova_cc_hooks as hooks
  35. utils.register_configs = _reg
  36. utils.restart_map = _map
  37. TO_PATCH = [
  38. 'apt_update',
  39. 'apt_install',
  40. 'configure_installation_source',
  41. 'charm_dir',
  42. 'do_openstack_upgrade',
  43. 'openstack_upgrade_available',
  44. 'cmd_all_services',
  45. 'config',
  46. 'config_value_changed',
  47. 'determine_endpoints',
  48. 'determine_packages',
  49. 'determine_ports',
  50. 'disable_services',
  51. 'enable_services',
  52. 'NovaCellContext',
  53. 'open_port',
  54. 'is_relation_made',
  55. 'local_unit',
  56. 'log',
  57. 'os_release',
  58. 'related_units',
  59. 'relation_get',
  60. 'relation_set',
  61. 'relation_ids',
  62. 'placement_api_enabled',
  63. 'ssh_compute_add',
  64. 'ssh_known_hosts_lines',
  65. 'ssh_authorized_keys_lines',
  66. 'save_script_rc',
  67. 'service_reload',
  68. 'services',
  69. 'execd_preinstall',
  70. 'network_manager',
  71. 'unit_get',
  72. 'uuid',
  73. 'is_leader',
  74. 'keystone_ca_cert_b64',
  75. 'migrate_nova_databases',
  76. 'uuid',
  77. 'get_hacluster_config',
  78. 'get_iface_for_address',
  79. 'get_netmask_for_address',
  80. 'update_nrpe_config',
  81. 'git_install',
  82. 'git_install_requested',
  83. 'status_set',
  84. 'update_dns_ha_resource_params',
  85. 'serial_console_settings',
  86. 'get_relation_ip',
  87. 'is_clustered',
  88. ]
  89. FAKE_KS_AUTH_CFG = {
  90. 'auth_host': 'kshost',
  91. 'auth_port': '5000',
  92. 'service_port': 'token',
  93. 'service_username': 'admin_user',
  94. 'service_password': 'admin_passwd',
  95. 'service_tenant_name': 'admin_tenant',
  96. 'auth_uri': 'http://kshost:5000/v2',
  97. # quantum-gateway interface deviates a bit.
  98. 'keystone_host': 'kshost',
  99. 'service_tenant': 'service_tenant',
  100. }
  101. class NovaCCHooksTests(CharmTestCase):
  102. def setUp(self):
  103. super(NovaCCHooksTests, self).setUp(hooks, TO_PATCH)
  104. (tmpfd, hooks.NOVA_CONSOLEAUTH_OVERRIDE) = tempfile.mkstemp()
  105. self.config.side_effect = self.test_config.get
  106. self.relation_get.side_effect = self.test_relation.get
  107. self.charm_dir.return_value = '/var/lib/juju/charms/nova/charm'
  108. def tearDown(self):
  109. try:
  110. os.remove(hooks.NOVA_CONSOLEAUTH_OVERRIDE)
  111. except OSError:
  112. pass
  113. super(NovaCCHooksTests, self).tearDown()
  114. def test_install_hook(self):
  115. self.determine_packages.return_value = [
  116. 'nova-scheduler', 'nova-api-ec2']
  117. self.determine_ports.return_value = [80, 81, 82]
  118. hooks.install()
  119. self.apt_install.assert_called_with(
  120. ['nova-scheduler', 'nova-api-ec2'], fatal=True)
  121. self.assertTrue(self.execd_preinstall.called)
  122. self.assertTrue(self.disable_services.called)
  123. self.cmd_all_services.assert_called_with('stop')
  124. def test_install_hook_git(self):
  125. self.git_install_requested.return_value = True
  126. self.determine_packages.return_value = ['foo', 'bar']
  127. self.determine_ports.return_value = [80, 81, 82]
  128. repo = 'cloud:trusty-juno'
  129. openstack_origin_git = {
  130. 'repositories': [
  131. {'name': 'requirements',
  132. 'repository': 'git://git.openstack.org/openstack/requirements', # noqa
  133. 'branch': 'stable/juno'},
  134. {'name': 'nova',
  135. 'repository': 'git://git.openstack.org/openstack/nova',
  136. 'branch': 'stable/juno'}
  137. ],
  138. 'directory': '/mnt/openstack-git',
  139. }
  140. projects_yaml = yaml.dump(openstack_origin_git)
  141. self.test_config.set('openstack-origin', repo)
  142. self.test_config.set('openstack-origin-git', projects_yaml)
  143. hooks.install()
  144. self.git_install.assert_called_with(projects_yaml)
  145. self.apt_install.assert_called_with(['foo', 'bar'], fatal=True)
  146. self.assertTrue(self.execd_preinstall.called)
  147. self.assertTrue(self.disable_services.called)
  148. self.cmd_all_services.assert_called_with('stop')
  149. @patch.object(hooks, 'update_aws_compat_services')
  150. @patch.object(hooks, 'update_nova_consoleauth_config')
  151. @patch.object(hooks, 'is_db_initialised')
  152. @patch.object(hooks, 'determine_packages')
  153. @patch.object(utils, 'service_resume')
  154. @patch.object(utils, 'config')
  155. @patch.object(hooks, 'filter_installed_packages')
  156. @patch.object(hooks, 'configure_https')
  157. def test_config_changed_no_upgrade(self, conf_https, mock_filter_packages,
  158. utils_config, mock_service_resume,
  159. mock_determine_packages,
  160. mock_is_db_initialised,
  161. mock_update_nova_consoleauth_config,
  162. mock_update_aws_compat_services):
  163. mock_determine_packages.return_value = []
  164. utils_config.side_effect = self.test_config.get
  165. self.test_config.set('console-access-protocol', 'dummy')
  166. self.git_install_requested.return_value = False
  167. self.openstack_upgrade_available.return_value = False
  168. mock_is_db_initialised.return_value = False
  169. self.os_release.return_value = 'diablo'
  170. hooks.config_changed()
  171. self.assertTrue(self.save_script_rc.called)
  172. mock_filter_packages.assert_called_with([])
  173. self.assertTrue(mock_update_nova_consoleauth_config.called)
  174. self.assertTrue(mock_update_aws_compat_services.called)
  175. @patch.object(hooks, 'update_aws_compat_services')
  176. @patch.object(hooks, 'update_nova_consoleauth_config')
  177. @patch.object(hooks, 'is_db_initialised')
  178. @patch.object(utils, 'service_resume')
  179. @patch.object(hooks, 'configure_https')
  180. def test_config_changed_git(self, configure_https, mock_service_resume,
  181. mock_is_db_initialised,
  182. mock_update_nova_consoleauth_config,
  183. mock_update_aws_compat_services):
  184. self.git_install_requested.return_value = True
  185. repo = 'cloud:trusty-juno'
  186. openstack_origin_git = {
  187. 'repositories': [
  188. {'name': 'requirements',
  189. 'repository':
  190. 'git://git.openstack.org/openstack/requirements',
  191. 'branch': 'stable/juno'},
  192. {'name': 'nova',
  193. 'repository': 'git://git.openstack.org/openstack/nova',
  194. 'branch': 'stable/juno'}
  195. ],
  196. 'directory': '/mnt/openstack-git',
  197. }
  198. projects_yaml = yaml.dump(openstack_origin_git)
  199. self.test_config.set('openstack-origin', repo)
  200. self.test_config.set('openstack-origin-git', projects_yaml)
  201. mock_is_db_initialised.return_value = False
  202. self.os_release.return_value = 'diablo'
  203. hooks.config_changed()
  204. self.git_install.assert_called_with(projects_yaml)
  205. self.assertFalse(self.do_openstack_upgrade.called)
  206. self.assertTrue(mock_update_nova_consoleauth_config.called)
  207. self.assertTrue(mock_update_aws_compat_services.called)
  208. @patch.object(hooks, 'update_aws_compat_services')
  209. @patch.object(hooks, 'update_nova_consoleauth_config')
  210. @patch.object(hooks, 'is_db_initialised')
  211. @patch.object(hooks, 'quantum_joined')
  212. @patch.object(hooks, 'determine_packages')
  213. @patch.object(utils, 'service_resume')
  214. @patch('charmhelpers.contrib.openstack.ip.unit_get')
  215. @patch('charmhelpers.contrib.hahelpers.cluster.relation_ids')
  216. @patch.object(utils, 'config')
  217. @patch.object(hooks, 'db_joined')
  218. @patch.object(hooks, 'filter_installed_packages')
  219. @patch('charmhelpers.contrib.openstack.ip.service_name',
  220. lambda *args: 'nova-cloud-controller')
  221. @patch.object(hooks, 'cluster_joined')
  222. @patch.object(hooks, 'identity_joined')
  223. @patch.object(hooks, 'neutron_api_relation_joined')
  224. @patch.object(hooks, 'configure_https')
  225. def test_config_changed_with_upgrade(self, conf_https, neutron_api_joined,
  226. identity_joined, cluster_joined,
  227. mock_filter_packages, db_joined,
  228. utils_config, mock_relids,
  229. mock_unit_get,
  230. mock_service_resume,
  231. mock_determine_packages,
  232. mock_quantum_joined,
  233. mock_is_db_initialised,
  234. mock_update_nova_consoleauth_config,
  235. mock_update_aws_compat_services):
  236. mock_determine_packages.return_value = []
  237. mock_is_db_initialised.return_value = False
  238. self.git_install_requested.return_value = False
  239. self.openstack_upgrade_available.return_value = True
  240. self.relation_ids.return_value = ['generic_rid']
  241. _zmq_joined = self.patch('zeromq_configuration_relation_joined')
  242. utils_config.side_effect = self.test_config.get
  243. self.test_config.set('console-access-protocol', 'dummy')
  244. mock_relids.return_value = []
  245. mock_unit_get.return_value = '127.0.0.1'
  246. self.os_release.return_value = 'diablo'
  247. hooks.config_changed()
  248. self.assertTrue(self.do_openstack_upgrade.called)
  249. self.assertTrue(neutron_api_joined.called)
  250. self.assertTrue(identity_joined.called)
  251. self.assertTrue(_zmq_joined.called)
  252. self.assertTrue(cluster_joined.called)
  253. self.assertTrue(db_joined.called)
  254. self.assertTrue(self.save_script_rc.called)
  255. mock_filter_packages.assert_called_with([])
  256. self.assertTrue(mock_quantum_joined.called)
  257. self.assertTrue(mock_update_nova_consoleauth_config.called)
  258. self.assertTrue(mock_update_aws_compat_services.called)
  259. @patch.object(hooks, 'update_aws_compat_services')
  260. @patch.object(hooks, 'update_nova_consoleauth_config')
  261. @patch.object(hooks, 'is_db_initialised')
  262. @patch.object(utils, 'service_resume')
  263. @patch.object(hooks, 'filter_installed_packages')
  264. @patch.object(hooks, 'configure_https')
  265. @patch.object(hooks, 'compute_changed')
  266. def test_config_changed_region_change(self, mock_compute_changed,
  267. mock_config_https,
  268. mock_filter_packages,
  269. mock_service_resume,
  270. mock_is_db_initialised,
  271. mock_update_nova_consoleauth_config,
  272. mock_update_aws_compat_services):
  273. self.git_install_requested.return_value = False
  274. self.openstack_upgrade_available.return_value = False
  275. self.config_value_changed.return_value = True
  276. self.related_units.return_value = ['unit/0']
  277. self.relation_ids.side_effect = \
  278. lambda x: ['generic_rid'] if x == 'cloud-compute' else []
  279. mock_is_db_initialised.return_value = False
  280. self.os_release.return_value = 'diablo'
  281. hooks.config_changed()
  282. mock_compute_changed.assert_has_calls([call('generic_rid', 'unit/0')])
  283. self.assertTrue(mock_update_nova_consoleauth_config.called)
  284. self.assertTrue(mock_update_aws_compat_services.called)
  285. @patch.object(hooks, 'is_cellv2_init_ready')
  286. @patch.object(hooks, 'is_db_initialised')
  287. @patch.object(hooks, 'nova_api_relation_joined')
  288. def test_compute_changed_nova_api_trigger(self, api_joined,
  289. mock_is_db_initialised,
  290. mock_is_cellv2_init_ready):
  291. self.relation_ids.return_value = ['nova-api/0']
  292. mock_is_db_initialised.return_value = False
  293. mock_is_cellv2_init_ready.return_value = False
  294. hooks.compute_changed()
  295. api_joined.assert_called_with(rid='nova-api/0')
  296. @patch.object(hooks, 'is_cellv2_init_ready')
  297. @patch.object(hooks, 'is_db_initialised')
  298. def test_compute_changed_ssh_migration(self, mock_is_db_initialised,
  299. mock_is_cellv2_init_ready):
  300. self.test_relation.set({
  301. 'migration_auth_type': 'ssh', 'ssh_public_key': 'fookey',
  302. 'private-address': '10.0.0.1', 'region': 'RegionOne'})
  303. self.ssh_known_hosts_lines.return_value = [
  304. 'k_h_0', 'k_h_1', 'k_h_2']
  305. self.ssh_authorized_keys_lines.return_value = [
  306. 'auth_0', 'auth_1', 'auth_2']
  307. mock_is_db_initialised.return_value = False
  308. mock_is_cellv2_init_ready.return_value = False
  309. hooks.compute_changed()
  310. self.ssh_compute_add.assert_called_with('fookey', rid=None, unit=None)
  311. expected_relations = [
  312. call(relation_settings={'authorized_keys_0': 'auth_0'},
  313. relation_id=None),
  314. call(relation_settings={'authorized_keys_1': 'auth_1'},
  315. relation_id=None),
  316. call(relation_settings={'authorized_keys_2': 'auth_2'},
  317. relation_id=None),
  318. call(relation_settings={'known_hosts_0': 'k_h_0'},
  319. relation_id=None),
  320. call(relation_settings={'known_hosts_1': 'k_h_1'},
  321. relation_id=None),
  322. call(relation_settings={'known_hosts_2': 'k_h_2'},
  323. relation_id=None),
  324. call(authorized_keys_max_index=3, relation_id=None),
  325. call(known_hosts_max_index=3, relation_id=None)]
  326. self.assertEqual(sorted(self.relation_set.call_args_list),
  327. sorted(expected_relations))
  328. @patch.object(hooks, 'is_cellv2_init_ready')
  329. @patch.object(hooks, 'is_db_initialised')
  330. def test_compute_changed_nova_public_key(self, mock_is_db_initialised,
  331. mock_is_cellv2_init_ready):
  332. self.test_relation.set({
  333. 'migration_auth_type': 'sasl', 'nova_ssh_public_key': 'fookey',
  334. 'private-address': '10.0.0.1', 'region': 'RegionOne'})
  335. self.ssh_known_hosts_lines.return_value = [
  336. 'k_h_0', 'k_h_1', 'k_h_2']
  337. self.ssh_authorized_keys_lines.return_value = [
  338. 'auth_0', 'auth_1', 'auth_2']
  339. mock_is_db_initialised.return_value = False
  340. mock_is_cellv2_init_ready.return_value = False
  341. hooks.compute_changed()
  342. self.ssh_compute_add.assert_called_with('fookey', user='nova',
  343. rid=None, unit=None)
  344. expected_relations = [
  345. call(relation_settings={'nova_authorized_keys_0': 'auth_0'},
  346. relation_id=None),
  347. call(relation_settings={'nova_authorized_keys_1': 'auth_1'},
  348. relation_id=None),
  349. call(relation_settings={'nova_authorized_keys_2': 'auth_2'},
  350. relation_id=None),
  351. call(relation_settings={'nova_known_hosts_0': 'k_h_0'},
  352. relation_id=None),
  353. call(relation_settings={'nova_known_hosts_1': 'k_h_1'},
  354. relation_id=None),
  355. call(relation_settings={'nova_known_hosts_2': 'k_h_2'},
  356. relation_id=None),
  357. call(relation_settings={'nova_known_hosts_max_index': 3},
  358. relation_id=None),
  359. call(relation_settings={'nova_authorized_keys_max_index': 3},
  360. relation_id=None)]
  361. self.assertEqual(sorted(self.relation_set.call_args_list),
  362. sorted(expected_relations))
  363. @patch.object(hooks, 'canonical_url')
  364. @patch.object(utils, 'config')
  365. @patch.object(hooks, '_auth_config')
  366. def test_compute_joined_neutron(self, auth_config, _util_config,
  367. _canonical_url):
  368. _util_config.return_value = None
  369. self.is_relation_made.return_value = False
  370. self.network_manager.return_value = 'neutron'
  371. self.is_leader = True
  372. self.keystone_ca_cert_b64.return_value = 'foocert64'
  373. self.unit_get.return_value = 'nova-cc-host1'
  374. self.serial_console_settings.return_value = {
  375. 'enable_serial_console': 'false',
  376. 'serial_console_base_url': 'ws://controller:6803',
  377. }
  378. _canonical_url.return_value = 'http://nova-cc-host1'
  379. auth_config.return_value = FAKE_KS_AUTH_CFG
  380. hooks.compute_joined()
  381. self.relation_set.assert_called_with(
  382. relation_id=None,
  383. ca_cert='foocert64',
  384. region='RegionOne',
  385. volume_service='cinder',
  386. ec2_host='nova-cc-host1',
  387. network_manager='neutron',
  388. enable_serial_console='false',
  389. serial_console_base_url='ws://controller:6803',
  390. **FAKE_KS_AUTH_CFG)
  391. @patch.object(hooks, 'canonical_url')
  392. @patch.object(utils, 'config')
  393. @patch.object(hooks, 'NeutronAPIContext')
  394. @patch.object(hooks, '_auth_config')
  395. def test_compute_joined_neutron_api_rel(self, auth_config, napi,
  396. _util_config, _canonical_url):
  397. def mock_NeutronAPIContext():
  398. return {
  399. 'neutron_plugin': 'bob',
  400. 'neutron_security_groups': 'yes',
  401. 'neutron_url': 'http://nova-cc-host1:9696',
  402. }
  403. _util_config.return_value = None
  404. napi.return_value = mock_NeutronAPIContext
  405. self.is_relation_made.return_value = True
  406. self.network_manager.return_value = 'neutron'
  407. self.is_leader = True
  408. self.keystone_ca_cert_b64.return_value = 'foocert64'
  409. self.unit_get.return_value = 'nova-cc-host1'
  410. self.serial_console_settings.return_value = {
  411. 'enable_serial_console': 'false',
  412. 'serial_console_base_url': 'ws://controller:6803',
  413. }
  414. _canonical_url.return_value = 'http://nova-cc-host1'
  415. auth_config.return_value = FAKE_KS_AUTH_CFG
  416. hooks.compute_joined()
  417. self.relation_set.assert_called_with(
  418. relation_id=None,
  419. quantum_url='http://nova-cc-host1:9696',
  420. ca_cert='foocert64',
  421. quantum_port=9696,
  422. quantum_host='nova-cc-host1',
  423. quantum_security_groups='yes',
  424. region='RegionOne',
  425. volume_service='cinder',
  426. ec2_host='nova-cc-host1',
  427. quantum_plugin='bob',
  428. network_manager='neutron',
  429. enable_serial_console='false',
  430. serial_console_base_url='ws://controller:6803',
  431. **FAKE_KS_AUTH_CFG)
  432. @patch.object(hooks, 'canonical_url')
  433. @patch.object(hooks, '_auth_config')
  434. def test_nova_vmware_joined(self, auth_config, _canonical_url):
  435. auth_config.return_value = FAKE_KS_AUTH_CFG
  436. self.is_relation_made.return_value = False
  437. self.network_manager.return_value = 'neutron'
  438. _canonical_url.return_value = 'http://nova-cc-host1'
  439. hooks.nova_vmware_relation_joined()
  440. self.relation_set.assert_called_with(
  441. network_manager='neutron',
  442. relation_id=None,
  443. **FAKE_KS_AUTH_CFG)
  444. def test_db_joined(self):
  445. self.get_relation_ip.return_value = '10.10.10.10'
  446. self.is_relation_made.return_value = False
  447. self.os_release.return_value = 'diablo'
  448. hooks.db_joined()
  449. self.relation_set.assert_called_with(nova_database='nova',
  450. nova_username='nova',
  451. nova_hostname='10.10.10.10',
  452. relation_id=None)
  453. self.get_relation_ip.assert_called_with('shared-db',
  454. cidr_network=None)
  455. def test_db_joined_spaces(self):
  456. self.get_relation_ip.return_value = '192.168.20.1'
  457. self.unit_get.return_value = 'nova.foohost.com'
  458. self.is_relation_made.return_value = False
  459. self.os_release.return_value = 'diablo'
  460. hooks.db_joined()
  461. self.relation_set.assert_called_with(nova_database='nova',
  462. nova_username='nova',
  463. nova_hostname='192.168.20.1',
  464. relation_id=None)
  465. def test_db_joined_mitaka(self):
  466. self.get_relation_ip.return_value = '10.10.10.10'
  467. self.os_release.return_value = 'mitaka'
  468. self.is_relation_made.return_value = False
  469. hooks.db_joined()
  470. self.relation_set.assert_has_calls([
  471. call(nova_database='nova',
  472. nova_username='nova',
  473. nova_hostname='10.10.10.10',
  474. relation_id=None),
  475. call(novaapi_database='nova_api',
  476. novaapi_username='nova',
  477. novaapi_hostname='10.10.10.10',
  478. relation_id=None),
  479. ])
  480. self.get_relation_ip.assert_called_with('shared-db',
  481. cidr_network=None)
  482. @patch('charmhelpers.contrib.openstack.ip.service_name',
  483. lambda *args: 'nova-cloud-controller')
  484. @patch('charmhelpers.contrib.openstack.ip.unit_get')
  485. @patch('charmhelpers.contrib.openstack.ip.is_clustered')
  486. @patch('charmhelpers.contrib.openstack.ip.config')
  487. def test_identity_joined(self, _ip_config, _is_clustered, _unit_get):
  488. _is_clustered.return_value = False
  489. _unit_get.return_value = '127.0.0.1'
  490. _ip_config.side_effect = self.test_config.get
  491. self.test_config.set('os-public-hostname', 'ncc.example.com')
  492. hooks.identity_joined()
  493. self.determine_endpoints.asssert_called_with(
  494. public_url='http://ncc.example.com',
  495. internal_url='http://127.0.0.1',
  496. admin_url='http://127.0.0.1'
  497. )
  498. def test_identity_joined_partial_cluster(self):
  499. self.is_clustered.return_value = False
  500. self.test_config.set('vip', '10.0.0.10')
  501. hooks.identity_joined()
  502. self.assertFalse(self.relation_set.called)
  503. def test_postgresql_nova_db_joined(self):
  504. self.is_relation_made.return_value = False
  505. hooks.pgsql_nova_db_joined()
  506. self.relation_set.assert_called_with(database='nova')
  507. def test_db_joined_with_postgresql(self):
  508. self.is_relation_made.return_value = True
  509. with self.assertRaises(Exception) as context:
  510. hooks.db_joined()
  511. self.assertEqual(context.exception.message,
  512. 'Attempting to associate a mysql database when'
  513. ' there is already associated a postgresql one')
  514. def test_postgresql_nova_joined_with_db(self):
  515. self.is_relation_made.return_value = True
  516. with self.assertRaises(Exception) as context:
  517. hooks.pgsql_nova_db_joined()
  518. self.assertEqual(context.exception.message,
  519. 'Attempting to associate a postgresql database when'
  520. ' there is already associated a mysql one')
  521. @patch.object(hooks, 'CONFIGS')
  522. def test_db_changed_missing_relation_data(self, configs):
  523. configs.complete_contexts = MagicMock()
  524. configs.complete_contexts.return_value = []
  525. hooks.db_changed()
  526. self.log.assert_called_with(
  527. 'shared-db relation incomplete. Peer not ready?'
  528. )
  529. @patch.object(hooks, 'CONFIGS')
  530. def test_postgresql_nova_db_changed_missing_relation_data(self, configs):
  531. configs.complete_contexts = MagicMock()
  532. configs.complete_contexts.return_value = []
  533. hooks.postgresql_nova_db_changed()
  534. self.log.assert_called_with(
  535. 'pgsql-nova-db relation incomplete. Peer not ready?'
  536. )
  537. def _shared_db_test(self, configs):
  538. configs.complete_contexts = MagicMock()
  539. configs.complete_contexts.return_value = ['shared-db']
  540. configs.write = MagicMock()
  541. hooks.db_changed()
  542. def _postgresql_db_test(self, configs):
  543. configs.complete_contexts = MagicMock()
  544. configs.complete_contexts.return_value = ['pgsql-nova-db']
  545. configs.write = MagicMock()
  546. hooks.postgresql_nova_db_changed()
  547. @patch.object(hooks, 'nova_api_relation_joined')
  548. @patch.object(hooks, 'is_db_initialised')
  549. @patch.object(hooks, 'CONFIGS')
  550. def test_db_changed(self, configs,
  551. mock_is_db_initialised, api_joined):
  552. self.relation_ids.return_value = ['nova-api/0']
  553. mock_is_db_initialised.return_value = False
  554. 'No database migration is attempted when ACL list is not present'
  555. self.os_release.return_value = 'diablo'
  556. self._shared_db_test(configs)
  557. self.assertTrue(configs.write_all.called)
  558. self.assertFalse(self.migrate_nova_databases.called)
  559. api_joined.asert_called_with(rid='nova-api/0')
  560. @patch.object(utils, 'is_leader')
  561. @patch.object(utils, 'os_release')
  562. @patch.object(hooks, 'is_db_initialised')
  563. @patch.object(hooks, 'CONFIGS')
  564. def test_db_changed_allowed(self, configs, mock_is_db_initialised,
  565. utils_os_release, utils_is_leader):
  566. mock_is_db_initialised.return_value = False
  567. allowed_units = 'nova-cloud-controller/0 nova-cloud-controller/3'
  568. self.test_relation.set({
  569. 'nova_allowed_units': allowed_units,
  570. })
  571. self.local_unit.return_value = 'nova-cloud-controller/3'
  572. self.os_release.return_value = 'diablo'
  573. utils_os_release.return_value = 'diablo'
  574. utils_is_leader.return_value = False
  575. self._shared_db_test(configs)
  576. self.assertTrue(configs.write_all.called)
  577. self.migrate_nova_databases.assert_called_with()
  578. @patch.object(hooks, 'is_db_initialised')
  579. @patch.object(hooks, 'CONFIGS')
  580. def test_db_changed_not_allowed(self, configs, mock_is_db_initialised):
  581. mock_is_db_initialised.return_value = False
  582. allowed_units = 'nova-cloud-controller/0 nova-cloud-controller/3'
  583. self.test_relation.set({
  584. 'nova_allowed_units': allowed_units,
  585. })
  586. self.local_unit.return_value = 'nova-cloud-controller/1'
  587. self.os_release.return_value = 'diablo'
  588. self._shared_db_test(configs)
  589. self.assertTrue(configs.write_all.called)
  590. self.assertFalse(self.migrate_nova_databases.called)
  591. @patch.object(utils, 'is_leader')
  592. @patch.object(utils, 'os_release')
  593. @patch.object(hooks, 'quantum_joined')
  594. @patch.object(hooks, 'nova_api_relation_joined')
  595. @patch.object(hooks, 'is_db_initialised')
  596. @patch.object(hooks, 'CONFIGS')
  597. def test_postgresql_db_changed(self, configs, mock_is_db_initialised,
  598. api_joined, quantum_joined,
  599. utils_os_release, utils_is_leader):
  600. self.relation_ids.side_effect = [
  601. [],
  602. ['neutron-gateway/0'],
  603. ['nova-api/0']]
  604. mock_is_db_initialised.return_value = False
  605. self.os_release.return_value = 'diablo'
  606. utils_os_release.return_value = 'diablo'
  607. utils_is_leader.return_value = True
  608. self._postgresql_db_test(configs)
  609. self.assertTrue(configs.write_all.called)
  610. self.migrate_nova_databases.assert_called_with()
  611. api_joined.assert_called_with(rid='nova-api/0')
  612. @patch.object(utils, 'is_leader')
  613. @patch.object(utils, 'os_release')
  614. @patch.object(hooks, 'quantum_joined')
  615. @patch.object(hooks, 'is_db_initialised')
  616. @patch.object(hooks, 'nova_cell_relation_joined')
  617. @patch.object(hooks, 'compute_joined')
  618. @patch.object(hooks, 'CONFIGS')
  619. def test_db_changed_remote_restarts(self, configs, comp_joined,
  620. cell_joined, mock_is_db_initialised,
  621. quantum_joined, utils_os_release,
  622. utils_is_leader):
  623. mock_is_db_initialised.return_value = False
  624. def _relation_ids(rel):
  625. relid = {
  626. 'cloud-compute': ['nova-compute/0'],
  627. 'cell': ['nova-cell-api/0'],
  628. 'neutron-api': ['neutron-api/0'],
  629. 'quantum-network-service': ['neutron-gateway/0']
  630. }
  631. return relid[rel]
  632. self.relation_ids.side_effect = _relation_ids
  633. allowed_units = 'nova-cloud-controller/0'
  634. self.test_relation.set({
  635. 'nova_allowed_units': allowed_units,
  636. })
  637. self.local_unit.return_value = 'nova-cloud-controller/0'
  638. self.os_release.return_value = 'diablo'
  639. utils_os_release.return_value = 'diablo'
  640. utils_is_leader.return_value = False
  641. self._shared_db_test(configs)
  642. comp_joined.assert_called_with(remote_restart=True,
  643. rid='nova-compute/0')
  644. cell_joined.assert_called_with(remote_restart=True,
  645. rid='nova-cell-api/0')
  646. quantum_joined.assert_called_with(remote_restart=True,
  647. rid='neutron-gateway/0')
  648. self.migrate_nova_databases.assert_called_with()
  649. @patch.object(hooks, 'nova_cell_relation_joined')
  650. @patch.object(hooks, 'CONFIGS')
  651. def test_amqp_relation_broken(self, configs, cell_joined):
  652. configs.write = MagicMock()
  653. self.relation_ids.return_value = ['nova-cell-api/0']
  654. hooks.relation_broken()
  655. self.assertTrue(configs.write_all.called)
  656. cell_joined.assert_called_with(rid='nova-cell-api/0')
  657. @patch.object(hooks, 'leader_init_db_if_ready_allowed_units')
  658. @patch.object(hooks, 'update_cell_db_if_ready_allowed_units')
  659. @patch.object(hooks, 'is_db_initialised')
  660. @patch.object(hooks, 'quantum_joined')
  661. @patch.object(hooks, 'nova_api_relation_joined')
  662. @patch.object(hooks, 'nova_cell_relation_joined')
  663. @patch.object(hooks, 'CONFIGS')
  664. def test_amqp_changed_api_rel(self, configs, cell_joined, api_joined,
  665. quantum_joined, mock_is_db_initialised,
  666. update_db_allowed, init_db_allowed):
  667. self.relation_ids.side_effect = [
  668. ['nova-cell-api/0'],
  669. ['nova-api/0'],
  670. ['quantum-service/0'],
  671. ]
  672. mock_is_db_initialised.return_value = False
  673. configs.complete_contexts = MagicMock()
  674. configs.complete_contexts.return_value = ['amqp']
  675. configs.write = MagicMock()
  676. self.os_release.return_value = 'diablo'
  677. self.is_relation_made.return_value = True
  678. hooks.amqp_changed()
  679. self.assertEqual(configs.write.call_args_list,
  680. [call('/etc/nova/nova.conf')])
  681. cell_joined.assert_called_with(rid='nova-cell-api/0')
  682. api_joined.assert_called_with(rid='nova-api/0')
  683. quantum_joined.assert_called_with(rid='quantum-service/0',
  684. remote_restart=True)
  685. @patch.object(hooks, 'leader_init_db_if_ready_allowed_units')
  686. @patch.object(hooks, 'update_cell_db_if_ready_allowed_units')
  687. @patch.object(hooks, 'is_db_initialised')
  688. @patch.object(hooks, 'quantum_joined')
  689. @patch.object(hooks, 'nova_api_relation_joined')
  690. @patch.object(hooks, 'nova_cell_relation_joined')
  691. @patch.object(hooks, 'CONFIGS')
  692. def test_amqp_changed_noapi_rel(self, configs, cell_joined, api_joined,
  693. quantum_joined, mock_is_db_initialised,
  694. update_db_allowed, init_db_allowed):
  695. mock_is_db_initialised.return_value = False
  696. configs.complete_contexts = MagicMock()
  697. configs.complete_contexts.return_value = ['amqp']
  698. configs.write = MagicMock()
  699. self.relation_ids.side_effect = [
  700. ['nova-cell-api/0'],
  701. ['nova-api/0'],
  702. ['quantum-service/0'],
  703. ]
  704. self.is_relation_made.return_value = False
  705. self.network_manager.return_value = 'neutron'
  706. self.os_release.return_value = 'diablo'
  707. hooks.amqp_changed()
  708. self.assertEqual(configs.write.call_args_list,
  709. [call('/etc/nova/nova.conf')])
  710. cell_joined.assert_called_with(rid='nova-cell-api/0')
  711. api_joined.assert_called_with(rid='nova-api/0')
  712. quantum_joined.assert_called_with(rid='quantum-service/0',
  713. remote_restart=True)
  714. @patch.object(hooks, 'canonical_url')
  715. def test_nova_cell_relation_joined(self, _canonical_url):
  716. self.uuid.uuid4.return_value = 'bob'
  717. _canonical_url.return_value = 'http://novaurl'
  718. hooks.nova_cell_relation_joined(rid='rid',
  719. remote_restart=True)
  720. self.relation_set.assert_called_with(restart_trigger='bob',
  721. nova_url='http://novaurl:8774/v2',
  722. relation_id='rid')
  723. @patch.object(hooks, 'CONFIGS')
  724. def test_nova_cell_relation_changed(self, configs):
  725. hooks.nova_cell_relation_changed()
  726. configs.write.assert_called_with('/etc/nova/nova.conf')
  727. def test_get_cell_type(self):
  728. self.NovaCellContext().return_value = {
  729. 'cell_type': 'parent',
  730. 'cell_name': 'api',
  731. }
  732. self.assertEqual(hooks.get_cell_type(), 'parent')
  733. @patch.object(hooks, 'canonical_url')
  734. @patch.object(os, 'rename')
  735. @patch.object(os.path, 'isfile')
  736. @patch.object(hooks, 'CONFIGS')
  737. @patch.object(hooks, 'get_cell_type')
  738. def test_neutron_api_relation_joined(self, get_cell_type, configs, isfile,
  739. rename, _canonical_url):
  740. nova_url = 'http://novaurl:8774/v2'
  741. isfile.return_value = True
  742. _identity_joined = self.patch('identity_joined')
  743. self.relation_ids.return_value = ['relid']
  744. _canonical_url.return_value = 'http://novaurl'
  745. get_cell_type.return_value = 'parent'
  746. self.uuid.uuid4.return_value = 'bob'
  747. hooks.neutron_api_relation_joined(remote_restart=True)
  748. self.assertTrue(_identity_joined.called)
  749. self.relation_set.assert_called_with(relation_id=None,
  750. cell_type='parent',
  751. nova_url=nova_url,
  752. restart_trigger='bob')
  753. @patch.object(hooks, 'CONFIGS')
  754. def test_neutron_api_relation_changed(self, configs):
  755. self.relation_ids.return_value = ['relid']
  756. _compute_joined = self.patch('compute_joined')
  757. _quantum_joined = self.patch('quantum_joined')
  758. hooks.neutron_api_relation_changed()
  759. self.assertTrue(configs.write.called_with('/etc/nova/nova.conf'))
  760. self.assertTrue(_compute_joined.called)
  761. self.assertTrue(_quantum_joined.called)
  762. @patch.object(os, 'remove')
  763. @patch.object(os.path, 'isfile')
  764. @patch.object(hooks, 'CONFIGS')
  765. def test_neutron_api_relation_broken(self, configs, isfile, remove):
  766. isfile.return_value = True
  767. self.relation_ids.return_value = ['relid']
  768. _compute_joined = self.patch('compute_joined')
  769. _quantum_joined = self.patch('quantum_joined')
  770. hooks.neutron_api_relation_broken()
  771. self.assertTrue(configs.write_all.called)
  772. self.assertTrue(_compute_joined.called)
  773. self.assertTrue(_quantum_joined.called)
  774. @patch.object(hooks, 'canonical_url')
  775. @patch.object(utils, 'config')
  776. def test_console_settings_vnc(self, _utils_config, _canonical_url):
  777. _utils_config.return_value = 'vnc'
  778. _cc_host = "nova-cc-host1"
  779. _canonical_url.return_value = 'http://' + _cc_host
  780. _con_sets = hooks.console_settings()
  781. console_settings = {
  782. 'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
  783. (_cc_host),
  784. 'console_proxy_novnc_port': 6080,
  785. 'console_access_protocol': 'vnc',
  786. 'console_proxy_novnc_host': _cc_host,
  787. 'console_proxy_xvpvnc_port': 6081,
  788. 'console_proxy_xvpvnc_host': _cc_host,
  789. 'console_proxy_xvpvnc_address': 'http://%s:6081/console' %
  790. (_cc_host),
  791. 'console_keymap': 'en-us'
  792. }
  793. self.assertEqual(_con_sets, console_settings)
  794. @patch.object(hooks, 'canonical_url')
  795. @patch.object(utils, 'config')
  796. def test_console_settings_xvpvnc(self, _utils_config, _canonical_url):
  797. _utils_config.return_value = 'xvpvnc'
  798. _cc_host = "nova-cc-host1"
  799. _canonical_url.return_value = 'http://' + _cc_host
  800. _con_sets = hooks.console_settings()
  801. console_settings = {
  802. 'console_access_protocol': 'xvpvnc',
  803. 'console_keymap': 'en-us',
  804. 'console_proxy_xvpvnc_port': 6081,
  805. 'console_proxy_xvpvnc_host': _cc_host,
  806. 'console_proxy_xvpvnc_address': 'http://%s:6081/console' %
  807. (_cc_host),
  808. }
  809. self.assertEqual(_con_sets, console_settings)
  810. @patch.object(hooks, 'canonical_url')
  811. @patch.object(utils, 'config')
  812. def test_console_settings_novnc(self, _utils_config, _canonical_url):
  813. _utils_config.return_value = 'novnc'
  814. _cc_host = "nova-cc-host1"
  815. _canonical_url.return_value = 'http://' + _cc_host
  816. _con_sets = hooks.console_settings()
  817. console_settings = {
  818. 'console_proxy_novnc_address': 'http://%s:6080/vnc_auto.html' %
  819. (_cc_host),
  820. 'console_proxy_novnc_port': 6080,
  821. 'console_access_protocol': 'novnc',
  822. 'console_proxy_novnc_host': _cc_host,
  823. 'console_keymap': 'en-us'
  824. }
  825. self.assertEqual(_con_sets, console_settings)
  826. @patch.object(hooks, 'canonical_url')
  827. @patch.object(utils, 'config')
  828. def test_console_settings_spice(self, _utils_config, _canonical_url):
  829. _utils_config.return_value = 'spice'
  830. _cc_host = "nova-cc-host1"
  831. _canonical_url.return_value = 'http://' + _cc_host
  832. _con_sets = hooks.console_settings()
  833. console_settings = {
  834. 'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' %
  835. (_cc_host),
  836. 'console_proxy_spice_host': _cc_host,
  837. 'console_proxy_spice_port': 6082,
  838. 'console_access_protocol': 'spice',
  839. 'console_keymap': 'en-us'
  840. }
  841. self.assertEqual(_con_sets, console_settings)
  842. @patch.object(hooks, 'https')
  843. @patch.object(utils, 'config')
  844. def test_console_settings_explicit_ip(self, _utils_config, _https):
  845. _utils_config.return_value = 'spice'
  846. _https.return_value = False
  847. _cc_public_host = "public-host"
  848. self.test_config.set('console-proxy-ip', _cc_public_host)
  849. _con_sets = hooks.console_settings()
  850. console_settings = {
  851. 'console_proxy_spice_address': 'http://%s:6082/spice_auto.html' %
  852. (_cc_public_host),
  853. 'console_proxy_spice_host': _cc_public_host,
  854. 'console_proxy_spice_port': 6082,
  855. 'console_access_protocol': 'spice',
  856. 'console_keymap': 'en-us'
  857. }
  858. self.assertEqual(_con_sets, console_settings)
  859. @patch.object(hooks, 'https')
  860. @patch.object(utils, 'config')
  861. def test_console_settings_explicit_ip_with_https(self, _utils_config,
  862. _https):
  863. _utils_config.return_value = 'spice'
  864. _https.return_value = True
  865. _cc_public_host = "public-host"
  866. self.test_config.set('console-proxy-ip', _cc_public_host)
  867. _con_sets = hooks.console_settings()
  868. console_settings = {
  869. 'console_proxy_spice_address': 'https://%s:6082/spice_auto.html' %
  870. (_cc_public_host),
  871. 'console_proxy_spice_host': _cc_public_host,
  872. 'console_proxy_spice_port': 6082,
  873. 'console_access_protocol': 'spice',
  874. 'console_keymap': 'en-us'
  875. }
  876. self.assertEqual(_con_sets, console_settings)
  877. @patch('nova_cc_utils.config')
  878. def test_ha_relation_joined_no_bound_ip(self, config):
  879. self.get_hacluster_config.return_value = {
  880. 'ha-bindiface': 'em0',
  881. 'ha-mcastport': '8080',
  882. 'vip': '10.10.10.10',
  883. }
  884. self.test_config.set('vip_iface', 'eth120')
  885. self.test_config.set('vip_cidr', '21')
  886. config.return_value = None
  887. self.get_iface_for_address.return_value = None
  888. self.get_netmask_for_address.return_value = None
  889. hooks.ha_joined()
  890. args = {
  891. 'relation_id': None,
  892. 'corosync_bindiface': 'em0',
  893. 'corosync_mcastport': '8080',
  894. 'init_services': {'res_nova_haproxy': 'haproxy'},
  895. 'resources': {'res_nova_eth120_vip': 'ocf:heartbeat:IPaddr2',
  896. 'res_nova_haproxy': 'lsb:haproxy'},
  897. 'resource_params': {
  898. 'res_nova_eth120_vip': 'params ip="10.10.10.10"'
  899. ' cidr_netmask="21" nic="eth120"',
  900. 'res_nova_haproxy': 'op monitor interval="5s"'},
  901. 'colocations': {},
  902. 'clones': {'cl_nova_haproxy': 'res_nova_haproxy'}
  903. }
  904. self.relation_set.assert_has_calls([
  905. call(groups={'grp_nova_vips': 'res_nova_eth120_vip'}),
  906. call(**args),
  907. ])
  908. def test_ha_joined_dns_ha(self):
  909. def _fake_update(resources, resource_params, relation_id=None):
  910. resources.update({'res_nova_public_hostname': 'ocf:maas:dns'})
  911. resource_params.update({'res_nova_public_hostname':
  912. 'params fqdn="nova.maas" '
  913. 'ip_address="10.0.0.1"'})
  914. self.test_config.set('dns-ha', True)
  915. self.get_hacluster_config.return_value = {
  916. 'vip': None,
  917. 'ha-bindiface': 'em0',
  918. 'ha-mcastport': '8080',
  919. 'os-admin-hostname': None,
  920. 'os-internal-hostname': None,
  921. 'os-public-hostname': 'nova.maas',
  922. }
  923. args = {
  924. 'relation_id': None,
  925. 'corosync_bindiface': 'em0',
  926. 'corosync_mcastport': '8080',
  927. 'init_services': {'res_nova_haproxy': 'haproxy'},
  928. 'resources': {'res_nova_public_hostname': 'ocf:maas:dns',
  929. 'res_nova_haproxy': 'lsb:haproxy'},
  930. 'resource_params': {
  931. 'res_nova_public_hostname': 'params fqdn="nova.maas" '
  932. 'ip_address="10.0.0.1"',
  933. 'res_nova_haproxy': 'op monitor interval="5s"'},
  934. 'clones': {'cl_nova_haproxy': 'res_nova_haproxy'},
  935. 'colocations': {},
  936. }
  937. self.update_dns_ha_resource_params.side_effect = _fake_update
  938. hooks.ha_joined()
  939. self.assertTrue(self.update_dns_ha_resource_params.called)
  940. self.relation_set.assert_called_with(**args)
  941. @patch('nova_cc_utils.config')
  942. def test_ha_relation_multi_consoleauth(self, config):
  943. self.get_hacluster_config.return_value = {
  944. 'ha-bindiface': 'em0',
  945. 'ha-mcastport': '8080',
  946. 'vip': '10.10.10.10',
  947. }
  948. self.test_config.set('vip_iface', 'eth120')
  949. self.test_config.set('vip_cidr', '21')
  950. self.test_config.set('single-nova-consoleauth', False)
  951. config.return_value = 'novnc'
  952. self.get_iface_for_address.return_value = None
  953. self.get_netmask_for_address.return_value = None
  954. hooks.ha_joined()
  955. args = {
  956. 'relation_id': None,
  957. 'corosync_bindiface': 'em0',
  958. 'corosync_mcastport': '8080',
  959. 'init_services': {'res_nova_haproxy': 'haproxy'},
  960. 'resources': {'res_nova_eth120_vip': 'ocf:heartbeat:IPaddr2',
  961. 'res_nova_haproxy': 'lsb:haproxy'},
  962. 'resource_params': {
  963. 'res_nova_eth120_vip': 'params ip="10.10.10.10"'
  964. ' cidr_netmask="21" nic="eth120"',
  965. 'res_nova_haproxy': 'op monitor interval="5s"'},
  966. 'colocations': {},
  967. 'clones': {'cl_nova_haproxy': 'res_nova_haproxy'}
  968. }
  969. self.relation_set.assert_has_calls([
  970. call(groups={'grp_nova_vips': 'res_nova_eth120_vip'}),
  971. call(**args),
  972. ])
  973. @patch('nova_cc_utils.config')
  974. def test_ha_relation_single_consoleauth(self, config):
  975. self.get_hacluster_config.return_value = {
  976. 'ha-bindiface': 'em0',
  977. 'ha-mcastport': '8080',
  978. 'vip': '10.10.10.10',
  979. }
  980. self.test_config.set('vip_iface', 'eth120')
  981. self.test_config.set('vip_cidr', '21')
  982. config.return_value = 'novnc'
  983. self.get_iface_for_address.return_value = None
  984. self.get_netmask_for_address.return_value = None
  985. hooks.ha_joined()
  986. args = {
  987. 'relation_id': None,
  988. 'corosync_bindiface': 'em0',
  989. 'corosync_mcastport': '8080',
  990. 'init_services': {'res_nova_haproxy': 'haproxy',
  991. 'res_nova_consoleauth': 'nova-consoleauth'},
  992. 'resources': {'res_nova_eth120_vip': 'ocf:heartbeat:IPaddr2',
  993. 'res_nova_haproxy': 'lsb:haproxy',
  994. 'res_nova_consoleauth':
  995. 'ocf:openstack:nova-consoleauth'},
  996. 'resource_params': {
  997. 'res_nova_eth120_vip': 'params ip="10.10.10.10"'
  998. ' cidr_netmask="21" nic="eth120"',
  999. 'res_nova_haproxy': 'op monitor interval="5s"',
  1000. 'res_nova_consoleauth': 'op monitor interval="5s"'},
  1001. 'colocations': {
  1002. 'vip_consoleauth': 'inf: res_nova_consoleauth grp_nova_vips'
  1003. },
  1004. 'clones': {'cl_nova_haproxy': 'res_nova_haproxy'}
  1005. }
  1006. self.relation_set.assert_has_calls([
  1007. call(groups={'grp_nova_vips': 'res_nova_eth120_vip'}),
  1008. call(**args),
  1009. ])
  1010. @patch.object(hooks, 'update_aws_compat_services')
  1011. @patch.object(hooks, 'is_db_initialised')
  1012. @patch.object(hooks, 'determine_packages')
  1013. @patch.object(hooks, 'service_pause')
  1014. @patch.object(hooks, 'filter_installed_packages')
  1015. @patch('nova_cc_hooks.configure_https')
  1016. @patch('nova_cc_utils.config')
  1017. def test_config_changed_single_consoleauth(self, mock_config,
  1018. mock_configure_https,
  1019. mock_filter_packages,
  1020. mock_service_pause,
  1021. mock_determine_packages,
  1022. mock_is_db_initialised,
  1023. mock_update_aws_compat_svcs):
  1024. mock_determine_packages.return_value = []
  1025. mock_is_db_initialised.return_value = False
  1026. self.config_value_changed.return_value = False
  1027. self.git_install_requested.return_value = False
  1028. self.os_release.return_value = 'diablo'
  1029. def cfg(k, v):
  1030. if k == "single-nova-authconsole":
  1031. return True
  1032. return 'novnc'
  1033. config.side_effect = cfg
  1034. rids = {'ha': ['ha:1']}
  1035. def f(r):
  1036. return rids.get(r, [])
  1037. self.relation_ids.side_effect = f
  1038. hooks.config_changed()
  1039. args = {
  1040. 'delete_resources': [],
  1041. 'init_services': {'res_nova_consoleauth': 'nova-consoleauth'},
  1042. 'resources': {'res_nova_consoleauth':
  1043. 'ocf:openstack:nova-consoleauth'},
  1044. 'resource_params': {
  1045. 'res_nova_consoleauth': 'op monitor interval="5s"'},
  1046. 'colocations': {
  1047. 'vip_consoleauth': 'inf: res_nova_consoleauth grp_nova_vips'
  1048. }
  1049. }
  1050. self.relation_set.assert_has_calls([
  1051. call(v, **args) for v in rids['ha']
  1052. ])
  1053. mock_service_pause.assert_has_calls([
  1054. call('nova-consoleauth')]
  1055. )
  1056. mock_filter_packages.assert_called_with([])
  1057. self.assertTrue(mock_update_aws_compat_svcs.called)
  1058. @patch.object(hooks, 'is_api_ready')
  1059. def _test_nova_api_relation_joined(self, tgt, is_api_ready):
  1060. is_api_ready.return_value = tgt
  1061. exp = 'yes' if tgt else 'no'
  1062. hooks.nova_api_relation_joined(rid='foo')
  1063. self.relation_set.assert_called_with(
  1064. 'foo', **{'nova-api-ready': exp})
  1065. def test_nova_api_relation_joined_ready(self):
  1066. self._test_nova_api_relation_joined(True)
  1067. def test_nova_api_relation_joined_not_ready(self):
  1068. self._test_nova_api_relation_joined(False)
  1069. @patch.object(hooks, 'memcached_common')
  1070. def test_memcache_joined(self, _memcached_common):
  1071. self.get_relation_ip.return_value = 'foo'
  1072. hooks.memcached_joined()
  1073. self.get_relation_ip.assert_called_once_with('memcache')
  1074. self.relation_set.assert_called_once_with(
  1075. relation_id=None,
  1076. relation_settings={'private-address': 'foo'})
  1077. hooks.memcached_joined()