From 6f668e350b97b239533bb6d63d9b386fc86922bd Mon Sep 17 00:00:00 2001 From: Rabi Mishra Date: Sat, 7 Mar 2020 23:08:44 +0530 Subject: [PATCH] Move GeneratePasswordsAction functionality to utils Moves the functionality to utils so that it can be used from ansible module. Change-Id: Idc7ef827060942bc64f63a05a7283ce65c979456 --- tripleo_common/actions/parameters.py | 77 +--- .../tests/actions/test_parameters.py | 333 -------------- tripleo_common/tests/utils/test_plan.py | 414 ++++++++++++++++++ tripleo_common/utils/plan.py | 88 ++++ 4 files changed, 509 insertions(+), 403 deletions(-) diff --git a/tripleo_common/actions/parameters.py b/tripleo_common/actions/parameters.py index 289e78370..d2dfed2d2 100644 --- a/tripleo_common/actions/parameters.py +++ b/tripleo_common/actions/parameters.py @@ -16,7 +16,6 @@ import json import logging -from heatclient import exc as heat_exc from mistral_lib import actions import six from swiftclient import exceptions as swiftexceptions @@ -25,7 +24,6 @@ from tripleo_common.actions import base from tripleo_common import constants from tripleo_common import exception from tripleo_common.utils import parameters as parameter_utils -from tripleo_common.utils import passwords as password_utils from tripleo_common.utils import plan as plan_utils from tripleo_common.utils import stack_parameters as stack_param_utils from tripleo_common.utils import template as template_utils @@ -130,74 +128,13 @@ class GeneratePasswordsAction(base.TripleOAction): mistral = self.get_workflow_client(context) try: - env = plan_utils.get_env(swift, self.container) - except swiftexceptions.ClientException as err: - err_msg = ("Error retrieving environment for plan %s: %s" % ( - self.container, err)) - LOG.exception(err_msg) - return actions.Result(error=err_msg) - - try: - stack_env = heat.stacks.environment( - stack_id=self.container) - - # legacy heat resource names from overcloud.yaml - # We don't modify these to avoid changing defaults - for pw_res in constants.LEGACY_HEAT_PASSWORD_RESOURCE_NAMES: - try: - res = heat.resources.get(self.container, pw_res) - param_defaults = stack_env.get('parameter_defaults', {}) - param_defaults[pw_res] = res.attributes['value'] - except heat_exc.HTTPNotFound: - LOG.debug('Heat resouce not found: %s' % pw_res) - pass - - except heat_exc.HTTPNotFound: - stack_env = None - - passwords = password_utils.generate_passwords( - mistralclient=mistral, - stack_env=stack_env, - rotate_passwords=self.rotate_passwords - ) - - # if passwords don't yet exist in plan environment - if 'passwords' not in env: - env['passwords'] = {} - - # NOTE(ansmith): if rabbit password previously generated and - # stored, facilitate upgrade and use for oslo messaging in plan env - if 'RabbitPassword' in env['passwords']: - for i in ('RpcPassword', 'NotifyPassword'): - if i not in env['passwords']: - env['passwords'][i] = env['passwords']['RabbitPassword'] - - # ensure all generated passwords are present in plan env, - # but respect any values previously generated and stored - for name, password in passwords.items(): - if name not in env['passwords']: - env['passwords'][name] = password - - if self.rotate_passwords: - if len(self.rotate_pw_list) > 0: - for name in self.rotate_pw_list: - env['passwords'][name] = passwords[name] - else: - for name, password in passwords.items(): - if name not in constants.DO_NOT_ROTATE_LIST: - env['passwords'][name] = password - - try: - plan_utils.put_env(swift, env) - except swiftexceptions.ClientException as err: - err_msg = "Error uploading to container: %s" % err - LOG.exception(err_msg) - return actions.Result(error=err_msg) - - plan_utils.cache_delete(swift, - self.container, - "tripleo.parameters.get") - return env['passwords'] + return plan_utils.generate_passwords( + swift, heat, mistral, container=self.container, + rotate_passwords=self.rotate_passwords, + rotate_pw_list=self.rotate_pw_list) + except Exception as err: + LOG.exception(six.text_type(err)) + return actions.Result(six.text_type(err)) class GetPasswordsAction(base.TripleOAction): diff --git a/tripleo_common/tests/actions/test_parameters.py b/tripleo_common/tests/actions/test_parameters.py index bf64f5fbb..f03aacb7c 100644 --- a/tripleo_common/tests/actions/test_parameters.py +++ b/tripleo_common/tests/actions/test_parameters.py @@ -156,339 +156,6 @@ _EXISTING_PASSWORDS = { } -class GeneratePasswordsActionTest(base.TestCase): - - @mock.patch('tripleo_common.utils.plan.' - 'cache_delete') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.utils.passwords.' - 'get_snmpd_readonly_user_password') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client', return_value="TestPassword") - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run(self, mock_get_object_client, - mock_get_workflow_client, - mock_get_snmpd_readonly_user_password, - mock_get_orchestration_client, mock_cache): - - mock_get_snmpd_readonly_user_password.return_value = "TestPassword" - mock_ctx = mock.MagicMock() - - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'name': 'overcast', - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}], - }, default_flow_style=False) - swift.get_object.return_value = ({}, mock_env) - mock_get_object_client.return_value = swift - - mock_orchestration = mock.MagicMock() - mock_orchestration.stacks.environment.return_value = { - 'parameter_defaults': {} - } - mock_resource = mock.MagicMock() - mock_resource.attributes = { - 'value': 'existing_value' - } - mock_orchestration.resources.get.return_value = mock_resource - mock_get_orchestration_client.return_value = mock_orchestration - - action = parameters.GeneratePasswordsAction() - result = action.run(mock_ctx) - - for password_param_name in constants.PASSWORD_PARAMETER_NAMES: - self.assertTrue(password_param_name in result, - "%s is not in %s" % (password_param_name, result)) - - if password_param_name in \ - constants.LEGACY_HEAT_PASSWORD_RESOURCE_NAMES: - self.assertEqual(result[password_param_name], 'existing_value') - else: - self.assertNotEqual(result[password_param_name], - 'existing_value') - - mock_cache.assert_called_once_with( - swift, - "overcloud", - "tripleo.parameters.get" - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_delete') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.utils.passwords.' - 'create_ssh_keypair') - @mock.patch('tripleo_common.utils.passwords.' - 'create_fernet_keys_repo_structure_and_keys') - @mock.patch('tripleo_common.utils.passwords.' - 'get_snmpd_readonly_user_password') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_passwords_exist(self, mock_get_object_client, - mock_get_workflow_client, - mock_get_snmpd_readonly_user_password, - mock_fernet_keys_setup, - mock_create_ssh_keypair, - mock_get_orchestration_client, - mock_cache): - - mock_get_snmpd_readonly_user_password.return_value = "TestPassword" - mock_create_ssh_keypair.return_value = {'public_key': 'Foo', - 'private_key': 'Bar'} - mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, - '/tmp/bar': {'content': 'Bar'}} - - mock_ctx = mock.MagicMock() - - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'name': constants.DEFAULT_CONTAINER_NAME, - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}], - 'passwords': _EXISTING_PASSWORDS.copy() - }, default_flow_style=False) - swift.get_object.return_value = ({}, mock_env) - mock_get_object_client.return_value = swift - - mock_orchestration = mock.MagicMock() - mock_orchestration.stacks.environment.return_value = { - 'parameter_defaults': {} - } - mock_get_orchestration_client.return_value = mock_orchestration - - action = parameters.GeneratePasswordsAction() - result = action.run(mock_ctx) - - # ensure old passwords used and no new generation - self.assertEqual(_EXISTING_PASSWORDS, result) - mock_cache.assert_called_once_with( - swift, - "overcloud", - "tripleo.parameters.get" - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_delete') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.utils.passwords.' - 'create_ssh_keypair') - @mock.patch('tripleo_common.utils.passwords.' - 'create_fernet_keys_repo_structure_and_keys') - @mock.patch('tripleo_common.utils.passwords.' - 'get_snmpd_readonly_user_password') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_rotate_no_rotate_list(self, mock_get_object_client, - mock_get_workflow_client, - mock_get_snmpd_readonly_user_password, - mock_fernet_keys_setup, - mock_create_ssh_keypair, - mock_get_orchestration_client, - mock_cache): - - mock_get_snmpd_readonly_user_password.return_value = "TestPassword" - mock_create_ssh_keypair.return_value = {'public_key': 'Foo', - 'private_key': 'Bar'} - mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, - '/tmp/bar': {'content': 'Bar'}} - - mock_ctx = mock.MagicMock() - - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'name': constants.DEFAULT_CONTAINER_NAME, - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}], - 'passwords': _EXISTING_PASSWORDS.copy() - }, default_flow_style=False) - swift.get_object.return_value = ({}, mock_env) - mock_get_object_client.return_value = swift - - mock_orchestration = mock.MagicMock() - mock_orchestration.stacks.environment.return_value = { - 'parameter_defaults': {} - } - - mock_resource = mock.MagicMock() - mock_resource.attributes = { - 'value': 'existing_value' - } - mock_orchestration.resources.get.return_value = mock_resource - mock_get_orchestration_client.return_value = mock_orchestration - - action = parameters.GeneratePasswordsAction(rotate_passwords=True) - result = action.run(mock_ctx) - - # ensure passwords in the DO_NOT_ROTATE_LIST are not modified - for name in constants.DO_NOT_ROTATE_LIST: - self.assertEqual(_EXISTING_PASSWORDS[name], result[name]) - - # ensure all passwords are generated - for name in constants.PASSWORD_PARAMETER_NAMES: - self.assertTrue(name in result, "%s is not in %s" % (name, result)) - - # ensure new passwords have been generated - self.assertNotEqual(_EXISTING_PASSWORDS, result) - mock_cache.assert_called_once_with( - swift, - "overcloud", - "tripleo.parameters.get" - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_delete') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.utils.passwords.' - 'create_ssh_keypair') - @mock.patch('tripleo_common.utils.passwords.' - 'create_fernet_keys_repo_structure_and_keys') - @mock.patch('tripleo_common.utils.passwords.' - 'get_snmpd_readonly_user_password') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_run_rotate_with_rotate_list(self, mock_get_object_client, - mock_get_workflow_client, - mock_get_snmpd_readonly_user_password, - mock_fernet_keys_setup, - mock_create_ssh_keypair, - mock_get_orchestration_client, - mock_cache): - - mock_get_snmpd_readonly_user_password.return_value = "TestPassword" - mock_create_ssh_keypair.return_value = {'public_key': 'Foo', - 'private_key': 'Bar'} - mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, - '/tmp/bar': {'content': 'Bar'}} - - mock_ctx = mock.MagicMock() - - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'name': constants.DEFAULT_CONTAINER_NAME, - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}], - 'passwords': _EXISTING_PASSWORDS.copy() - }, default_flow_style=False) - swift.get_object.return_value = ({}, mock_env) - mock_get_object_client.return_value = swift - - mock_orchestration = mock.MagicMock() - mock_orchestration.stacks.environment.return_value = { - 'parameter_defaults': {} - } - mock_resource = mock.MagicMock() - mock_resource.attributes = { - 'value': 'existing_value' - } - mock_orchestration.resources.get.return_value = mock_resource - mock_get_orchestration_client.return_value = mock_orchestration - - rotate_list = [ - 'MistralPassword', - 'BarbicanPassword', - 'AdminPassword', - 'CeilometerMeteringSecret', - 'ZaqarPassword', - 'NovaPassword', - 'MysqlRootPassword' - ] - - action = parameters.GeneratePasswordsAction( - rotate_passwords=True, - rotate_pw_list=rotate_list - ) - result = action.run(mock_ctx) - - # ensure only specified passwords are regenerated - for name in constants.PASSWORD_PARAMETER_NAMES: - self.assertTrue(name in result, "%s is not in %s" % (name, result)) - if name in rotate_list: - self.assertNotEqual(_EXISTING_PASSWORDS[name], result[name]) - else: - self.assertEqual(_EXISTING_PASSWORDS[name], result[name]) - - mock_cache.assert_called_once_with( - swift, - "overcloud", - "tripleo.parameters.get" - ) - - @mock.patch('tripleo_common.utils.plan.' - 'cache_delete') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_orchestration_client') - @mock.patch('tripleo_common.utils.passwords.' - 'create_ssh_keypair') - @mock.patch('tripleo_common.utils.passwords.' - 'create_fernet_keys_repo_structure_and_keys') - @mock.patch('tripleo_common.utils.passwords.' - 'get_snmpd_readonly_user_password') - @mock.patch('tripleo_common.actions.base.TripleOAction.' - 'get_workflow_client') - @mock.patch('tripleo_common.actions.base.TripleOAction.get_object_client') - def test_passwords_exist_in_heat(self, mock_get_object_client, - mock_get_workflow_client, - mock_get_snmpd_readonly_user_password, - mock_fernet_keys_setup, - mock_create_ssh_keypair, - mock_get_orchestration_client, - mock_cache): - - mock_get_snmpd_readonly_user_password.return_value = "TestPassword" - mock_create_ssh_keypair.return_value = {'public_key': 'Foo', - 'private_key': 'Bar'} - mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, - '/tmp/bar': {'content': 'Bar'}} - - existing_passwords = _EXISTING_PASSWORDS.copy() - existing_passwords.pop("AdminPassword") - - mock_ctx = mock.MagicMock() - swift = mock.MagicMock(url="http://test.com") - mock_env = yaml.safe_dump({ - 'name': constants.DEFAULT_CONTAINER_NAME, - 'temp_environment': 'temp_environment', - 'template': 'template', - 'environments': [{u'path': u'environments/test.yaml'}], - 'passwords': existing_passwords.copy() - }, default_flow_style=False) - - swift.get_object.return_value = ({}, mock_env) - mock_get_object_client.return_value = swift - - mock_orchestration = mock.MagicMock() - mock_orchestration.stacks.environment.return_value = { - 'parameter_defaults': { - 'AdminPassword': 'ExistingPasswordInHeat', - } - } - mock_get_orchestration_client.return_value = mock_orchestration - - action = parameters.GeneratePasswordsAction() - result = action.run(mock_ctx) - - existing_passwords["AdminPassword"] = "ExistingPasswordInHeat" - # ensure old passwords used and no new generation - self.assertEqual(existing_passwords, result) - mock_cache.assert_called_once_with( - swift, - "overcloud", - "tripleo.parameters.get" - ) - - class GetPasswordsActionTest(base.TestCase): @mock.patch('tripleo_common.actions.base.TripleOAction.' diff --git a/tripleo_common/tests/utils/test_plan.py b/tripleo_common/tests/utils/test_plan.py index 13fd8a2ad..e59a97982 100644 --- a/tripleo_common/tests/utils/test_plan.py +++ b/tripleo_common/tests/utils/test_plan.py @@ -16,10 +16,12 @@ import json import mock import os +import yaml import zlib from swiftclient import exceptions as swiftexceptions +from tripleo_common import constants from tripleo_common.tests import base from tripleo_common.utils import passwords as password_utils from tripleo_common.utils import plan as plan_utils @@ -77,6 +79,139 @@ CAPABILITIES_DICT = { }] } +_EXISTING_PASSWORDS = { + 'PlacementPassword': 'VFJeqBKbatYhQm9jja67hufft', + 'MistralPassword': 'VFJeqBKbatYhQm9jja67hufft', + 'BarbicanPassword': 'MGGQBtgKT7FnywvkcdMwE9nhx', + 'BarbicanSimpleCryptoKek': 'dGhpcnR5X3R3b19ieXRlX2tleWJsYWhibGFoYmxhaGg=', + 'AdminPassword': 'jFmY8FTpvtF2e4d4ReXvmUP8k', + 'CeilometerMeteringSecret': 'CbHTGK4md4Cc8P8ZyzTns6wry', + 'ZaqarPassword': 'bbFgCTFbAH8vf9n3xvZCP8aMR', + 'NovaPassword': '7dZATgVPwD7Ergs9kTTDMCr7F', + 'MysqlRootPassword': 'VqJYpEdKks', + 'RabbitCookie': 'BqJYpEdKksAqJYpEdKks', + 'HeatAuthEncryptionKey': '9xZXehsKc2HbmFFMKjuqxTJHn', + 'PcsdPassword': 'KjEzeitus8eu751a', + 'HorizonSecret': 'mjEzeitus8eu751B', + 'NovajoinPassword': '7dZATgVPwD7Ergs9kTTDMCr7F', + 'IronicPassword': '4hFDgn9ANeVfuqk84pHpD4ksa', + 'RedisPassword': 'xjj3QZDcUQmU6Q7NzWBHRUhGd', + 'SaharaPassword': 'spFvYGezdFwnTk7NPxgYTbUPh', + 'AdminToken': 'jq6G6HyZtj7dcZEvuyhAfjutM', + 'CinderPassword': 'dcxC3xyUcrmvzfrrxpAd3REcm', + 'CongressPassword': 'DwcKvMqXMuNYYFU4zTCuG4234', + 'GlancePassword': 'VqJYNEdKKsGZtgnHct77XBtrV', + 'RabbitPassword': 'ahuHRXdPMx9rzCdjD9CJJNCgA', + 'RpcPassword': 'ahuHRXdPMx9rzCdjD9CJJNCgA', + 'NotifyPassword': 'ahuHRXdPMx9rzCdjD9CJJNCgA', + 'CephAdminKey': b'AQCQXtlXAAAAABAAT4Gk+U8EqqStL+JFa9bp1Q==', + 'HAProxyStatsPassword': 'P8tbdK6n4YUkTaUyy8XgEVTe6', + 'CeilometerPassword': 'RRdpwK6qf2pbKz2UtzxqauAdk', + 'GnocchiPassword': 'cRYHcUkMuJeK3vyU9pCaznUZc', + 'HeatStackDomainAdminPassword': 'GgTRyWzKYsxK4mReTJ4CM6sMc', + 'CephRgwKey': b'AQCQXtlXAAAAABAAUKcqUMu6oMjAXMjoUV4/3A==', + 'AodhPassword': '8VZXehsKc2HbmFFMKYuqxTJHn', + 'PankoPassword': 'cVZXehsSc2KdmFFMKDudxTLKn', + 'OctaviaHeartbeatKey': 'oct-heartbeat-key', + 'OctaviaPassword': 'NMl7j3nKk1VVwMxUZC8Cgw==', + 'OctaviaServerCertsKeyPassphrase': 'aW5zZWN1cmUta2V5LWRvLW5vdC11c2U=', + 'OctaviaCaKeyPassphrase': 'SLj4c3uCk4DDxPwQOG1Heb==', + 'ManilaPassword': 'NYJN86Fua3X8AVFWmMhQa2zTH', + 'NeutronMetadataProxySharedSecret': 'Q2YgUCwmBkYdqsdhhCF4hbghu', + 'CephMdsKey': b'AQCQXtlXAAAAABAAT4Gk+U8EqqStL+JFa9bp1Q==', + 'CephManilaClientKey': b'AQANOFFY1NW6AxAAu6jWI3YSOsp2QWusb5Y3DQ==', + 'CephMonKey': b'AQCQXtlXAAAAABAA9l+59N3yH+C49Y0JiKeGFg==', + 'CephGrafanaAdminPassword': 'NYJN86Fua3X8AVFWmMhQa2zTH', + 'CephDashboardAdminPassword': 'NYJN86Fua3X8AVFWmMhQa2zTH', + 'SwiftHashSuffix': 'td8mV6k7TYEGKCDvjVBwckpn9', + 'SnmpdReadonlyUserPassword': 'TestPassword', + 'SwiftPassword': 'z6EWAVfW7CuxvKdzjWTdrXCeg', + 'HeatPassword': 'bREnsXtMHKTHxt8XW6NXAYr48', + 'MysqlClustercheckPassword': 'jN4RMMWWJ4sycaRwh7UvrAtfX', + 'CephClientKey': b'AQCQXtlXAAAAABAAKyc+8St8i9onHyu2mPk+vg==', + 'NeutronPassword': 'ZxAjdU2UXCV4GM3WyPKrzAZXD', + 'DesignatePassword': 'wHYj7rftFzHMpJKnGxbjjR9CW', + 'DesignateRndcKey': 'hB8XaZRd2Tf00jKsyoXpyw==', + 'KeystoneCredential0': 'ftJNQ_XlDUK7Lgvv1kdWf3SyqVsrvNDgoNV4kJg3yzw=', + 'KeystoneCredential1': 'c4MFq82TQLFLKpiiUjrKkp15dafE2ALcD3jbaIu3rfE=', + 'KeystoneFernetKey0': 'O8NSPxr4zXBBAoGIj-5aUmtE7-Jk5a4ptVsEhzJ8Vd8=', + 'KeystoneFernetKey1': 'AueoL37kd6eLjV29AG-Ruxu5szW47osgXx6aPOqtI6I=', + 'KeystoneFernetKeys': { + '/etc/keystone/fernet-keys/0': {'content': 'IAMAVERYSAFEKEY'}, + '/etc/keystone/fernet-keys/1': {'content': 'IALSOAMAVERYSAFEKEY'} + }, + 'CephClusterFSID': u'97c16f44-b62c-11e6-aed3-185e0f73fdc5', + 'Ec2ApiPassword': 'FPvz2WiWxrHVWrmSSvv44bqmr', + 'EtcdInitialClusterToken': 'fcVZXehsSc2KdmFFMKDudxTLKa', + 'PacemakerRemoteAuthkey': + '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' + 'sGhUtCQVed7dtj12', + 'MigrationSshKey': { + 'private_key': 'private_key', + 'public_key': 'public_key' + }, + 'LibvirtTLSPassword': 'xCdt9yeamKz8Fb6EGba9u82XU', +} + class PlanTest(base.TestCase): def setUp(self): @@ -465,3 +600,282 @@ class PlanTest(base.TestCase): max_keys = 3 keys_map = plan_utils.purge_excess_keys(max_keys, keys_map) self.assertEqual(2, len(keys_map)) + + @mock.patch('tripleo_common.utils.plan.' + 'cache_delete') + @mock.patch('tripleo_common.utils.passwords.' + 'get_snmpd_readonly_user_password') + def test_generate_password(self, mock_get_snmpd_readonly_user_password, + mock_cache): + + mock_get_snmpd_readonly_user_password.return_value = "TestPassword" + + swift = mock.MagicMock(url="http://test.com") + mock_env = yaml.safe_dump({ + 'name': 'overcast', + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}], + }, default_flow_style=False) + swift.get_object.return_value = ({}, mock_env) + + mock_orchestration = mock.MagicMock() + mock_orchestration.stacks.environment.return_value = { + 'parameter_defaults': {} + } + mock_resource = mock.MagicMock() + mock_resource.attributes = { + 'value': 'existing_value' + } + mock_orchestration.resources.get.return_value = mock_resource + + mock_workflow = mock.MagicMock() + result = plan_utils.generate_passwords(swift, mock_orchestration, + mock_workflow) + + for password_param_name in constants.PASSWORD_PARAMETER_NAMES: + self.assertTrue(password_param_name in result, + "%s is not in %s" % (password_param_name, result)) + + if password_param_name in \ + constants.LEGACY_HEAT_PASSWORD_RESOURCE_NAMES: + self.assertEqual(result[password_param_name], 'existing_value') + else: + self.assertNotEqual(result[password_param_name], + 'existing_value') + + mock_cache.assert_called_once_with( + swift, + "overcloud", + "tripleo.parameters.get" + ) + + @mock.patch('tripleo_common.utils.plan.' + 'cache_delete') + @mock.patch('tripleo_common.utils.passwords.' + 'create_ssh_keypair') + @mock.patch('tripleo_common.utils.passwords.' + 'create_fernet_keys_repo_structure_and_keys') + @mock.patch('tripleo_common.utils.passwords.' + 'get_snmpd_readonly_user_password') + def test_run_passwords_exist(self, mock_get_snmpd_readonly_user_password, + mock_fernet_keys_setup, + mock_create_ssh_keypair, + mock_cache): + + mock_get_snmpd_readonly_user_password.return_value = "TestPassword" + mock_create_ssh_keypair.return_value = {'public_key': 'Foo', + 'private_key': 'Bar'} + mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, + '/tmp/bar': {'content': 'Bar'}} + + swift = mock.MagicMock(url="http://test.com") + mock_env = yaml.safe_dump({ + 'name': constants.DEFAULT_CONTAINER_NAME, + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}], + 'passwords': _EXISTING_PASSWORDS.copy() + }, default_flow_style=False) + swift.get_object.return_value = ({}, mock_env) + + mock_orchestration = mock.MagicMock() + mock_orchestration.stacks.environment.return_value = { + 'parameter_defaults': {} + } + + mock_workflow = mock.MagicMock() + + result = plan_utils.generate_passwords(swift, mock_orchestration, + mock_workflow) + + # ensure old passwords used and no new generation + self.assertEqual(_EXISTING_PASSWORDS, result) + mock_cache.assert_called_once_with( + swift, + "overcloud", + "tripleo.parameters.get" + ) + + @mock.patch('tripleo_common.utils.plan.' + 'cache_delete') + @mock.patch('tripleo_common.utils.passwords.' + 'create_ssh_keypair') + @mock.patch('tripleo_common.utils.passwords.' + 'create_fernet_keys_repo_structure_and_keys') + @mock.patch('tripleo_common.utils.passwords.' + 'get_snmpd_readonly_user_password') + def test_run_rotate_no_rotate_list( + self, mock_get_snmpd_readonly_user_password, + mock_fernet_keys_setup, mock_create_ssh_keypair, + mock_cache): + + mock_get_snmpd_readonly_user_password.return_value = "TestPassword" + mock_create_ssh_keypair.return_value = {'public_key': 'Foo', + 'private_key': 'Bar'} + mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, + '/tmp/bar': {'content': 'Bar'}} + + swift = mock.MagicMock(url="http://test.com") + mock_env = yaml.safe_dump({ + 'name': constants.DEFAULT_CONTAINER_NAME, + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}], + 'passwords': _EXISTING_PASSWORDS.copy() + }, default_flow_style=False) + swift.get_object.return_value = ({}, mock_env) + + mock_orchestration = mock.MagicMock() + mock_orchestration.stacks.environment.return_value = { + 'parameter_defaults': {} + } + + mock_resource = mock.MagicMock() + mock_resource.attributes = { + 'value': 'existing_value' + } + mock_orchestration.resources.get.return_value = mock_resource + + mock_workflow = mock.MagicMock() + result = plan_utils.generate_passwords(swift, mock_orchestration, + mock_workflow, + rotate_passwords=True) + + # ensure passwords in the DO_NOT_ROTATE_LIST are not modified + for name in constants.DO_NOT_ROTATE_LIST: + self.assertEqual(_EXISTING_PASSWORDS[name], result[name]) + + # ensure all passwords are generated + for name in constants.PASSWORD_PARAMETER_NAMES: + self.assertTrue(name in result, "%s is not in %s" % (name, result)) + + # ensure new passwords have been generated + self.assertNotEqual(_EXISTING_PASSWORDS, result) + mock_cache.assert_called_once_with( + swift, + "overcloud", + "tripleo.parameters.get" + ) + + @mock.patch('tripleo_common.utils.plan.' + 'cache_delete') + @mock.patch('tripleo_common.utils.passwords.' + 'create_ssh_keypair') + @mock.patch('tripleo_common.utils.passwords.' + 'create_fernet_keys_repo_structure_and_keys') + @mock.patch('tripleo_common.utils.passwords.' + 'get_snmpd_readonly_user_password') + def test_run_rotate_with_rotate_list( + self, mock_get_snmpd_readonly_user_password, + mock_fernet_keys_setup, mock_create_ssh_keypair, + mock_cache): + + mock_get_snmpd_readonly_user_password.return_value = "TestPassword" + mock_create_ssh_keypair.return_value = {'public_key': 'Foo', + 'private_key': 'Bar'} + mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, + '/tmp/bar': {'content': 'Bar'}} + + swift = mock.MagicMock(url="http://test.com") + mock_env = yaml.safe_dump({ + 'name': constants.DEFAULT_CONTAINER_NAME, + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}], + 'passwords': _EXISTING_PASSWORDS.copy() + }, default_flow_style=False) + swift.get_object.return_value = ({}, mock_env) + + mock_orchestration = mock.MagicMock() + mock_orchestration.stacks.environment.return_value = { + 'parameter_defaults': {} + } + mock_resource = mock.MagicMock() + mock_resource.attributes = { + 'value': 'existing_value' + } + mock_orchestration.resources.get.return_value = mock_resource + + rotate_list = [ + 'MistralPassword', + 'BarbicanPassword', + 'AdminPassword', + 'CeilometerMeteringSecret', + 'ZaqarPassword', + 'NovaPassword', + 'MysqlRootPassword' + ] + + mock_workflow = mock.MagicMock() + result = plan_utils.generate_passwords(swift, mock_orchestration, + mock_workflow, + rotate_passwords=True, + rotate_pw_list=rotate_list) + + # ensure only specified passwords are regenerated + for name in constants.PASSWORD_PARAMETER_NAMES: + self.assertTrue(name in result, "%s is not in %s" % (name, result)) + if name in rotate_list: + self.assertNotEqual(_EXISTING_PASSWORDS[name], result[name]) + else: + self.assertEqual(_EXISTING_PASSWORDS[name], result[name]) + + mock_cache.assert_called_once_with( + swift, + "overcloud", + "tripleo.parameters.get" + ) + + @mock.patch('tripleo_common.utils.plan.' + 'cache_delete') + @mock.patch('tripleo_common.utils.passwords.' + 'create_ssh_keypair') + @mock.patch('tripleo_common.utils.passwords.' + 'create_fernet_keys_repo_structure_and_keys') + @mock.patch('tripleo_common.utils.passwords.' + 'get_snmpd_readonly_user_password') + def test_passwords_exist_in_heat( + self, mock_get_snmpd_readonly_user_password, + mock_fernet_keys_setup, mock_create_ssh_keypair, + mock_cache): + + mock_get_snmpd_readonly_user_password.return_value = "TestPassword" + mock_create_ssh_keypair.return_value = {'public_key': 'Foo', + 'private_key': 'Bar'} + mock_fernet_keys_setup.return_value = {'/tmp/foo': {'content': 'Foo'}, + '/tmp/bar': {'content': 'Bar'}} + + existing_passwords = _EXISTING_PASSWORDS.copy() + existing_passwords.pop("AdminPassword") + + swift = mock.MagicMock(url="http://test.com") + mock_env = yaml.safe_dump({ + 'name': constants.DEFAULT_CONTAINER_NAME, + 'temp_environment': 'temp_environment', + 'template': 'template', + 'environments': [{u'path': u'environments/test.yaml'}], + 'passwords': existing_passwords.copy() + }, default_flow_style=False) + + swift.get_object.return_value = ({}, mock_env) + + mock_orchestration = mock.MagicMock() + mock_orchestration.stacks.environment.return_value = { + 'parameter_defaults': { + 'AdminPassword': 'ExistingPasswordInHeat', + } + } + + mock_workflow = mock.MagicMock() + result = plan_utils.generate_passwords(swift, mock_orchestration, + mock_workflow) + + existing_passwords["AdminPassword"] = "ExistingPasswordInHeat" + # ensure old passwords used and no new generation + self.assertEqual(existing_passwords, result) + mock_cache.assert_called_once_with( + swift, + "overcloud", + "tripleo.parameters.get" + ) diff --git a/tripleo_common/utils/plan.py b/tripleo_common/utils/plan.py index 4d63d63b9..b519ec698 100644 --- a/tripleo_common/utils/plan.py +++ b/tripleo_common/utils/plan.py @@ -24,6 +24,7 @@ import yaml import zlib from heatclient.common import template_utils +from heatclient import exc as heat_exc import six from swiftclient import exceptions as swiftexceptions @@ -375,6 +376,93 @@ def update_plan_environment_with_image_parameters( return env +def generate_passwords(swift, heat, mistral, + container=constants.DEFAULT_CONTAINER_NAME, + rotate_passwords=False, rotate_pw_list=None): + """Generates passwords needed for Overcloud deployment + + This method generates passwords and ensures they are stored in the + plan environment. By default, this method respects previously + generated passwords and adds new passwords as necessary. + + If rotate_passwords is set to True, then passwords will be replaced as + follows: + - if password names are specified in the rotate_pw_list, then only those + passwords will be replaced. + - otherwise, all passwords not in the DO_NOT_ROTATE list (as they require + special handling, like KEKs and Fernet keys) will be replaced. + """ + if rotate_pw_list is None: + rotate_pw_list = [] + try: + env = get_env(swift, container) + except swiftexceptions.ClientException as err: + err_msg = ("Error retrieving environment for plan %s: %s" % ( + container, err)) + LOG.exception(err_msg) + return RuntimeError(err_msg) + + try: + stack_env = heat.stacks.environment( + stack_id=container) + + # legacy heat resource names from overcloud.yaml + # We don't modify these to avoid changing defaults + for pw_res in constants.LEGACY_HEAT_PASSWORD_RESOURCE_NAMES: + try: + res = heat.resources.get(container, pw_res) + param_defaults = stack_env.get('parameter_defaults', {}) + param_defaults[pw_res] = res.attributes['value'] + except heat_exc.HTTPNotFound: + LOG.debug('Heat resouce not found: %s' % pw_res) + pass + + except heat_exc.HTTPNotFound: + stack_env = None + + passwords = password_utils.generate_passwords( + mistralclient=mistral, + stack_env=stack_env, + rotate_passwords=rotate_passwords + ) + + # if passwords don't yet exist in plan environment + if 'passwords' not in env: + env['passwords'] = {} + + # NOTE(ansmith): if rabbit password previously generated and + # stored, facilitate upgrade and use for oslo messaging in plan env + if 'RabbitPassword' in env['passwords']: + for i in ('RpcPassword', 'NotifyPassword'): + if i not in env['passwords']: + env['passwords'][i] = env['passwords']['RabbitPassword'] + + # ensure all generated passwords are present in plan env, + # but respect any values previously generated and stored + for name, password in passwords.items(): + if name not in env['passwords']: + env['passwords'][name] = password + + if rotate_passwords: + if len(rotate_pw_list) > 0: + for name in rotate_pw_list: + env['passwords'][name] = passwords[name] + else: + for name, password in passwords.items(): + if name not in constants.DO_NOT_ROTATE_LIST: + env['passwords'][name] = password + + try: + put_env(swift, env) + except swiftexceptions.ClientException as err: + err_msg = "Error uploading to container: %s" % err + LOG.exception(err_msg) + raise RuntimeError(err_msg) + + cache_delete(swift, container, "tripleo.parameters.get") + return env['passwords'] + + def update_plan_rotate_fernet_keys(swift, container=constants.DEFAULT_CONTAINER_NAME): try: