Save DefaultPasswords values for undercloud deploy
There are a few legacy secrets created via the heat stack which aren't currently generated via tripleo-common, so we need to save these values or they get re-generated when you re-run undercloud deploy. Note adding the test also required some fixups to make it run on python3. Change-Id: Ib7e30ec7ecb3cc35b333e72a8b56b7e3c7d96899 Related-Bug: #1684044
This commit is contained in:
parent
846f087ea4
commit
3dad0fdc4e
@ -12,7 +12,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
#
|
#
|
||||||
import BaseHTTPServer
|
|
||||||
|
try:
|
||||||
|
import http.server as BaseHTTPServer # Python3
|
||||||
|
except ImportError:
|
||||||
|
import BaseHTTPServer # Python2
|
||||||
import datetime
|
import datetime
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
@ -44,7 +44,7 @@ class TestCase(testtools.TestCase):
|
|||||||
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
|
self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
|
||||||
|
|
||||||
self.useFixture(fixtures.NestedTempfile())
|
self.useFixture(fixtures.NestedTempfile())
|
||||||
self.useFixture(fixtures.TempHomeDir())
|
self.temp_homedir = self.useFixture(fixtures.TempHomeDir()).path
|
||||||
|
|
||||||
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
|
if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
|
||||||
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
stdout = self.useFixture(fixtures.StringStream('stdout')).stream
|
||||||
|
85
tripleoclient/tests/v1/undercloud/test_undercloud_deploy.py
Normal file
85
tripleoclient/tests/v1/undercloud/test_undercloud_deploy.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Copyright 2015 Red Hat, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
import mock
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tripleoclient.tests.v1.test_plugin import TestPluginV1
|
||||||
|
|
||||||
|
# Load the plugin init module for the plugin list and show commands
|
||||||
|
from tripleoclient.v1 import undercloud_deploy
|
||||||
|
|
||||||
|
|
||||||
|
class FakePluginV1Client(object):
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.auth_token = kwargs['token']
|
||||||
|
self.management_url = kwargs['endpoint']
|
||||||
|
|
||||||
|
|
||||||
|
class TestUndercloudDeploy(TestPluginV1):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestUndercloudDeploy, self).setUp()
|
||||||
|
|
||||||
|
# Get the command object to test
|
||||||
|
self.cmd = undercloud_deploy.DeployUndercloud(self.app, None)
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
@mock.patch('tripleo_common.utils.passwords.generate_passwords')
|
||||||
|
@mock.patch('yaml.safe_dump')
|
||||||
|
def test_update_passwords_env_init(self, mock_dump, mock_pw, mock_exists):
|
||||||
|
pw_dict = {"GeneratedPassword": 123}
|
||||||
|
pw_env_path = os.path.join(self.temp_homedir,
|
||||||
|
'tripleo-undercloud-passwords.yaml')
|
||||||
|
|
||||||
|
mock_pw.return_value = pw_dict
|
||||||
|
mock_exists.return_value = False
|
||||||
|
mock_open_context = mock.mock_open()
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||||
|
self.cmd._update_passwords_env()
|
||||||
|
|
||||||
|
mock_exists.assert_called_once_with(pw_env_path)
|
||||||
|
mock_open_context.assert_called_once_with(pw_env_path, 'w')
|
||||||
|
mock_open_handle = mock_open_context()
|
||||||
|
mock_dump.assert_called_once_with({'parameter_defaults': pw_dict},
|
||||||
|
mock_open_handle,
|
||||||
|
default_flow_style=False)
|
||||||
|
|
||||||
|
@mock.patch('os.path.exists')
|
||||||
|
@mock.patch('tripleo_common.utils.passwords.generate_passwords')
|
||||||
|
@mock.patch('yaml.safe_dump')
|
||||||
|
def test_update_passwords_env_update(self, mock_dump, mock_pw,
|
||||||
|
mock_exists):
|
||||||
|
pw_dict = {"GeneratedPassword": 123}
|
||||||
|
pw_env_path = os.path.join(self.temp_homedir,
|
||||||
|
'tripleo-undercloud-passwords.yaml')
|
||||||
|
|
||||||
|
mock_pw.return_value = pw_dict
|
||||||
|
mock_exists.return_value = True
|
||||||
|
mock_open_context = mock.mock_open(
|
||||||
|
read_data='parameter_defaults: {ExistingKey: xyz}\n')
|
||||||
|
with mock.patch('six.moves.builtins.open', mock_open_context):
|
||||||
|
self.cmd._update_passwords_env(
|
||||||
|
passwords={'ADefault': 456, 'ExistingKey': 'dontupdate'})
|
||||||
|
|
||||||
|
mock_exists.assert_called_once_with(pw_env_path)
|
||||||
|
mock_open_context.assert_called_with(pw_env_path, 'w')
|
||||||
|
mock_open_handle = mock_open_context()
|
||||||
|
expected_dict = {'parameter_defaults': {'GeneratedPassword': 123,
|
||||||
|
'ExistingKey': 'xyz',
|
||||||
|
'ADefault': 456}}
|
||||||
|
mock_dump.assert_called_once_with(expected_dict,
|
||||||
|
mock_open_handle,
|
||||||
|
default_flow_style=False)
|
@ -24,9 +24,18 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
import time
|
||||||
import urllib2
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
try:
|
||||||
|
from urllib2 import HTTPError
|
||||||
|
from urllib2 import URLError
|
||||||
|
from urllib2 import urlopen
|
||||||
|
except ImportError:
|
||||||
|
# python3
|
||||||
|
from urllib.error import HTTPError
|
||||||
|
from urllib.error import URLError
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
from cliff import command
|
from cliff import command
|
||||||
from heatclient.common import template_utils
|
from heatclient.common import template_utils
|
||||||
from openstackclient.i18n import _
|
from openstackclient.i18n import _
|
||||||
@ -76,12 +85,12 @@ class DeployUndercloud(command.Command):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
count += 1
|
count += 1
|
||||||
try:
|
try:
|
||||||
urllib2.urlopen("http://127.0.0.1:%s/" % api_port, timeout=1)
|
urlopen("http://127.0.0.1:%s/" % api_port, timeout=1)
|
||||||
except urllib2.HTTPError as he:
|
except HTTPError as he:
|
||||||
if he.code == 300:
|
if he.code == 300:
|
||||||
return True
|
return True
|
||||||
pass
|
pass
|
||||||
except urllib2.URLError:
|
except URLError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -162,11 +171,17 @@ class DeployUndercloud(command.Command):
|
|||||||
os.kill(pid, signal.SIGKILL)
|
os.kill(pid, signal.SIGKILL)
|
||||||
if event_list_pid:
|
if event_list_pid:
|
||||||
os.kill(event_list_pid, signal.SIGKILL)
|
os.kill(event_list_pid, signal.SIGKILL)
|
||||||
status = orchestration_client.stacks.get(stack_id).status
|
stack_get = orchestration_client.stacks.get(stack_id)
|
||||||
if status == 'FAILED':
|
status = stack_get.status
|
||||||
return False
|
if status != 'FAILED':
|
||||||
|
pw_rsrc = orchestration_client.resources.get(
|
||||||
|
stack_id, 'DefaultPasswords')
|
||||||
|
passwords = {p.title().replace("_", ""): v for p, v in
|
||||||
|
pw_rsrc.attributes.get('passwords', {}).items()}
|
||||||
|
return passwords
|
||||||
else:
|
else:
|
||||||
return True
|
msg = "Stack create failed, reason: %s" % stack_get.reason
|
||||||
|
raise Exception(msg)
|
||||||
|
|
||||||
def _fork_heat_event_list(self):
|
def _fork_heat_event_list(self):
|
||||||
pid = os.fork()
|
pid = os.fork()
|
||||||
@ -208,19 +223,26 @@ class DeployUndercloud(command.Command):
|
|||||||
else:
|
else:
|
||||||
return pid
|
return pid
|
||||||
|
|
||||||
def _generate_passwords_env(self):
|
def _update_passwords_env(self, passwords=None):
|
||||||
pw_file = os.path.join(os.environ.get('HOME', ''),
|
pw_file = os.path.join(os.environ.get('HOME', ''),
|
||||||
'tripleo-undercloud-passwords.yaml')
|
'tripleo-undercloud-passwords.yaml')
|
||||||
stack_env = {}
|
stack_env = {'parameter_defaults': {}}
|
||||||
if os.path.exists(pw_file):
|
if os.path.exists(pw_file):
|
||||||
with open(pw_file) as pf:
|
with open(pw_file) as pf:
|
||||||
stack_env = yaml.load(pf.read())
|
stack_env = yaml.load(pf.read())
|
||||||
|
|
||||||
pw = password_utils.generate_passwords(stack_env=stack_env)
|
pw = password_utils.generate_passwords(stack_env=stack_env)
|
||||||
|
stack_env['parameter_defaults'].update(pw)
|
||||||
|
|
||||||
|
if passwords:
|
||||||
|
# These passwords are the DefaultPasswords so we only
|
||||||
|
# update if they don't already exist in stack_env
|
||||||
|
for p, v in passwords.items():
|
||||||
|
if p not in stack_env['parameter_defaults']:
|
||||||
|
stack_env['parameter_defaults'][p] = v
|
||||||
|
|
||||||
with open(pw_file, 'w') as pf:
|
with open(pw_file, 'w') as pf:
|
||||||
yaml.safe_dump({'parameter_defaults': pw}, pf,
|
yaml.safe_dump(stack_env, pf, default_flow_style=False)
|
||||||
default_flow_style=False)
|
|
||||||
|
|
||||||
return pw_file
|
return pw_file
|
||||||
|
|
||||||
@ -277,7 +299,7 @@ class DeployUndercloud(command.Command):
|
|||||||
environments.insert(0, resource_registry_path)
|
environments.insert(0, resource_registry_path)
|
||||||
|
|
||||||
# this will allow the user to overwrite passwords with custom envs
|
# this will allow the user to overwrite passwords with custom envs
|
||||||
pw_file = self._generate_passwords_env()
|
pw_file = self._update_passwords_env()
|
||||||
environments.insert(1, pw_file)
|
environments.insert(1, pw_file)
|
||||||
|
|
||||||
undercloud_env_path = os.path.join(
|
undercloud_env_path = os.path.join(
|
||||||
@ -307,11 +329,17 @@ class DeployUndercloud(command.Command):
|
|||||||
environments.append(tmp_env_file.name)
|
environments.append(tmp_env_file.name)
|
||||||
|
|
||||||
undercloud_yaml = os.path.join(tht_root, 'overcloud.yaml')
|
undercloud_yaml = os.path.join(tht_root, 'overcloud.yaml')
|
||||||
return self._heat_deploy(parsed_args.stack, undercloud_yaml,
|
passwords = self._heat_deploy(parsed_args.stack, undercloud_yaml,
|
||||||
parameters, environments,
|
parameters, environments,
|
||||||
parsed_args.timeout,
|
parsed_args.timeout,
|
||||||
parsed_args.heat_api_port,
|
parsed_args.heat_api_port,
|
||||||
parsed_args.fake_keystone_port)
|
parsed_args.fake_keystone_port)
|
||||||
|
if passwords:
|
||||||
|
# Get legacy passwords/secrets generated via heat
|
||||||
|
# These need to be written to the passwords file
|
||||||
|
# to avoid re-creating them every update
|
||||||
|
self._update_passwords_env(passwords)
|
||||||
|
return True
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
|
Loading…
Reference in New Issue
Block a user