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.baremetal.update_node_capability = tripleo_common.actions.baremetal:UpdateNodeCapability
|
||||||
tripleo.deployment.config = tripleo_common.actions.deployment:OrchestrationDeployAction
|
tripleo.deployment.config = tripleo_common.actions.deployment:OrchestrationDeployAction
|
||||||
tripleo.deployment.deploy = tripleo_common.actions.deployment:DeployStackAction
|
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.get = tripleo_common.actions.heat_capabilities:GetCapabilitiesAction
|
||||||
tripleo.heat_capabilities.update = tripleo_common.actions.heat_capabilities:UpdateCapabilitiesAction
|
tripleo.heat_capabilities.update = tripleo_common.actions.heat_capabilities:UpdateCapabilitiesAction
|
||||||
tripleo.parameters.get = tripleo_common.actions.parameters:GetParametersAction
|
tripleo.parameters.get = tripleo_common.actions.parameters:GetParametersAction
|
||||||
|
|
|
@ -19,10 +19,12 @@ import time
|
||||||
from heatclient.common import deployment_utils
|
from heatclient.common import deployment_utils
|
||||||
from heatclient import exc as heat_exc
|
from heatclient import exc as heat_exc
|
||||||
from mistral.workflow import utils as mistral_workflow_utils
|
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 base
|
||||||
from tripleo_common.actions import templates
|
from tripleo_common.actions import templates
|
||||||
from tripleo_common import constants
|
from tripleo_common import constants
|
||||||
|
from tripleo_common.utils import overcloudrc
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -179,3 +181,55 @@ class DeployStackAction(templates.ProcessTemplatesAction):
|
||||||
LOG.info("Performing Heat stack update")
|
LOG.info("Performing Heat stack update")
|
||||||
stack_args['existing'] = 'true'
|
stack_args['existing'] = 'true'
|
||||||
return heat.stacks.update(stack.id, **stack_args)
|
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.
|
# under the License.
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
|
from heatclient import exc as heat_exc
|
||||||
from mistral.workflow import utils as mistral_workflow_utils
|
from mistral.workflow import utils as mistral_workflow_utils
|
||||||
|
from mistralclient.api import base as mistralclient_exc
|
||||||
from swiftclient import exceptions as swiftexceptions
|
from swiftclient import exceptions as swiftexceptions
|
||||||
|
|
||||||
from tripleo_common.actions import deployment
|
from tripleo_common.actions import deployment
|
||||||
|
@ -258,3 +260,84 @@ class DeployStackActionTest(base.TestCase):
|
||||||
template={'heat_template_version': '2016-04-30'},
|
template={'heat_template_version': '2016-04-30'},
|
||||||
timeout_mins=1,
|
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