#!/usr/bin/env python # 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 openstack import os import subprocess from keystoneauth1 import exceptions as ks_exceptions from mistralclient.api import base as mistralclient_exc from mistralclient.api import client as mistralclient CONF = json.loads(os.environ['config']) WORKBOOK_PATH = '/usr/share/openstack-tripleo-common/workbooks' THT_DIR = '/usr/share/openstack-tripleo-heat-templates' def _run_command(args, env=None, name=None): """Run the command defined by args and return its output :param args: List of arguments for the command to be run. :param env: Dict defining the environment variables. Pass None to use the current environment. :param name: User-friendly name for the command being run. A value of None will cause args[0] to be used. """ if name is None: name = args[0] if env is None: env = os.environ env = env.copy() # When running a localized python script, we need to tell it that we're # using utf-8 for stdout, otherwise it can't tell because of the pipe. env['PYTHONIOENCODING'] = 'utf8' try: return subprocess.check_output(args, stderr=subprocess.STDOUT, env=env).decode('utf-8') except subprocess.CalledProcessError as ex: print('ERROR: %s failed: %s' % (name, ex.output)) raise def _configure_nova(sdk): """Disable nova quotas""" sdk.set_compute_quotas('admin', cores='-1', instances='-1', ram='-1') # Configure flavors. sizings = {'ram': 4096, 'vcpus': 1, 'disk': 40} extra_specs = {'resources:CUSTOM_BAREMETAL': 1, 'resources:VCPU': 0, 'resources:MEMORY_MB': 0, 'resources:DISK_GB': 0} profiles = ['control', 'compute', 'ceph-storage', 'block-storage', 'swift-storage', 'baremetal'] flavors = [flavor.name for flavor in sdk.list_flavors()] for profile in profiles: if profile not in flavors: flavor = sdk.create_flavor(profile, **sizings) if profile != 'baremetal': extra_specs.update({'capabilities:profile': profile}) else: extra_specs.pop('capabilities:profile', None) sdk.set_flavor_specs(flavor.id, extra_specs) else: flavor = sdk.get_flavor(profile) # In place to migrate flavors from rocky too stein if flavor.extra_specs.get('capabilities:boot_option') == 'local': sdk.unset_flavor_specs(flavor.id, ['capabilities:boot_option']) print('INFO: Undercloud Post - Nova configuration completed successfully.') def _create_default_keypair(sdk): """Set up a default keypair.""" ssh_dir = os.path.join(CONF['home_dir'], '.ssh') public_key_file = os.path.join(ssh_dir, 'id_rsa.pub') if (not [True for kp in sdk.compute.keypairs() if kp.name == 'default'] and os.path.isfile(public_key_file)): with open(public_key_file, 'r') as pub_key_file: sdk.compute.create_keypair(name='default', public_key=pub_key_file.read()) def _configure_workbooks_and_workflows(mistral): for workbook in [w for w in mistral.workbooks.list() if w.name.startswith('tripleo')]: mistral.workbooks.delete(workbook.name) managed_tag = 'tripleo-common-managed' all_workflows = mistral.workflows.list() workflows_delete = [w.name for w in all_workflows if managed_tag in w.tags] # in order to delete workflows they should have no triggers associated for trigger in [t for t in mistral.cron_triggers.list() if t.workflow_name in workflows_delete]: mistral.cron_triggers.delete(trigger.name) for workflow_name in workflows_delete: mistral.workflows.delete(workflow_name) for workbook in [f for f in os.listdir(WORKBOOK_PATH) if os.path.isfile(os.path.join(WORKBOOK_PATH, f))]: mistral.workbooks.create(os.path.join(WORKBOOK_PATH, workbook)) print('INFO: Undercloud post - Mistral workbooks configured successfully.') def _store_passwords_in_mistral_env(mistral): """Store required passwords in a mistral environment""" env_name = 'tripleo.undercloud-config' config_data = { 'undercloud_ceilometer_snmpd_password': CONF['snmp_readonly_user_password'], 'undercloud_db_password': CONF['undercloud_db_password'], 'undercloud_db_host': CONF['undercloud_db_host'] } try: mistral.environments.get(env_name).variables mistral.environments.update( name=env_name, description='Undercloud configuration parameters', variables=json.dumps(config_data, sort_keys=True)) except (ks_exceptions.NotFound, mistralclient_exc.APIException): # The environment is not created, we need to create it mistral.environments.create( name=env_name, description='Undercloud configuration parameters', variables=json.dumps(config_data, sort_keys=True)) print('INFO: Undercloud post - Mistral environment configured ' 'successfully.') def _prepare_ssh_environment(mistral): mistral.executions.create('tripleo.validations.v1.copy_ssh_key') def _upload_validations_to_swift(mistral): mistral.executions.create('tripleo.validations.v1.upload_validations') def _create_default_plan(mistral): plan_exists = [True for c in sdk.list_containers() if c['name'] == 'overcloud'] if not plan_exists and os.path.isdir(THT_DIR): mistral.executions.create( 'tripleo.plan_management.v1.create_deployment_plan', workflow_input={'container': 'overcloud', 'use_default_templates': True}) print('INFO: Undercloud post - Default plan overcloud created.') nova_api_enabled = 'true' in _run_command( ['hiera', 'nova_api_enabled']).lower() mistral_api_enabled = 'true' in _run_command( ['hiera', 'mistral_api_enabled']).lower() tripleo_validations_enabled = 'true' in _run_command( ['hiera', 'tripleo_validations_enabled']).lower() if not nova_api_enabled: print('WARNING: Undercloud Post - Nova API is disabled.') if not mistral_api_enabled: print('WARNING: Undercloud Post - Mistral API is disabled.') if not tripleo_validations_enabled: print('WARNING: Undercloud Post - Tripleo validations is disabled.') sdk = openstack.connect(CONF['cloud_name']) try: if nova_api_enabled: _configure_nova(sdk) _create_default_keypair(sdk) if mistral_api_enabled: mistral = mistralclient.client(mistral_url=sdk.workflow.get_endpoint(), session=sdk.session) _configure_workbooks_and_workflows(mistral) _store_passwords_in_mistral_env(mistral) _create_default_plan(mistral) if tripleo_validations_enabled: _prepare_ssh_environment(mistral) _upload_validations_to_swift(mistral) print('INFO: Undercloud post - Validations executed and ' 'uploaded to Swift.') except Exception: print('ERROR: Undercloud Post - Failed.') raise