Move the overcloudrc generation from tripleoclient to a Mistral action

This change moves the code from tripleoclient to a new Mistral action.

Closes-Bug: #1649576
Partial-Bug: #1615720
Change-Id: I1302dcfa83ee9eed8ebe721536dfc454b5da5b6c
(cherry picked from commit 8aa57e2715)
This commit is contained in:
Dougal Matthews 2016-11-14 13:28:02 +00:00
parent b83a0935bc
commit f59d9d8664
5 changed files with 293 additions and 0 deletions

View File

@ -65,6 +65,7 @@ mistral.actions =
tripleo.baremetal.update_node_capability = tripleo_common.actions.baremetal:UpdateNodeCapability
tripleo.deployment.config = tripleo_common.actions.deployment:OrchestrationDeployAction
tripleo.deployment.deploy = tripleo_common.actions.deployment:DeployStackAction
tripleo.deployment.overcloudrc = tripleo_common.actions.deployment:OvercloudRcAction
tripleo.heat_capabilities.get = tripleo_common.actions.heat_capabilities:GetCapabilitiesAction
tripleo.heat_capabilities.update = tripleo_common.actions.heat_capabilities:UpdateCapabilitiesAction
tripleo.parameters.get = tripleo_common.actions.parameters:GetParametersAction

View File

@ -19,10 +19,12 @@ import time
from heatclient.common import deployment_utils
from heatclient import exc as heat_exc
from mistral.workflow import utils as mistral_workflow_utils
from mistralclient.api import base as mistralclient_exc
from tripleo_common.actions import base
from tripleo_common.actions import templates
from tripleo_common import constants
from tripleo_common.utils import overcloudrc
LOG = logging.getLogger(__name__)
@ -179,3 +181,55 @@ class DeployStackAction(templates.ProcessTemplatesAction):
LOG.info("Performing Heat stack update")
stack_args['existing'] = 'true'
return heat.stacks.update(stack.id, **stack_args)
class OvercloudRcAction(base.TripleOAction):
"""Generate the overcloudrc and overcloudrc.v3 for a plan
Given the name of a container, generate the overcloudrc files needed to
access the overcloud via the CLI.
no_proxy is optional and is a comma-separated string of hosts that
shouldn't be proxied
"""
def __init__(self, container, no_proxy=""):
self.container = container
self.no_proxy = no_proxy
def run(self):
orchestration_client = self._get_orchestration_client()
workflow_client = self._get_workflow_client()
try:
stack = orchestration_client.stacks.get(self.container)
except heat_exc.HTTPNotFound:
error = (
"The Heat stack {} cound not be found. Make sure you have "
"deployed before calling this action.").format(self.container)
return mistral_workflow_utils.Result(error=error)
try:
environment = workflow_client.environments.get(self.container)
except mistralclient_exc.APIException:
error = "The Mistral environment {} cound not be found.".format(
self.container)
return mistral_workflow_utils.Result(error=error)
# We need to check parameter_defaults first for a user provided
# password. If that doesn't exist, we then should look in the
# automatically generated passwords.
# TODO(d0ugal): Abstract this operation somewhere. We shouldn't need to
# know about the structure of the environment to get a password.
try:
parameter_defaults = environment.variables['parameter_defaults']
passwords = environment.variables['passwords']
admin_pass = parameter_defaults.get('AdminPassword')
if admin_pass is None:
admin_pass = passwords['AdminPassword']
except KeyError:
error = ("Unable to find the AdminPassword in the Mistral "
"environment.")
return mistral_workflow_utils.Result(error=error)
return overcloudrc.create_overcloudrc(stack, self.no_proxy, admin_pass)

View File

@ -14,7 +14,9 @@
# under the License.
import mock
from heatclient import exc as heat_exc
from mistral.workflow import utils as mistral_workflow_utils
from mistralclient.api import base as mistralclient_exc
from swiftclient import exceptions as swiftexceptions
from tripleo_common.actions import deployment
@ -258,3 +260,84 @@ class DeployStackActionTest(base.TestCase):
template={'heat_template_version': '2016-04-30'},
timeout_mins=1,
)
class OvercloudRcActionTestCase(base.TestCase):
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_orchestration_client')
@mock.patch('mistral.context.ctx')
def test_no_stack(self, mock_context, mock_get_orchestration,
mock_get_workflow):
not_found = heat_exc.HTTPNotFound()
mock_get_orchestration.return_value.stacks.get.side_effect = not_found
action = deployment.OvercloudRcAction("overcast")
result = action.run()
self.assertEqual(result.error, (
"The Heat stack overcast cound not be found. Make sure you have "
"deployed before calling this action."
))
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_orchestration_client')
@mock.patch('mistral.context.ctx')
def test_no_env(self, mock_context, mock_get_orchestration,
mock_get_workflow):
not_found = mistralclient_exc.APIException()
mock_get_workflow.return_value.environments.get.side_effect = not_found
action = deployment.OvercloudRcAction("overcast")
result = action.run()
self.assertEqual(
result.error,
"The Mistral environment overcast cound not be found.")
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_orchestration_client')
@mock.patch('mistral.context.ctx')
def test_no_password(self, mock_context, mock_get_orchestration,
mock_get_workflow):
mock_env = mock.MagicMock(variables={})
mock_get_workflow.return_value.environments.get.return_value = mock_env
action = deployment.OvercloudRcAction("overcast")
result = action.run()
self.assertEqual(
result.error,
"Unable to find the AdminPassword in the Mistral environment.")
@mock.patch('tripleo_common.utils.overcloudrc.create_overcloudrc')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_workflow_client')
@mock.patch('tripleo_common.actions.base.TripleOAction.'
'_get_orchestration_client')
@mock.patch('mistral.context.ctx')
def test_no_success(self, mock_context, mock_get_orchestration,
mock_get_workflow, mock_create_overcloudrc):
mock_create_overcloudrc.return_value = {
"overcloudrc": "fake overcloudrc"
}
mock_env = mock.MagicMock(variables={
"parameter_defaults": {},
"passwords": {"AdminPassword": "SUPERSECUREPASSWORD"}
})
mock_get_workflow.return_value.environments.get.return_value = mock_env
action = deployment.OvercloudRcAction("overcast")
result = action.run()
self.assertEqual(result, {"overcloudrc": "fake overcloudrc"})

