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:
parent
b83a0935bc
commit
f59d9d8664
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"})
|
||||
|
|
|
@ -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'])
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue