Enable getting data from metadatarepository
Modify unit tests Change-Id: I75a71b48475b75ca6603756c33fad3742fea2900
This commit is contained in:
parent
ce869822e4
commit
bf2cc47a7e
36
etc/agent-config/Default.template
Normal file
36
etc/agent-config/Default.template
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<configuration>
|
||||||
|
<configSections>
|
||||||
|
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
|
||||||
|
</configSections>
|
||||||
|
<startup>
|
||||||
|
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
|
||||||
|
</startup>
|
||||||
|
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<targets>
|
||||||
|
<target name="file" xsi:type="File" fileName="${basedir}/log.txt"
|
||||||
|
layout="${date} ${level}: <${logger:shortName=true}> ${message} ${exception:format=tostring}"/>
|
||||||
|
</targets>
|
||||||
|
|
||||||
|
<rules>
|
||||||
|
<logger name="*" minlevel="Debug" writeTo="file" />
|
||||||
|
</rules>
|
||||||
|
</nlog>
|
||||||
|
<appSettings>
|
||||||
|
<add key="rabbitmq.host" value="%RABBITMQ_HOST%"/>
|
||||||
|
<add key="rabbitmq.port" value="%RABBITMQ_PORT%"/>
|
||||||
|
<add key="rabbitmq.user" value="%RABBITMQ_USER%"/>
|
||||||
|
<add key="rabbitmq.password" value="%RABBITMQ_PASSWORD%"/>
|
||||||
|
<add key="rabbitmq.vhost" value="%RABBITMQ_VHOST%"/>
|
||||||
|
<add key="rabbitmq.inputQueue" value="%RABBITMQ_INPUT_QUEUE%"/>
|
||||||
|
<add key="rabbitmq.resultExchange" value=""/>
|
||||||
|
<add key="rabbitmq.resultRoutingKey" value="%RESULT_QUEUE%"/>
|
||||||
|
<add key="rabbitmq.durableMessages" value="true"/>
|
||||||
|
|
||||||
|
<add key="rabbitmq.ssl" value="%RABBITMQ_SSL%"/>
|
||||||
|
<add key="rabbitmq.allowInvalidCA" value="true"/>
|
||||||
|
<add key="rabbitmq.sslServerName" value=""/>
|
||||||
|
|
||||||
|
</appSettings>
|
||||||
|
</configuration>
|
8
etc/agent-config/Demo.template
Normal file
8
etc/agent-config/Demo.template
Normal file
@ -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%"
|
35
etc/agent-config/Linux.template
Normal file
35
etc/agent-config/Linux.template
Normal file
@ -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%
|
@ -7,9 +7,18 @@ log_file = /tmp/conductor.log
|
|||||||
debug=True
|
debug=True
|
||||||
verbose=True
|
verbose=True
|
||||||
|
|
||||||
# Directory where conductor's data directory located.
|
# Provide directory with initialization scripts
|
||||||
# "data" must be subdirectory to this.
|
init_scripts_dir = ./etc/init-scripts
|
||||||
data_dir = /etc/murano-conductor
|
|
||||||
|
# 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
|
# Maximum number of environments that can be processed simultaneously
|
||||||
max_environments = 20
|
max_environments = 20
|
||||||
@ -45,7 +54,7 @@ endpoint_type = publicURL
|
|||||||
[rabbitmq]
|
[rabbitmq]
|
||||||
# Connection parameters to RabbitMQ service
|
# 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 !!!
|
# !!! Change localhost to your real IP or hostname as this address must be reachable from VMs !!!
|
||||||
host = localhost
|
host = localhost
|
||||||
|
|
||||||
|
11
etc/init-scripts/demo_init.sh
Normal file
11
etc/init-scripts/demo_init.sh
Normal file
@ -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
|
68
etc/init-scripts/init.ps1
Normal file
68
etc/init-scripts/init.ps1
Normal file
@ -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 '<exception>'
|
||||||
|
Write-LogError $_ -EntireObject
|
||||||
|
Write-LogError '</exception>'
|
||||||
|
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'
|
||||||
|
}
|
6
etc/init-scripts/linux_init.sh
Normal file
6
etc/init-scripts/linux_init.sh
Normal file
@ -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
|
@ -47,7 +47,6 @@ class MuranoEnvironment(resource.Resource):
|
|||||||
except HTTPNotFound:
|
except HTTPNotFound:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _find_environment(self, client):
|
def _find_environment(self, client):
|
||||||
environments = client.environments.list()
|
environments = client.environments.list()
|
||||||
for environment in environments:
|
for environment in environments:
|
||||||
@ -75,10 +74,10 @@ class MuranoEnvironment(resource.Resource):
|
|||||||
delay = 2
|
delay = 2
|
||||||
while True:
|
while True:
|
||||||
environment = client.environments.get(environment_id)
|
environment = client.environments.get(environment_id)
|
||||||
if environment.status == 'pending' and i > 5*60:
|
if environment.status == 'pending' and i > 5 * 60:
|
||||||
raise EnvironmentError(
|
raise EnvironmentError(
|
||||||
"Environment deployment hasn't started")
|
"Environment deployment hasn't started")
|
||||||
elif environment.status == 'deploying' and i > 65*60:
|
elif environment.status == 'deploying' and i > 65 * 60:
|
||||||
raise EnvironmentError(
|
raise EnvironmentError(
|
||||||
"Environment deployment takes too long")
|
"Environment deployment takes too long")
|
||||||
elif environment.status == 'ready':
|
elif environment.status == 'ready':
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import glob
|
import glob
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
|
||||||
|
|
||||||
import anyjson
|
import anyjson
|
||||||
import eventlet
|
import eventlet
|
||||||
@ -28,7 +27,7 @@ import reporting
|
|||||||
from muranocommon.messaging import MqClient, Message
|
from muranocommon.messaging import MqClient, Message
|
||||||
from muranoconductor import config as cfg
|
from muranoconductor import config as cfg
|
||||||
from muranocommon.helpers.token_sanitizer import TokenSanitizer
|
from muranocommon.helpers.token_sanitizer import TokenSanitizer
|
||||||
|
from muranoconductor import metadata
|
||||||
import vm_agent
|
import vm_agent
|
||||||
import cloud_formation
|
import cloud_formation
|
||||||
|
|
||||||
@ -80,23 +79,29 @@ class ConductorWorkflowService(service.Service):
|
|||||||
message_id = message.id
|
message_id = message.id
|
||||||
do_ack = False
|
do_ack = False
|
||||||
reporter = None
|
reporter = None
|
||||||
|
|
||||||
with self.create_rmq_client() as mq:
|
with self.create_rmq_client() as mq:
|
||||||
try:
|
try:
|
||||||
|
|
||||||
secure_task = TokenSanitizer().sanitize(task)
|
secure_task = TokenSanitizer().sanitize(task)
|
||||||
log.info('Starting processing task {0}: {1}'.format(
|
log.info('Starting processing task {0}: {1}'.format(
|
||||||
message_id, anyjson.dumps(secure_task)))
|
message_id, anyjson.dumps(secure_task)))
|
||||||
reporter = reporting.Reporter(mq, message_id, task['id'])
|
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,
|
command_dispatcher = CommandDispatcher('e' + task['id'], mq,
|
||||||
task['token'],
|
task['token'],
|
||||||
task['tenant_id'],
|
task['tenant_id'],
|
||||||
reporter)
|
reporter)
|
||||||
|
|
||||||
workflows = []
|
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))
|
log.debug('Loading XML {0}'.format(path))
|
||||||
workflow = Workflow(path, task, command_dispatcher, config,
|
workflow = Workflow(path, task, command_dispatcher, config,
|
||||||
reporter)
|
reporter, metadata_version)
|
||||||
workflows.append(workflow)
|
workflows.append(workflow)
|
||||||
|
|
||||||
stop = False
|
stop = False
|
||||||
@ -131,9 +136,11 @@ class ConductorWorkflowService(service.Service):
|
|||||||
if stop:
|
if stop:
|
||||||
log.info("Workflow stopped by 'stop' command")
|
log.info("Workflow stopped by 'stop' command")
|
||||||
do_ack = True
|
do_ack = True
|
||||||
|
metadata.release(task['id'])
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
log.exception(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
|
do_ack = True
|
||||||
finally:
|
finally:
|
||||||
if do_ack:
|
if do_ack:
|
||||||
|
@ -14,14 +14,14 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import config
|
from os.path import basename
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import xml_code_engine
|
import xml_code_engine
|
||||||
from openstack.common import log as logging
|
from openstack.common import log as logging
|
||||||
|
from muranoconductor import config as cfg
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -29,6 +29,7 @@ log = logging.getLogger(__name__)
|
|||||||
def update_cf_stack(engine, context, body, template, result=None, error=None,
|
def update_cf_stack(engine, context, body, template, result=None, error=None,
|
||||||
**kwargs):
|
**kwargs):
|
||||||
command_dispatcher = context['/commandDispatcher']
|
command_dispatcher = context['/commandDispatcher']
|
||||||
|
metadata_id = context['/metadata_id']
|
||||||
|
|
||||||
def callback(result_value, error_result=None):
|
def callback(result_value, error_result=None):
|
||||||
if result is not 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,
|
name='cf', command='CreateOrUpdate', template=template,
|
||||||
mappings=(kwargs.get('mappings') or {}),
|
mappings=(kwargs.get('mappings') or {}),
|
||||||
arguments=(kwargs.get('arguments') or {}),
|
arguments=(kwargs.get('arguments') or {}),
|
||||||
callback=callback)
|
callback=callback,
|
||||||
|
metadata_id=metadata_id)
|
||||||
|
|
||||||
|
|
||||||
def delete_cf_stack(engine, context, body, **kwargs):
|
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,
|
def prepare_user_data(context, hostname, service, unit,
|
||||||
template='Default', initFile='init.ps1', **kwargs):
|
template='Default', initFile='init.ps1', **kwargs):
|
||||||
settings = config.CONF.rabbitmq
|
settings = cfg.CONF.rabbitmq
|
||||||
|
path_to_init_file = '{0}/{1}'.format(basename(cfg.CONF.init_scripts_dir),
|
||||||
with open('data/{0}'.format(initFile)) as init_script_file:
|
initFile)
|
||||||
with open('data/templates/agent-config/{0}.template'.format(
|
with open(path_to_init_file) as init_script_file:
|
||||||
template)) as template_file:
|
with open('{0}/{1}.template'.format(
|
||||||
|
basename(cfg.CONF.agent_config_dir), template)
|
||||||
|
) as template_file:
|
||||||
init_script = init_script_file.read()
|
init_script = init_script_file.read()
|
||||||
template_data = template_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('%INTERNAL_HOSTNAME%', hostname)
|
||||||
init_script = init_script.replace(
|
init_script = init_script.replace(
|
||||||
'%MURANO_SERVER_ADDRESS%',
|
'%MURANO_SERVER_ADDRESS%',
|
||||||
config.CONF.file_server or settings.host)
|
cfg.CONF.file_server or settings.host)
|
||||||
|
|
||||||
init_script = init_script.replace(
|
init_script = init_script.replace(
|
||||||
'%CA_ROOT_CERT_BASE64%',
|
'%CA_ROOT_CERT_BASE64%',
|
||||||
@ -124,7 +128,7 @@ def set_config_params(template_data, replacements):
|
|||||||
|
|
||||||
|
|
||||||
def get_ca_certificate():
|
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:
|
if not ca_file:
|
||||||
return ''
|
return ''
|
||||||
with open(ca_file) as stream:
|
with open(ca_file) as stream:
|
||||||
|
@ -31,12 +31,13 @@ from muranoconductor import config
|
|||||||
from muranoconductor.openstack.common import log
|
from muranoconductor.openstack.common import log
|
||||||
from muranoconductor.openstack.common import service
|
from muranoconductor.openstack.common import service
|
||||||
from muranoconductor.app import ConductorWorkflowService
|
from muranoconductor.app import ConductorWorkflowService
|
||||||
|
from muranoconductor import metadata
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
config.parse_args()
|
config.parse_args()
|
||||||
os.chdir(config.CONF.data_dir)
|
metadata.prepare(config.CONF.data_dir)
|
||||||
log.setup('conductor')
|
log.setup('conductor')
|
||||||
launcher = service.ServiceLauncher()
|
launcher = service.ServiceLauncher()
|
||||||
launcher.launch_service(ConductorWorkflowService())
|
launcher.launch_service(ConductorWorkflowService())
|
||||||
|
@ -77,12 +77,16 @@ class HeatExecutor(CommandBase):
|
|||||||
kwargs.get('mappings') or {}),
|
kwargs.get('mappings') or {}),
|
||||||
muranoconductor.helpers.str2unicode(
|
muranoconductor.helpers.str2unicode(
|
||||||
kwargs.get('arguments') or {}),
|
kwargs.get('arguments') or {}),
|
||||||
callback)
|
callback,
|
||||||
|
kwargs['metadata_id'])
|
||||||
elif command == 'Delete':
|
elif command == 'Delete':
|
||||||
return self._execute_delete(callback)
|
return self._execute_delete(callback)
|
||||||
|
|
||||||
def _execute_create_update(self, template, mappings, arguments, callback):
|
def _execute_create_update(self, template, mappings,
|
||||||
with open('data/templates/cf/%s.template' % template) as template_file:
|
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 = template_file.read()
|
||||||
|
|
||||||
template_data = muranoconductor.helpers.transform_json(
|
template_data = muranoconductor.helpers.transform_json(
|
||||||
|
@ -19,12 +19,13 @@ import vm_agent
|
|||||||
|
|
||||||
|
|
||||||
class CommandDispatcher(command.CommandBase):
|
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 = {
|
self._command_map = {
|
||||||
'cf': cloud_formation.HeatExecutor(environment, token, tenant_id,
|
'cf': cloud_formation.HeatExecutor(environment, token, tenant_id,
|
||||||
reporter),
|
reporter),
|
||||||
'agent': vm_agent.VmAgentExecutor(
|
'agent': vm_agent.VmAgentExecutor(environment, rmqclient,
|
||||||
environment, rmqclient, reporter)
|
reporter)
|
||||||
}
|
}
|
||||||
|
|
||||||
def execute(self, name, **kwargs):
|
def execute(self, name, **kwargs):
|
||||||
|
@ -21,9 +21,11 @@ class VmAgentExecutor(CommandBase):
|
|||||||
self._reporter = reporter
|
self._reporter = reporter
|
||||||
rmqclient.declare(self._results_queue)
|
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):
|
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:
|
#with open(template_path) as t_file:
|
||||||
# template_data = t_file.read()
|
# template_data = t_file.read()
|
||||||
#
|
#
|
||||||
|
@ -34,6 +34,12 @@ paste_deploy_opts = [
|
|||||||
cfg.StrOpt('config_file'),
|
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 = [
|
rabbit_opts = [
|
||||||
cfg.StrOpt('host', default='localhost'),
|
cfg.StrOpt('host', default='localhost'),
|
||||||
cfg.IntOpt('port', default=5672),
|
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(rabbit_opts, group='rabbitmq')
|
||||||
CONF.register_opts(heat_opts, group='heat')
|
CONF.register_opts(heat_opts, group='heat')
|
||||||
CONF.register_opts(keystone_opts, group='keystone')
|
CONF.register_opts(keystone_opts, group='keystone')
|
||||||
|
CONF.register_opts(directories)
|
||||||
CONF.register_opt(cfg.StrOpt('file_server'))
|
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))
|
CONF.register_opt(cfg.IntOpt('max_environments', default=20))
|
||||||
|
|
||||||
|
162
muranoconductor/metadata.py
Normal file
162
muranoconductor/metadata.py
Normal file
@ -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
|
@ -74,6 +74,7 @@ def _extract_v2_results(result_value, ok, errors):
|
|||||||
def send_command(engine, context, body, template, service, unit,
|
def send_command(engine, context, body, template, service, unit,
|
||||||
mappings=None, result=None, error=None, timeout=None,
|
mappings=None, result=None, error=None, timeout=None,
|
||||||
osVersion=None, **kwargs):
|
osVersion=None, **kwargs):
|
||||||
|
metadata_id = context['/metadata_id']
|
||||||
if not mappings:
|
if not mappings:
|
||||||
mappings = {}
|
mappings = {}
|
||||||
if osVersion:
|
if osVersion:
|
||||||
@ -112,8 +113,14 @@ def send_command(engine, context, body, template, service, unit,
|
|||||||
raise UnhandledAgentException(errors)
|
raise UnhandledAgentException(errors)
|
||||||
|
|
||||||
command_dispatcher.execute(
|
command_dispatcher.execute(
|
||||||
name='agent', template=template, mappings=mappings,
|
name='agent',
|
||||||
unit=unit, service=service, callback=callback, timeout=timeout)
|
template=template,
|
||||||
|
mappings=mappings,
|
||||||
|
unit=unit,
|
||||||
|
service=service,
|
||||||
|
callback=callback,
|
||||||
|
timeout=timeout,
|
||||||
|
metadata_id=metadata_id)
|
||||||
|
|
||||||
|
|
||||||
def _get_array_item(array, index):
|
def _get_array_item(array, index):
|
||||||
@ -127,7 +134,7 @@ def _get_exception_info(data):
|
|||||||
'message': _get_array_item(data, 1),
|
'message': _get_array_item(data, 1),
|
||||||
'command': _get_array_item(data, 2),
|
'command': _get_array_item(data, 2),
|
||||||
'details': _get_array_item(data, 3),
|
'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")
|
xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command")
|
||||||
|
@ -28,7 +28,8 @@ object_id = id
|
|||||||
|
|
||||||
|
|
||||||
class Workflow(object):
|
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._data = data
|
||||||
self._engine = xml_code_engine.XmlCodeEngine()
|
self._engine = xml_code_engine.XmlCodeEngine()
|
||||||
with open(filename) as xml:
|
with open(filename) as xml:
|
||||||
@ -40,6 +41,7 @@ class Workflow(object):
|
|||||||
# format: (rule-id, entity-id) => True for auto-reset bans,
|
# format: (rule-id, entity-id) => True for auto-reset bans,
|
||||||
# False for permanent bans
|
# False for permanent bans
|
||||||
self._blacklist = {}
|
self._blacklist = {}
|
||||||
|
self._metadata_id = metadata_id
|
||||||
|
|
||||||
def execute(self):
|
def execute(self):
|
||||||
context = function_context.Context()
|
context = function_context.Context()
|
||||||
@ -48,6 +50,7 @@ class Workflow(object):
|
|||||||
context['/config'] = self._config
|
context['/config'] = self._config
|
||||||
context['/reporter'] = self._reporter
|
context['/reporter'] = self._reporter
|
||||||
context['/__blacklist'] = self._blacklist
|
context['/__blacklist'] = self._blacklist
|
||||||
|
context['/metadata_id'] = self._metadata_id
|
||||||
return self._engine.execute(context)
|
return self._engine.execute(context)
|
||||||
|
|
||||||
def prepare(self):
|
def prepare(self):
|
||||||
|
@ -13,4 +13,4 @@ oslo.config>=1.2.0
|
|||||||
deep
|
deep
|
||||||
murano-common>=0.2.2
|
murano-common>=0.2.2
|
||||||
PyYAML>=3.1.0
|
PyYAML>=3.1.0
|
||||||
|
murano-metadataclient==0.4.a13.gd65dfd2
|
||||||
|
@ -31,8 +31,10 @@ class TestHeatExecutor(unittest.TestCase):
|
|||||||
"$key": "$value"
|
"$key": "$value"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
self.metadata_id = 'b5bbea94023083e1ee06a52af5663b15c1fb1b7c'
|
||||||
self.mfs.add_entries({
|
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):
|
def tearDown(self):
|
||||||
mockfs.restore_builtins()
|
mockfs.restore_builtins()
|
||||||
@ -66,7 +68,8 @@ class TestHeatExecutor(unittest.TestCase):
|
|||||||
arguments={
|
arguments={
|
||||||
'arg1': 'arg1Value',
|
'arg1': 'arg1Value',
|
||||||
'arg2': 'arg2Value'},
|
'arg2': 'arg2Value'},
|
||||||
callback=callback)
|
callback=callback,
|
||||||
|
metadata_id=self.metadata_id)
|
||||||
|
|
||||||
heat_mock().stacks.get().stack_status = 'CREATE_COMPLETE'
|
heat_mock().stacks.get().stack_status = 'CREATE_COMPLETE'
|
||||||
heat_mock().stacks.template = mock.MagicMock(
|
heat_mock().stacks.template = mock.MagicMock(
|
||||||
@ -107,7 +110,8 @@ class TestHeatExecutor(unittest.TestCase):
|
|||||||
arguments={
|
arguments={
|
||||||
'arg1': 'arg1Value',
|
'arg1': 'arg1Value',
|
||||||
'arg2': 'arg2Value'},
|
'arg2': 'arg2Value'},
|
||||||
callback=callback)
|
callback=callback,
|
||||||
|
metadata_id=self.metadata_id)
|
||||||
|
|
||||||
get_mock = heat_mock().stacks.get()
|
get_mock = heat_mock().stacks.get()
|
||||||
get_mock.stack_name = 'stack'
|
get_mock.stack_name = 'stack'
|
||||||
|
@ -36,16 +36,23 @@ class TestVmAgent(unittest.TestCase):
|
|||||||
}],
|
}],
|
||||||
"RebootOnCompletion": 0
|
"RebootOnCompletion": 0
|
||||||
}
|
}
|
||||||
|
self.metadata_id = 'a8571e3b1ba6b33f6c7dbe0f81217c5070377abe'
|
||||||
|
|
||||||
self.mfs.add_entries({
|
self.mfs.add_entries({
|
||||||
'./data/templates/agent/test.template':
|
'./a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/'
|
||||||
|
'templates/agent/test.template':
|
||||||
json.dumps(self.template),
|
json.dumps(self.template),
|
||||||
'./data/templates/agent/scripts/Get-DnsListeningIpAddress.ps1':
|
|
||||||
|
'./a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/'
|
||||||
|
'templates/agent/scripts/Get-DnsListeningIpAddress.ps1':
|
||||||
'function GetDNSip(){\ntest\n}\n',
|
'function GetDNSip(){\ntest\n}\n',
|
||||||
'./data/templates/agent/scripts/Join-Domain.ps1':
|
|
||||||
|
'./a8571e3b1ba6b33f6c7dbe0f81217c5070377abe/'
|
||||||
|
'templates/agent/scripts/Join-Domain.ps1':
|
||||||
'function JoinDomain(){\ntest\n}\n',
|
'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):
|
def test_script_encode(self):
|
||||||
stack = mock.MagicMock()
|
stack = mock.MagicMock()
|
||||||
|
@ -37,6 +37,7 @@ class TestWorkflow(unittest.TestCase):
|
|||||||
self.mfs = mockfs.replace_builtins()
|
self.mfs = mockfs.replace_builtins()
|
||||||
self.model = json.loads(load_sample('objectModel1.json'))
|
self.model = json.loads(load_sample('objectModel1.json'))
|
||||||
self.original_model = json.loads(load_sample('objectModel1.json'))
|
self.original_model = json.loads(load_sample('objectModel1.json'))
|
||||||
|
self.metadata_id = 'b5bbea94023083e1ee06a52af5663b15c1fb1b7c'
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
mockfs.restore_builtins()
|
mockfs.restore_builtins()
|
||||||
@ -45,7 +46,8 @@ class TestWorkflow(unittest.TestCase):
|
|||||||
self.mfs.add_entries({'test': xml})
|
self.mfs.add_entries({'test': xml})
|
||||||
stub = mock.MagicMock()
|
stub = mock.MagicMock()
|
||||||
stub.side_effect = RuntimeError
|
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()
|
workflow.execute()
|
||||||
|
|
||||||
def test_empty_workflow_leaves_object_model_unchanged(self):
|
def test_empty_workflow_leaves_object_model_unchanged(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user