View File

@ -0,0 +1,42 @@
# Copyright 2016 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
from tripleo_common.tests import base
from tripleo_common.utils import overcloudrc
class OvercloudRcTest(base.TestCase):
def test_generate_overcloudrc(self):
stack = mock.MagicMock()
stack.stack_name = 'overcast'
stack.to_dict.return_value = {
"outputs": [
{'output_key': 'KeystoneURL',
'output_value': 'http://foo.com:8000/'},
{'output_key': 'EndpointMap',
'output_value': {'KeystoneAdmin': {'host': 'fd00::1'}}},
]
}
result = overcloudrc.create_overcloudrc(stack, "", "AdminPassword")
self.assertIn("OS_PASSWORD=AdminPassword", result['overcloudrc'])
self.assertIn("OS_PASSWORD=AdminPassword", result['overcloudrc.v3'])
self.assertNotIn("OS_IDENTITY_API_VERSION=3", result['overcloudrc'])
self.assertIn("OS_IDENTITY_API_VERSION=3", result['overcloudrc.v3'])

View File

@ -0,0 +1,113 @@
# 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 socket
from six.moves import urllib
def get_service_ips(stack):
service_ips = {}
for output in stack.to_dict().get('outputs', {}):
service_ips[output['output_key']] = output['output_value']
return service_ips
def get_endpoint_map(stack):
endpoint_map = {}
for output in stack.to_dict().get('outputs', {}):
if output['output_key'] == 'EndpointMap':
endpoint_map = output['output_value']
break
return endpoint_map
def get_endpoint(key, stack):
endpoint_map = get_endpoint_map(stack)
if endpoint_map:
return endpoint_map[key]['host']
else:
return get_service_ips(stack).get(key + 'Vip')
def get_overcloud_endpoint(stack):
for output in stack.to_dict().get('outputs', {}):
if output['output_key'] == 'KeystoneURL':
return output['output_value']
def bracket_ipv6(address):
"""Put a bracket around address if it is valid IPv6
Return it unchanged if it is a hostname or IPv4 address.
"""
try:
socket.inet_pton(socket.AF_INET6, address)
return "[%s]" % address
except socket.error:
return address
CLEAR_ENV = """# Clear any old environment that may conflict.
for key in $( set | awk '{FS=\"=\"} /^OS_/ {print $1}' ); do unset $key ; done
"""
def create_overcloudrc(stack, no_proxy, admin_password):
"""Given the stack and proxy settings, create the overcloudrc
stack: Heat stack containing the deployed overcloud
no_proxy: a comma-separated string of hosts that shouldn't be proxied
"""
overcloud_endpoint = get_overcloud_endpoint(stack)
overcloud_host = urllib.parse.urlparse(overcloud_endpoint).hostname
overcloud_admin_vip = get_endpoint('KeystoneAdmin', stack)
no_proxy_list = map(bracket_ipv6,
[no_proxy, overcloud_host, overcloud_admin_vip])
rc_params = {
'NOVA_VERSION': '1.1',
'COMPUTE_API_VERSION': '1.1',
'OS_USERNAME': 'admin',
'OS_PROJECT_NAME': 'admin',
'OS_NO_CACHE': 'True',
'OS_CLOUDNAME': stack.stack_name,
'no_proxy': ','.join(no_proxy_list),
'PYTHONWARNINGS': ('"ignore:Certificate has no, ignore:A true '
'SSLContext object is not available"'),
'OS_PASSWORD': admin_password,
'OS_AUTH_URL': overcloud_endpoint,
}
overcloudrc = CLEAR_ENV
for key, value in rc_params.items():
line = "export %(key)s=%(value)s\n" % {'key': key, 'value': value}
overcloudrc = overcloudrc + line
rc_params.update({
'OS_AUTH_URL': overcloud_endpoint.replace('/v2.0', '') + '/v3',
'OS_USER_DOMAIN_NAME': 'Default',
'OS_PROJECT_DOMAIN_NAME': 'Default',
'OS_IDENTITY_API_VERSION': '3'
})
overcloudrc_v3 = CLEAR_ENV
for key, value in rc_params.items():
line = "export %(key)s=%(value)s\n" % {'key': key, 'value': value}
overcloudrc_v3 = overcloudrc_v3 + line
return {
"overcloudrc": overcloudrc,
"overcloudrc.v3": overcloudrc_v3
}