From bf2cc47a7ee182522e68a7c492557c58bcba75c3 Mon Sep 17 00:00:00 2001 From: Ekaterina Fedorova Date: Thu, 24 Oct 2013 13:46:02 +0400 Subject: [PATCH] Enable getting data from metadatarepository Modify unit tests Change-Id: I75a71b48475b75ca6603756c33fad3742fea2900 --- etc/agent-config/Default.template | 36 +++++ etc/agent-config/Demo.template | 8 + etc/agent-config/Linux.template | 35 +++++ etc/conductor.conf | 17 +- etc/init-scripts/demo_init.sh | 11 ++ etc/init-scripts/init.ps1 | 68 ++++++++ etc/init-scripts/linux_init.sh | 6 + heatplugin/murano/environment.py | 5 +- muranoconductor/app.py | 19 ++- muranoconductor/cloud_formation.py | 24 +-- muranoconductor/cmd/run.py | 3 +- muranoconductor/commands/cloud_formation.py | 10 +- muranoconductor/commands/dispatcher.py | 7 +- muranoconductor/commands/vm_agent.py | 6 +- muranoconductor/config.py | 10 +- muranoconductor/metadata.py | 162 ++++++++++++++++++++ muranoconductor/vm_agent.py | 13 +- muranoconductor/workflow.py | 5 +- requirements.txt | 2 +- tests/conductor/test_heat_commands.py | 10 +- tests/conductor/test_vm_agent.py | 15 +- tests/conductor/test_workflow.py | 4 +- 22 files changed, 430 insertions(+), 46 deletions(-) create mode 100644 etc/agent-config/Default.template create mode 100644 etc/agent-config/Demo.template create mode 100644 etc/agent-config/Linux.template create mode 100644 etc/init-scripts/demo_init.sh create mode 100644 etc/init-scripts/init.ps1 create mode 100644 etc/init-scripts/linux_init.sh create mode 100644 muranoconductor/metadata.py diff --git a/etc/agent-config/Default.template b/etc/agent-config/Default.template new file mode 100644 index 0000000..839abe8 --- /dev/null +++ b/etc/agent-config/Default.template @@ -0,0 +1,36 @@ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/etc/agent-config/Demo.template b/etc/agent-config/Demo.template new file mode 100644 index 0000000..8fec1df --- /dev/null +++ b/etc/agent-config/Demo.template @@ -0,0 +1,8 @@ +RABBITMQ_HOST = "%RABBITMQ_HOST%" +RABBITMQ_PORT = "%RABBITMQ_PORT%" +RABBITMQ_USERNAME = "%RABBITMQ_USER%" +RABBITMQ_PASSWORD = "%RABBITMQ_PASSWORD%" +RABBITMQ_VHOST = "%RABBITMQ_VHOST%" +RABBITMQ_INPUT_QUEUE = "%RABBITMQ_INPUT_QUEUE%" +RESULT_QUEUE = "%RESULT_QUEUE%" +RABBITMQ_RESULT_ROUTING_KEY = "%RESULT_QUEUE%" diff --git a/etc/agent-config/Linux.template b/etc/agent-config/Linux.template new file mode 100644 index 0000000..b1f3db9 --- /dev/null +++ b/etc/agent-config/Linux.template @@ -0,0 +1,35 @@ +[DEFAULT] +debug=True +verbose=True +log_file = /var/log/murano-agnet.log + +storage=/var/murano/plans + +[rabbitmq] + +# Input queue name +input_queue = %RABBITMQ_INPUT_QUEUE% + +# Output routing key (usually queue name) +result_routing_key = %RESULT_QUEUE% + +# Connection parameters to RabbitMQ service + +# Hostname or IP address where RabbitMQ is located. +host = %RABBITMQ_HOST% + +# RabbitMQ port (5672 is a default) +port = %RABBITMQ_PORT% + +# Use SSL for RabbitMQ connections (True or False) +ssl = %RABBITMQ_SSL% + +# Path to SSL CA certificate or empty to allow self signed server certificate +ca_certs = + +# RabbitMQ credentials. Fresh RabbitMQ installation has "guest" account with "guest" password. +login = %RABBITMQ_USER% +password = %RABBITMQ_PASSWORD% + +# RabbitMQ virtual host (vhost). Fresh RabbitMQ installation has "/" vhost preconfigured. +virtual_host = %RABBITMQ_VHOST% diff --git a/etc/conductor.conf b/etc/conductor.conf index 6c346f5..bf1417f 100644 --- a/etc/conductor.conf +++ b/etc/conductor.conf @@ -7,9 +7,18 @@ log_file = /tmp/conductor.log debug=True verbose=True -# Directory where conductor's data directory located. -# "data" must be subdirectory to this. -data_dir = /etc/murano-conductor +# Provide directory with initialization scripts +init_scripts_dir = ./etc/init-scripts + +# Provide directory with agent configs +agent_config_dir = ./etc/agent-config + +# Provide absolute or relative path to data storing +# directory (may not be exist) +data_dir = test_data + +# Provide url to Murano Metadata repository +murano_metadata_url = http://localhost:8084 # Maximum number of environments that can be processed simultaneously max_environments = 20 @@ -45,7 +54,7 @@ endpoint_type = publicURL [rabbitmq] # Connection parameters to RabbitMQ service -# Hostname or IP address where RabbitMQ is located. +# Hostname or IP address where RabbitMQ is located. # !!! Change localhost to your real IP or hostname as this address must be reachable from VMs !!! host = localhost diff --git a/etc/init-scripts/demo_init.sh b/etc/init-scripts/demo_init.sh new file mode 100644 index 0000000..c1cfe24 --- /dev/null +++ b/etc/init-scripts/demo_init.sh @@ -0,0 +1,11 @@ +#!/bin/sh + +AgentConfigBase64='%AGENT_CONFIG_BASE64%' + +mkdir /etc/murano + +echo $AgentConfigBase64 | base64 -d > /etc/murano/agent.config + +chmod 664 /etc/murano/agent.config +sleep 10 +reboot diff --git a/etc/init-scripts/init.ps1 b/etc/init-scripts/init.ps1 new file mode 100644 index 0000000..9fbba22 --- /dev/null +++ b/etc/init-scripts/init.ps1 @@ -0,0 +1,68 @@ +#ps1 + +$WindowsAgentConfigBase64 = '%AGENT_CONFIG_BASE64%' +$WindowsAgentConfigFile = "C:\Murano\Agent\WindowsAgent.exe.config" +$WindowsAgentLogFile = "C:\Murano\Agent\log.txt" + +$NewComputerName = '%INTERNAL_HOSTNAME%' +$MuranoFileShare = '\\%MURANO_SERVER_ADDRESS%\share' + +$CaRootCertBase64 = "%CA_ROOT_CERT_BASE64%" +$CaRootCertFile = "C:\Murano\ca.cert" + +$RestartRequired = $false + +Import-Module CoreFunctions +Initialize-Logger 'CloudBase-Init' 'C:\Murano\PowerShell.log' + +$ErrorActionPreference = 'Stop' + +trap { + Write-LogError '' + Write-LogError $_ -EntireObject + Write-LogError '' + exit 1 +} + +Write-Log "Importing CA certificate ..." +if ($CaRootCertBase64 -eq '') { + Write-Log "Importing CA certificate ... skipped" +} +else { + ConvertFrom-Base64String -Base64String $CaRootCertBase64 -Path $CaRootCertFile + $cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 $CaRootCertFile + $store = New-Object System.Security.Cryptography.X509Certificates.X509Store("AuthRoot","LocalMachine") + $store.Open("MaxAllowed") + $store.Add($cert) + $store.Close() + Write-Log "Importing CA certificate ... done" +} + +Write-Log "Updating Murano Windows Agent." +Stop-Service "Murano Agent" +Backup-File $WindowsAgentConfigFile +Remove-Item $WindowsAgentConfigFile -Force +Remove-Item $WindowsAgentLogFile -Force +ConvertFrom-Base64String -Base64String $WindowsAgentConfigBase64 -Path $WindowsAgentConfigFile +Exec sc.exe 'config','"Murano Agent"','start=','delayed-auto' +Write-Log "Service has been updated." + +Write-Log "Adding environment variable 'MuranoFileShare' = '$MuranoFileShare' ..." +[Environment]::SetEnvironmentVariable('MuranoFileShare', $MuranoFileShare, [EnvironmentVariableTarget]::Machine) +Write-Log "Environment variable added." + +Write-Log "Renaming computer to '$NewComputerName' ..." +$null = Rename-Computer -NewName $NewComputerName -Force + +Write-Log "New name assigned, restart required." +$RestartRequired = $true + + +Write-Log 'All done!' +if ( $RestartRequired ) { + Write-Log "Restarting computer ..." + Restart-Computer -Force +} +else { + Start-Service 'Murano Agent' +} diff --git a/etc/init-scripts/linux_init.sh b/etc/init-scripts/linux_init.sh new file mode 100644 index 0000000..e9f8ffd --- /dev/null +++ b/etc/init-scripts/linux_init.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +AgentConfigBase64='%AGENT_CONFIG_BASE64%' +service murano-agent stop +echo $AgentConfigBase64 | base64 -d > /etc/murano-agent.conf +service murano-agent start diff --git a/heatplugin/murano/environment.py b/heatplugin/murano/environment.py index a748d8d..35f08f2 100644 --- a/heatplugin/murano/environment.py +++ b/heatplugin/murano/environment.py @@ -47,7 +47,6 @@ class MuranoEnvironment(resource.Resource): except HTTPNotFound: pass - def _find_environment(self, client): environments = client.environments.list() for environment in environments: @@ -75,10 +74,10 @@ class MuranoEnvironment(resource.Resource): delay = 2 while True: environment = client.environments.get(environment_id) - if environment.status == 'pending' and i > 5*60: + if environment.status == 'pending' and i > 5 * 60: raise EnvironmentError( "Environment deployment hasn't started") - elif environment.status == 'deploying' and i > 65*60: + elif environment.status == 'deploying' and i > 65 * 60: raise EnvironmentError( "Environment deployment takes too long") elif environment.status == 'ready': diff --git a/muranoconductor/app.py b/muranoconductor/app.py index 90e4d5b..6310606 100644 --- a/muranoconductor/app.py +++ b/muranoconductor/app.py @@ -15,7 +15,6 @@ import glob import sys -import traceback import anyjson import eventlet @@ -28,7 +27,7 @@ import reporting from muranocommon.messaging import MqClient, Message from muranoconductor import config as cfg from muranocommon.helpers.token_sanitizer import TokenSanitizer - +from muranoconductor import metadata import vm_agent import cloud_formation @@ -80,23 +79,29 @@ class ConductorWorkflowService(service.Service): message_id = message.id do_ack = False reporter = None + with self.create_rmq_client() as mq: try: + secure_task = TokenSanitizer().sanitize(task) log.info('Starting processing task {0}: {1}'.format( message_id, anyjson.dumps(secure_task))) reporter = reporting.Reporter(mq, message_id, task['id']) - config = Config() + metadata_version = metadata.get_metadata(task['id'], + task['token']) command_dispatcher = CommandDispatcher('e' + task['id'], mq, task['token'], task['tenant_id'], reporter) + workflows = [] - for path in glob.glob("data/workflows/*.xml"): + config = Config() + for path in glob.glob( + '{0}/workflows/*.xml'.format(metadata_version)): log.debug('Loading XML {0}'.format(path)) workflow = Workflow(path, task, command_dispatcher, config, - reporter) + reporter, metadata_version) workflows.append(workflow) stop = False @@ -131,9 +136,11 @@ class ConductorWorkflowService(service.Service): if stop: log.info("Workflow stopped by 'stop' command") do_ack = True + metadata.release(task['id']) except Exception as ex: log.exception(ex) - log.debug("Non-processable message detected, will ack message") + log.debug("Non-processable message detected, " + "will ack message") do_ack = True finally: if do_ack: diff --git a/muranoconductor/cloud_formation.py b/muranoconductor/cloud_formation.py index 0bef346..0ea07c2 100644 --- a/muranoconductor/cloud_formation.py +++ b/muranoconductor/cloud_formation.py @@ -14,14 +14,14 @@ # limitations under the License. import base64 -import config +from os.path import basename import random import string import time import datetime - import xml_code_engine from openstack.common import log as logging +from muranoconductor import config as cfg log = logging.getLogger(__name__) @@ -29,6 +29,7 @@ log = logging.getLogger(__name__) def update_cf_stack(engine, context, body, template, result=None, error=None, **kwargs): command_dispatcher = context['/commandDispatcher'] + metadata_id = context['/metadata_id'] def callback(result_value, error_result=None): if result is not None: @@ -60,7 +61,8 @@ def update_cf_stack(engine, context, body, template, result=None, error=None, name='cf', command='CreateOrUpdate', template=template, mappings=(kwargs.get('mappings') or {}), arguments=(kwargs.get('arguments') or {}), - callback=callback) + callback=callback, + metadata_id=metadata_id) def delete_cf_stack(engine, context, body, **kwargs): @@ -77,11 +79,13 @@ def delete_cf_stack(engine, context, body, **kwargs): def prepare_user_data(context, hostname, service, unit, template='Default', initFile='init.ps1', **kwargs): - settings = config.CONF.rabbitmq - - with open('data/{0}'.format(initFile)) as init_script_file: - with open('data/templates/agent-config/{0}.template'.format( - template)) as template_file: + settings = cfg.CONF.rabbitmq + path_to_init_file = '{0}/{1}'.format(basename(cfg.CONF.init_scripts_dir), + initFile) + with open(path_to_init_file) as init_script_file: + with open('{0}/{1}.template'.format( + basename(cfg.CONF.agent_config_dir), template) + ) as template_file: init_script = init_script_file.read() template_data = template_file.read() @@ -108,7 +112,7 @@ def prepare_user_data(context, hostname, service, unit, init_script = init_script.replace('%INTERNAL_HOSTNAME%', hostname) init_script = init_script.replace( '%MURANO_SERVER_ADDRESS%', - config.CONF.file_server or settings.host) + cfg.CONF.file_server or settings.host) init_script = init_script.replace( '%CA_ROOT_CERT_BASE64%', @@ -124,7 +128,7 @@ def set_config_params(template_data, replacements): def get_ca_certificate(): - ca_file = (config.CONF.rabbitmq.ca_certs or '').strip() + ca_file = (cfg.CONF.rabbitmq.ca_certs or '').strip() if not ca_file: return '' with open(ca_file) as stream: diff --git a/muranoconductor/cmd/run.py b/muranoconductor/cmd/run.py index d653134..df18890 100644 --- a/muranoconductor/cmd/run.py +++ b/muranoconductor/cmd/run.py @@ -31,12 +31,13 @@ from muranoconductor import config from muranoconductor.openstack.common import log from muranoconductor.openstack.common import service from muranoconductor.app import ConductorWorkflowService +from muranoconductor import metadata def main(): try: config.parse_args() - os.chdir(config.CONF.data_dir) + metadata.prepare(config.CONF.data_dir) log.setup('conductor') launcher = service.ServiceLauncher() launcher.launch_service(ConductorWorkflowService()) diff --git a/muranoconductor/commands/cloud_formation.py b/muranoconductor/commands/cloud_formation.py index a7d58e1..24d13dd 100644 --- a/muranoconductor/commands/cloud_formation.py +++ b/muranoconductor/commands/cloud_formation.py @@ -77,12 +77,16 @@ class HeatExecutor(CommandBase): kwargs.get('mappings') or {}), muranoconductor.helpers.str2unicode( kwargs.get('arguments') or {}), - callback) + callback, + kwargs['metadata_id']) elif command == 'Delete': return self._execute_delete(callback) - def _execute_create_update(self, template, mappings, arguments, callback): - with open('data/templates/cf/%s.template' % template) as template_file: + def _execute_create_update(self, template, mappings, + arguments, callback, metadata_id): + template_path = '{0}/templates/cf/{1}.template'.format(metadata_id, + template) + with open(template_path) as template_file: template_data = template_file.read() template_data = muranoconductor.helpers.transform_json( diff --git a/muranoconductor/commands/dispatcher.py b/muranoconductor/commands/dispatcher.py index 5850f40..bb98520 100644 --- a/muranoconductor/commands/dispatcher.py +++ b/muranoconductor/commands/dispatcher.py @@ -19,12 +19,13 @@ import vm_agent class CommandDispatcher(command.CommandBase): - def __init__(self, environment, rmqclient, token, tenant_id, reporter): + def __init__(self, environment, rmqclient, token, + tenant_id, reporter): self._command_map = { 'cf': cloud_formation.HeatExecutor(environment, token, tenant_id, reporter), - 'agent': vm_agent.VmAgentExecutor( - environment, rmqclient, reporter) + 'agent': vm_agent.VmAgentExecutor(environment, rmqclient, + reporter) } def execute(self, name, **kwargs): diff --git a/muranoconductor/commands/vm_agent.py b/muranoconductor/commands/vm_agent.py index 13f8164..4720b62 100644 --- a/muranoconductor/commands/vm_agent.py +++ b/muranoconductor/commands/vm_agent.py @@ -21,9 +21,11 @@ class VmAgentExecutor(CommandBase): self._reporter = reporter rmqclient.declare(self._results_queue) - def execute(self, template, mappings, unit, service, callback, + def execute(self, template, mappings, unit, service, callback, metadata_id, timeout=None): - template_path = 'data/templates/agent/%s.template' % template + template_path = '{0}/templates/agent/{1}.template'.format(metadata_id, + template) + #with open(template_path) as t_file: # template_data = t_file.read() # diff --git a/muranoconductor/config.py b/muranoconductor/config.py index a40727d..eadaeeb 100644 --- a/muranoconductor/config.py +++ b/muranoconductor/config.py @@ -34,6 +34,12 @@ paste_deploy_opts = [ cfg.StrOpt('config_file'), ] +directories = [ + cfg.StrOpt('data_dir', default='program_data'), + cfg.StrOpt('init_scripts_dir', default='./etc/init-scripts'), + cfg.StrOpt('agent_config_dir', default='./etc/agent-config'), +] + rabbit_opts = [ cfg.StrOpt('host', default='localhost'), cfg.IntOpt('port', default=5672), @@ -65,8 +71,10 @@ CONF.register_opts(paste_deploy_opts, group='paste_deploy') CONF.register_opts(rabbit_opts, group='rabbitmq') CONF.register_opts(heat_opts, group='heat') CONF.register_opts(keystone_opts, group='keystone') +CONF.register_opts(directories) CONF.register_opt(cfg.StrOpt('file_server')) -CONF.register_cli_opt(cfg.StrOpt('data_dir', default='./')) +CONF.register_cli_opt(cfg.StrOpt('murano_metadata_url')) + CONF.register_opt(cfg.IntOpt('max_environments', default=20)) diff --git a/muranoconductor/metadata.py b/muranoconductor/metadata.py new file mode 100644 index 0000000..bd8ac12 --- /dev/null +++ b/muranoconductor/metadata.py @@ -0,0 +1,162 @@ +# Copyright (c) 2013 Mirantis 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 sys +import tarfile +import shutil +import tempfile +import hashlib +from glob import glob +from metadataclient.common.exceptions import CommunicationError +from muranoconductor import config +from metadataclient.v1.client import Client +import os +from openstack.common import log as logging + +CHUNK_SIZE = 1 << 20 # 1MB + +log = logging.getLogger(__name__) +CONF = config.CONF + + +class MetadataException(BaseException): + # Inherited not from Exception in purpose: + # On this exception ack message would not be sent + pass + + +def _unpack_data_archive(task_id, hash): + archive_name = hash + '.tar.gz' + if not tarfile.is_tarfile(archive_name): + raise MetadataException('Received invalid file {0} from Metadata ' + 'Repository'.format(hash)) + dst_dir = task_id + if not os.path.exists(dst_dir): + os.mkdir(dst_dir) + with tarfile.open(archive_name, 'r:gz') as tar: + tar.extractall(path=dst_dir) + return dst_dir + + +def get_endpoint(): + endpoint = CONF.murano_metadata_url + + if not endpoint: + #TODO: add keystone catalog lookup + pass + return endpoint + + +def metadataclient(token_id): + endpoint = get_endpoint() + return Client(endpoint=endpoint, token=token_id) + + +def get_metadata(task_id, token_id): + hash = _check_existing_hash() + try: + log.debug('Retrieving metadata from Murano Metadata Repository') + resp, body_iter = metadataclient(token_id).\ + metadata_client.get_conductor_data(hash) + except CommunicationError as e: + if hash: + log.warning('Metadata update failed: ' + 'Unable to connect Metadata Repository due to {0}. ' + 'Using existing version of metadata'.format(e)) + else: + log.exception(e) + exc_type, exc_value, exc_traceback = sys.exc_info() + raise MetadataException('Unable to get data ' + 'from Metadata Repository due to {0}: ' + '{1}'.format(exc_type.__name__, exc_value)) + + else: + if resp.status == 304: + log.debug('Metadata unmodified. Using existing archive.') + + elif resp.status == 200: + with tempfile.NamedTemporaryFile(delete=False) as archive: + for chunk in body_iter: + archive.write(chunk) + hash = _get_hash(archive.name) + shutil.move(archive.name, hash + '.tar.gz') + else: + msg = 'Metadata update failed: ' \ + 'Got {0} status in response.'.format(resp.status) + if hash: + log.warning(msg + ' Using existing version of metadata.') + else: + raise MetadataException(msg) + return _unpack_data_archive(task_id, hash) + + +def release(folder): + log.debug('Deleting metadata folder {0}'.format(folder)) + try: + shutil.rmtree(folder) + except Exception as e: + log.exception('Unable to delete folder {0} with ' + 'task metadata due to {1}'.format(folder, e)) + + +def prepare(data_dir): + if not os.path.exists(data_dir): + os.makedirs(data_dir) + init_scripts_dst = os.path.join(data_dir, + os.path.basename(CONF.init_scripts_dir)) + if not os.path.exists(init_scripts_dst): + shutil.copytree(CONF.init_scripts_dir, init_scripts_dst) + agent_config_dst = os.path.join(data_dir, + os.path.basename(CONF.agent_config_dir)) + if not os.path.exists(agent_config_dst): + shutil.copytree(CONF.agent_config_dir, agent_config_dst) + os.chdir(data_dir) + + +def _get_hash(archive_path): + """Calculate SHA1-hash of archive file. + + SHA-1 take a bit more time than MD5 (see http://tinyurl.com/kpj5jy7), + but is more secure. + """ + if os.path.exists(archive_path): + sha1 = hashlib.sha1() + with open(archive_path) as f: + buf = f.read(CHUNK_SIZE) + while buf: + sha1.update(buf) + buf = f.read(CHUNK_SIZE) + hsum = sha1.hexdigest() + log.debug("Archive '{0}' has hash-sum {1}".format(archive_path, hsum)) + return hsum + else: + log.info("Archive '{0}' doesn't exist, no hash to calculate".format( + archive_path)) + return None + + +def _check_existing_hash(): + hash_archives = glob('*.tar.gz') + if not hash_archives: + hash = None + else: + if len(hash_archives) > 1: + log.warning('There are to metadata archive. Deleting them both') + for item in hash_archives: + os.remove(item) + hash = None + else: + file_name, extension = hash_archives[0].split('.', 1) + hash = file_name + return hash diff --git a/muranoconductor/vm_agent.py b/muranoconductor/vm_agent.py index d4d4755..6545e0e 100644 --- a/muranoconductor/vm_agent.py +++ b/muranoconductor/vm_agent.py @@ -74,6 +74,7 @@ def _extract_v2_results(result_value, ok, errors): def send_command(engine, context, body, template, service, unit, mappings=None, result=None, error=None, timeout=None, osVersion=None, **kwargs): + metadata_id = context['/metadata_id'] if not mappings: mappings = {} if osVersion: @@ -112,8 +113,14 @@ def send_command(engine, context, body, template, service, unit, raise UnhandledAgentException(errors) command_dispatcher.execute( - name='agent', template=template, mappings=mappings, - unit=unit, service=service, callback=callback, timeout=timeout) + name='agent', + template=template, + mappings=mappings, + unit=unit, + service=service, + callback=callback, + timeout=timeout, + metadata_id=metadata_id) def _get_array_item(array, index): @@ -127,7 +134,7 @@ def _get_exception_info(data): 'message': _get_array_item(data, 1), 'command': _get_array_item(data, 2), 'details': _get_array_item(data, 3), - 'timestamp': datetime.datetime.now().isoformat() + 'timestamp': datetime.datetime.now().isoformat() } xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command") diff --git a/muranoconductor/workflow.py b/muranoconductor/workflow.py index baf7a1b..5b441b4 100644 --- a/muranoconductor/workflow.py +++ b/muranoconductor/workflow.py @@ -28,7 +28,8 @@ object_id = id class Workflow(object): - def __init__(self, filename, data, command_dispatcher, config, reporter): + def __init__(self, filename, data, command_dispatcher, + config, reporter, metadata_id): self._data = data self._engine = xml_code_engine.XmlCodeEngine() with open(filename) as xml: @@ -40,6 +41,7 @@ class Workflow(object): # format: (rule-id, entity-id) => True for auto-reset bans, # False for permanent bans self._blacklist = {} + self._metadata_id = metadata_id def execute(self): context = function_context.Context() @@ -48,6 +50,7 @@ class Workflow(object): context['/config'] = self._config context['/reporter'] = self._reporter context['/__blacklist'] = self._blacklist + context['/metadata_id'] = self._metadata_id return self._engine.execute(context) def prepare(self): diff --git a/requirements.txt b/requirements.txt index e83ee45..6b28786 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,4 +13,4 @@ oslo.config>=1.2.0 deep murano-common>=0.2.2 PyYAML>=3.1.0 - +murano-metadataclient==0.4.a13.gd65dfd2 diff --git a/tests/conductor/test_heat_commands.py b/tests/conductor/test_heat_commands.py index b0f236d..6d083ac 100644 --- a/tests/conductor/test_heat_commands.py +++ b/tests/conductor/test_heat_commands.py @@ -31,8 +31,10 @@ class TestHeatExecutor(unittest.TestCase): "$key": "$value" } } + self.metadata_id = 'b5bbea94023083e1ee06a52af5663b15c1fb1b7c' self.mfs.add_entries({ - './data/templates/cf/test.template': json.dumps(template)}) + './{0}/templates/cf/test.template'.format(self.metadata_id): + json.dumps(template)}) def tearDown(self): mockfs.restore_builtins() @@ -66,7 +68,8 @@ class TestHeatExecutor(unittest.TestCase): arguments={ 'arg1': 'arg1Value', 'arg2': 'arg2Value'}, - callback=callback) + callback=callback, + metadata_id=self.metadata_id) heat_mock().stacks.get().stack_status = 'CREATE_COMPLETE' heat_mock().stacks.template = mock.MagicMock( @@ -107,7 +110,8 @@ class TestHeatExecutor(unittest.TestCase): arguments={ 'arg1': 'arg1Value', 'arg2': 'arg2Value'}, - callback=callback) + callback=callback, + metadata_id=self.metadata_id) get_mock = heat_mock().stacks.get() get_mock.stack_name = 'stack' diff --git a/tests/conductor/test_vm_agent.py b/tests/conductor/test_vm_agent.py index 05eb69f..ca43292 100644 --- a/tests/conductor/test_vm_agent.py +++ b/tests/conductor/test_vm_agent.py @@ -36,16 +36,23 @@ class TestVmAgent(unittest.TestCase): }], "RebootOnCompletion": 0 } + self.metadata_id = 'a8571e3b1ba6b33f6c7dbe0f81217c5070377abe' self.mfs.add_entries({ - './data/templates/agent/test.template': + './a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/' + 'templates/agent/test.template': json.dumps(self.template), - './data/templates/agent/scripts/Get-DnsListeningIpAddress.ps1': + + './a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/' + 'templates/agent/scripts/Get-DnsListeningIpAddress.ps1': 'function GetDNSip(){\ntest\n}\n', - './data/templates/agent/scripts/Join-Domain.ps1': + + './a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/' + 'templates/agent/scripts/Join-Domain.ps1': 'function JoinDomain(){\ntest\n}\n', }) - self.template_path = './data/templates/agent/test.template' + self.template_path = './a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/' \ + 'templates/agent/test.template' def test_script_encode(self): stack = mock.MagicMock() diff --git a/tests/conductor/test_workflow.py b/tests/conductor/test_workflow.py index 406d3d1..9d57296 100644 --- a/tests/conductor/test_workflow.py +++ b/tests/conductor/test_workflow.py @@ -37,6 +37,7 @@ class TestWorkflow(unittest.TestCase): self.mfs = mockfs.replace_builtins() self.model = json.loads(load_sample('objectModel1.json')) self.original_model = json.loads(load_sample('objectModel1.json')) + self.metadata_id = 'b5bbea94023083e1ee06a52af5663b15c1fb1b7c' def tearDown(self): mockfs.restore_builtins() @@ -45,7 +46,8 @@ class TestWorkflow(unittest.TestCase): self.mfs.add_entries({'test': xml}) stub = mock.MagicMock() stub.side_effect = RuntimeError - workflow = Workflow('test', self.model, stub, stub, stub) + workflow = Workflow('test', self.model, stub, + stub, stub, self.metadata_id) workflow.execute() def test_empty_workflow_leaves_object_model_unchanged(self):