283 lines
9.6 KiB
Python
283 lines
9.6 KiB
Python
# Copyright 2017 Red Hat, Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 json
|
|
import logging
|
|
import time
|
|
import uuid
|
|
|
|
from heatclient import exc as heat_exc
|
|
from swiftclient import exceptions as swiftexceptions
|
|
|
|
from tripleo_common import constants
|
|
from tripleo_common import update
|
|
from tripleo_common.utils import plan as plan_utils
|
|
from tripleo_common.utils import template as templates
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def _process_params(flattened, params):
|
|
for item in params:
|
|
if item not in flattened['parameters']:
|
|
param_obj = {}
|
|
for key, value in params.get(item).items():
|
|
camel_case_key = key[0].lower() + key[1:]
|
|
param_obj[camel_case_key] = value
|
|
param_obj['name'] = item
|
|
flattened['parameters'][item] = param_obj
|
|
return list(params)
|
|
|
|
|
|
def _flat_it(flattened, name, data):
|
|
key = str(uuid.uuid4())
|
|
value = {}
|
|
value.update({
|
|
'name': name,
|
|
'id': key
|
|
})
|
|
if 'Type' in data:
|
|
value['type'] = data['Type']
|
|
if 'Description' in data:
|
|
value['description'] = data['Description']
|
|
if 'Parameters' in data:
|
|
value['parameters'] = _process_params(flattened,
|
|
data['Parameters'])
|
|
if 'ParameterGroups' in data:
|
|
value['parameter_groups'] = data['ParameterGroups']
|
|
if 'NestedParameters' in data:
|
|
nested = data['NestedParameters']
|
|
nested_ids = []
|
|
for nested_key in nested.keys():
|
|
nested_data = _flat_it(flattened, nested_key,
|
|
nested.get(nested_key))
|
|
# nested_data will always have one key (and only one)
|
|
nested_ids.append(list(nested_data)[0])
|
|
|
|
value['resources'] = nested_ids
|
|
|
|
flattened['resources'][key] = value
|
|
return {key: value}
|
|
|
|
|
|
def validate_stack_and_flatten_parameters(heat, processed_data, env):
|
|
params = env.get('parameter_defaults')
|
|
fields = {
|
|
'template': processed_data['template'],
|
|
'files': processed_data['files'],
|
|
'environment': processed_data['environment'],
|
|
'show_nested': True
|
|
}
|
|
|
|
processed_data = {
|
|
'heat_resource_tree': heat.stacks.validate(**fields),
|
|
'environment_parameters': params,
|
|
}
|
|
|
|
if processed_data['heat_resource_tree']:
|
|
flattened = {'resources': {}, 'parameters': {}}
|
|
_flat_it(flattened, 'Root',
|
|
processed_data['heat_resource_tree'])
|
|
processed_data['heat_resource_tree'] = flattened
|
|
return processed_data
|
|
|
|
|
|
def deploy_stack(swift, heat, container, skip_deploy_identifier=False,
|
|
timeout_mins=240):
|
|
try:
|
|
stack = heat.stacks.get(container, resolve_outputs=False)
|
|
except heat_exc.HTTPNotFound:
|
|
stack = None
|
|
|
|
stack_is_new = stack is None
|
|
|
|
# update StackAction, DeployIdentifier
|
|
|
|
parameters = dict()
|
|
if not skip_deploy_identifier:
|
|
parameters['DeployIdentifier'] = int(time.time())
|
|
else:
|
|
parameters['DeployIdentifier'] = ''
|
|
|
|
parameters['StackAction'] = 'CREATE' if stack_is_new else 'UPDATE'
|
|
|
|
try:
|
|
env = plan_utils.get_env(swift, container)
|
|
except swiftexceptions.ClientException as err:
|
|
err_msg = ("Error retrieving environment for plan %s: %s" % (
|
|
container, err))
|
|
LOG.exception(err_msg)
|
|
raise RuntimeError(err_msg)
|
|
|
|
set_tls_parameters(parameters, env)
|
|
try:
|
|
plan_utils.update_in_env(swift, env, 'parameter_defaults',
|
|
parameters)
|
|
except swiftexceptions.ClientException as err:
|
|
err_msg = ("Error updating environment for plan %s: %s" % (
|
|
container, err))
|
|
LOG.exception(err_msg)
|
|
raise RuntimeError(err_msg)
|
|
|
|
if not stack_is_new:
|
|
try:
|
|
LOG.debug('Checking for compatible neutron mechanism drivers')
|
|
msg = update.check_neutron_mechanism_drivers(env, stack,
|
|
swift,
|
|
container)
|
|
if msg:
|
|
raise RuntimeError(msg)
|
|
except swiftexceptions.ClientException as err:
|
|
err_msg = ("Error getting template %s: %s" % (
|
|
container, err))
|
|
LOG.exception(err_msg)
|
|
raise RuntimeError(err_msg)
|
|
|
|
# process all plan files and create or update a stack
|
|
processed_data = templates.process_templates(
|
|
swift, heat, container=container,
|
|
prune_services=True
|
|
)
|
|
stack_args = processed_data.copy()
|
|
stack_args['timeout_mins'] = timeout_mins
|
|
|
|
if stack_is_new:
|
|
try:
|
|
swift.copy_object(
|
|
"%s-swift-rings" % container, "swift-rings.tar.gz",
|
|
"%s-swift-rings/%s-%d" % (
|
|
container, "swift-rings.tar.gz", time.time()))
|
|
swift.delete_object(
|
|
"%s-swift-rings" % container, "swift-rings.tar.gz")
|
|
except swiftexceptions.ClientException:
|
|
pass
|
|
LOG.info("Perfoming Heat stack create")
|
|
try:
|
|
return heat.stacks.create(**stack_args)
|
|
except heat_exc.HTTPException as err:
|
|
err_msg = "Error during stack creation: %s" % (err,)
|
|
LOG.exception(err_msg)
|
|
raise RuntimeError(err_msg)
|
|
|
|
LOG.info("Performing Heat stack update")
|
|
stack_args['existing'] = 'true'
|
|
try:
|
|
return heat.stacks.update(stack.id, **stack_args)
|
|
except heat_exc.HTTPException as err:
|
|
err_msg = "Error during stack update: %s" % (err,)
|
|
LOG.exception(err_msg)
|
|
raise RuntimeError(err_msg)
|
|
|
|
|
|
def set_tls_parameters(parameters, env,
|
|
local_ca_path=constants.LOCAL_CACERT_PATH):
|
|
|
|
def get_camap():
|
|
return env['parameter_defaults'].get('CAMap', {})
|
|
|
|
def get_updated_camap_entry(entry_name, cacert, orig_camap):
|
|
ca_map_entry = {
|
|
entry_name: {
|
|
'content': cacert
|
|
}
|
|
}
|
|
orig_camap.update(ca_map_entry)
|
|
return orig_camap
|
|
|
|
cacert_string = get_local_cacert(local_ca_path)
|
|
if cacert_string:
|
|
parameters['CAMap'] = get_updated_camap_entry(
|
|
'undercloud-ca', cacert_string, get_camap())
|
|
|
|
|
|
def get_local_cacert(local_ca_path):
|
|
# Since the undercloud has TLS by default, we'll add the undercloud's
|
|
# CA to be trusted by the overcloud.
|
|
try:
|
|
with open(local_ca_path, 'rb') as ca_file:
|
|
return ca_file.read().decode('utf-8')
|
|
except IOError:
|
|
# If the file wasn't found it means that the undercloud's TLS
|
|
# was explicitly disabled or another CA is being used. So we'll
|
|
# let the user handle this.
|
|
return None
|
|
except Exception:
|
|
raise
|
|
|
|
|
|
def preview_stack_and_network_configs(heat, processed_data,
|
|
container, role_name):
|
|
# stacks.preview method raises validation message if stack is
|
|
# already deployed. here renaming container to get preview data.
|
|
container_temp = container + "-TEMP"
|
|
fields = {
|
|
'template': processed_data['template'],
|
|
'files': processed_data['files'],
|
|
'environment': processed_data['environment'],
|
|
'stack_name': container_temp,
|
|
}
|
|
preview_data = heat.stacks.preview(**fields)
|
|
|
|
try:
|
|
stack = heat.stacks.get(container_temp)
|
|
except heat_exc.HTTPNotFound:
|
|
msg = "Error retrieving stack: %s" % container_temp
|
|
LOG.exception(msg)
|
|
raise RuntimeError(msg)
|
|
|
|
return get_network_config(preview_data, stack.stack_name, role_name)
|
|
|
|
|
|
def get_network_config(preview_data, stack_name, role_name):
|
|
result = None
|
|
if preview_data:
|
|
for res in preview_data.resources:
|
|
net_script = process_preview_list(res, stack_name,
|
|
role_name)
|
|
if net_script:
|
|
ns_len = len(net_script)
|
|
start_index = (net_script.find(
|
|
"echo '{\"network_config\"", 0, ns_len) + 6)
|
|
# In file network/scripts/run-os-net-config.sh
|
|
end_str = "' > /etc/os-net-config/config.json"
|
|
end_index = net_script.find(end_str, start_index, ns_len)
|
|
if (end_index > start_index):
|
|
net_config = net_script[start_index:end_index]
|
|
if net_config:
|
|
result = json.loads(net_config)
|
|
break
|
|
if not result:
|
|
err_msg = ("Unable to determine network config for role '%s'."
|
|
% role_name)
|
|
LOG.exception(err_msg)
|
|
raise RuntimeError(err_msg)
|
|
return result
|
|
|
|
|
|
def process_preview_list(res, stack_name, role_name):
|
|
if type(res) == list:
|
|
for item in res:
|
|
out = process_preview_list(item, stack_name, role_name)
|
|
if out:
|
|
return out
|
|
elif type(res) == dict:
|
|
res_stack_name = stack_name + '-' + role_name
|
|
if res['resource_name'] == "OsNetConfigImpl" and \
|
|
res['resource_identity'] and \
|
|
res_stack_name in res['resource_identity']['stack_name']:
|
|
return res['properties']['config']
|
|
return None
|