Licenses added

This commit is contained in:
Stan Lagun 2013-04-04 18:10:25 +04:00
parent f30a833cca
commit c8887c6b66
16 changed files with 966 additions and 790 deletions

View File

@ -1,20 +1,17 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
# 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
# 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
# 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.
# 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

View File

@ -1,90 +1,105 @@
import datetime
import glob
import sys
import traceback
import anyjson
from conductor.openstack.common import service
from workflow import Workflow
from commands.dispatcher import CommandDispatcher
from openstack.common import log as logging
from config import Config
import reporting
import rabbitmq
import windows_agent
import cloud_formation
config = Config(sys.argv[1] if len(sys.argv) > 1 else None)
log = logging.getLogger(__name__)
def task_received(task, message_id):
with rabbitmq.RmqClient() as rmqclient:
try:
log.info('Starting processing task {0}: {1}'.format(
message_id, anyjson.dumps(task)))
reporter = reporting.Reporter(rmqclient, message_id, task['id'])
command_dispatcher = CommandDispatcher(
task['name'], rmqclient, task['token'], task['tenant_id'])
workflows = []
for path in glob.glob("data/workflows/*.xml"):
log.debug('Loading XML {0}'.format(path))
workflow = Workflow(path, task, command_dispatcher, config,
reporter)
workflows.append(workflow)
while True:
try:
while True:
result = False
for workflow in workflows:
if workflow.execute():
result = True
if not result:
break
if not command_dispatcher.execute_pending():
break
except Exception as ex:
log.exception(ex)
break
command_dispatcher.close()
finally:
del task['token']
result_msg = rabbitmq.Message()
result_msg.body = task
result_msg.id = message_id
rmqclient.send(message=result_msg, key='task-results')
log.info('Finished processing task {0}. Result = {1}'.format(
message_id, anyjson.dumps(task)))
class ConductorWorkflowService(service.Service):
def __init__(self):
super(ConductorWorkflowService, self).__init__()
def start(self):
super(ConductorWorkflowService, self).start()
self.tg.add_thread(self._start_rabbitmq)
def stop(self):
super(ConductorWorkflowService, self).stop()
def _start_rabbitmq(self):
while True:
try:
with rabbitmq.RmqClient() as rmq:
rmq.declare('tasks', 'tasks')
rmq.declare('task-results')
with rmq.open('tasks') as subscription:
while True:
msg = subscription.get_message()
self.tg.add_thread(
task_received, msg.body, msg.id)
except Exception as ex:
log.exception(ex)
# 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 datetime
import glob
import sys
import traceback
import anyjson
from conductor.openstack.common import service
from workflow import Workflow
from commands.dispatcher import CommandDispatcher
from openstack.common import log as logging
from config import Config
import reporting
import rabbitmq
import windows_agent
import cloud_formation
config = Config(sys.argv[1] if len(sys.argv) > 1 else None)
log = logging.getLogger(__name__)
def task_received(task, message_id):
with rabbitmq.RmqClient() as rmqclient:
try:
log.info('Starting processing task {0}: {1}'.format(
message_id, anyjson.dumps(task)))
reporter = reporting.Reporter(rmqclient, message_id, task['id'])
command_dispatcher = CommandDispatcher(
task['name'], rmqclient, task['token'], task['tenant_id'])
workflows = []
for path in glob.glob("data/workflows/*.xml"):
log.debug('Loading XML {0}'.format(path))
workflow = Workflow(path, task, command_dispatcher, config,
reporter)
workflows.append(workflow)
while True:
try:
while True:
result = False
for workflow in workflows:
if workflow.execute():
result = True
if not result:
break
if not command_dispatcher.execute_pending():
break
except Exception as ex:
log.exception(ex)
break
command_dispatcher.close()
finally:
del task['token']
result_msg = rabbitmq.Message()
result_msg.body = task
result_msg.id = message_id
rmqclient.send(message=result_msg, key='task-results')
log.info('Finished processing task {0}. Result = {1}'.format(
message_id, anyjson.dumps(task)))
class ConductorWorkflowService(service.Service):
def __init__(self):
super(ConductorWorkflowService, self).__init__()
def start(self):
super(ConductorWorkflowService, self).start()
self.tg.add_thread(self._start_rabbitmq)
def stop(self):
super(ConductorWorkflowService, self).stop()
def _start_rabbitmq(self):
while True:
try:
with rabbitmq.RmqClient() as rmq:
rmq.declare('tasks', 'tasks')
rmq.declare('task-results')
with rmq.open('tasks') as subscription:
while True:
msg = subscription.get_message()
self.tg.add_thread(
task_received, msg.body, msg.id)
except Exception as ex:
log.exception(ex)

View File

@ -1,98 +1,113 @@
import base64
import xml_code_engine
import config
from random import choice
import time
import string
def update_cf_stack(engine, context, body, template,
mappings, arguments, **kwargs):
command_dispatcher = context['/commandDispatcher']
callback = lambda result: engine.evaluate_content(
body.find('success'), context)
command_dispatcher.execute(
name='cf', command='CreateOrUpdate', template=template,
mappings=mappings, arguments=arguments, callback=callback)
def delete_cf_stack(engine, context, body, **kwargs):
command_dispatcher = context['/commandDispatcher']
callback = lambda result: engine.evaluate_content(
body.find('success'), context)
command_dispatcher.execute(
name='cf', command='Delete', callback=callback)
def prepare_user_data(context, hostname, service, unit, template='Default', **kwargs):
settings = config.CONF.rabbitmq
with open('data/init.ps1') as init_script_file:
with open('data/templates/agent-config/{0}.template'.format(
template)) as template_file:
init_script = init_script_file.read()
template_data = template_file.read()
template_data = template_data.replace(
'%RABBITMQ_HOST%', settings.host)
template_data = template_data.replace(
'%RABBITMQ_INPUT_QUEUE%',
'-'.join([str(context['/dataSource']['name']),
str(service), str(unit)]).lower()
)
template_data = template_data.replace(
'%RESULT_QUEUE%',
'-execution-results-{0}'.format(
str(context['/dataSource']['name'])).lower())
init_script = init_script.replace(
'%WINDOWS_AGENT_CONFIG_BASE64%',
base64.b64encode(template_data))
init_script = init_script.replace('%INTERNAL_HOSTNAME%', hostname)
return init_script
counter = 0
def int2base(x, base):
digs = string.digits + string.lowercase
if x < 0: sign = -1
elif x==0: return '0'
else: sign = 1
x *= sign
digits = []
while x:
digits.append(digs[x % base])
x /= base
if sign < 0:
digits.append('-')
digits.reverse()
return ''.join(digits)
def generate_hostname(**kwargs):
global counter
prefix = ''.join(choice(string.lowercase) for _ in range(5))
timestamp = int2base(int(time.time() * 1000), 36)[:8]
suffix = int2base(counter, 36)
counter = (counter + 1) % 1296
return prefix + timestamp + suffix
xml_code_engine.XmlCodeEngine.register_function(
update_cf_stack, "update-cf-stack")
xml_code_engine.XmlCodeEngine.register_function(
delete_cf_stack, "delete-cf-stack")
xml_code_engine.XmlCodeEngine.register_function(
prepare_user_data, "prepare-user-data")
xml_code_engine.XmlCodeEngine.register_function(
generate_hostname, "generate-hostname")
# 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 base64
import xml_code_engine
import config
from random import choice
import time
import string
def update_cf_stack(engine, context, body, template,
mappings, arguments, **kwargs):
command_dispatcher = context['/commandDispatcher']
callback = lambda result: engine.evaluate_content(
body.find('success'), context)
command_dispatcher.execute(
name='cf', command='CreateOrUpdate', template=template,
mappings=mappings, arguments=arguments, callback=callback)
def delete_cf_stack(engine, context, body, **kwargs):
command_dispatcher = context['/commandDispatcher']
callback = lambda result: engine.evaluate_content(
body.find('success'), context)
command_dispatcher.execute(
name='cf', command='Delete', callback=callback)
def prepare_user_data(context, hostname, service, unit,
template='Default', **kwargs):
settings = config.CONF.rabbitmq
with open('data/init.ps1') as init_script_file:
with open('data/templates/agent-config/{0}.template'.format(
template)) as template_file:
init_script = init_script_file.read()
template_data = template_file.read()
template_data = template_data.replace(
'%RABBITMQ_HOST%', settings.host)
template_data = template_data.replace(
'%RABBITMQ_INPUT_QUEUE%',
'-'.join([str(context['/dataSource']['name']),
str(service), str(unit)]).lower()
)
template_data = template_data.replace(
'%RESULT_QUEUE%',
'-execution-results-{0}'.format(
str(context['/dataSource']['name'])).lower())
init_script = init_script.replace(
'%WINDOWS_AGENT_CONFIG_BASE64%',
base64.b64encode(template_data))
init_script = init_script.replace('%INTERNAL_HOSTNAME%', hostname)
return init_script
counter = 0
def int2base(x, base):
digs = string.digits + string.lowercase
if x < 0: sign = -1
elif x==0: return '0'
else: sign = 1
x *= sign
digits = []
while x:
digits.append(digs[x % base])
x /= base
if sign < 0:
digits.append('-')
digits.reverse()
return ''.join(digits)
def generate_hostname(**kwargs):
global counter
prefix = ''.join(choice(string.lowercase) for _ in range(5))
timestamp = int2base(int(time.time() * 1000), 36)[:8]
suffix = int2base(counter, 36)
counter = (counter + 1) % 1296
return prefix + timestamp + suffix
xml_code_engine.XmlCodeEngine.register_function(
update_cf_stack, "update-cf-stack")
xml_code_engine.XmlCodeEngine.register_function(
delete_cf_stack, "delete-cf-stack")
xml_code_engine.XmlCodeEngine.register_function(
prepare_user_data, "prepare-user-data")
xml_code_engine.XmlCodeEngine.register_function(
generate_hostname, "generate-hostname")

View File

@ -1 +1,16 @@
# 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 command

View File

@ -1,169 +1,188 @@
import anyjson
import eventlet
import jsonpath
from conductor.openstack.common import log as logging
import conductor.helpers
from command import CommandBase
import conductor.config
from heatclient.client import Client
import heatclient.exc
from keystoneclient.v2_0 import client as ksclient
import types
log = logging.getLogger(__name__)
class HeatExecutor(CommandBase):
def __init__(self, stack, token, tenant_id):
self._update_pending_list = []
self._delete_pending_list = []
self._stack = stack
settings = conductor.config.CONF.heat
client = ksclient.Client(endpoint=settings.auth_url)
auth_data = client.tokens.authenticate(
tenant_id=tenant_id,
token=token)
scoped_token = auth_data.id
heat_url = jsonpath.jsonpath(auth_data.serviceCatalog,
"$[?(@.name == 'heat')].endpoints[0].publicURL")[0]
self._heat_client = Client('1', heat_url,
token_only=True, token=scoped_token)
def execute(self, command, callback, **kwargs):
log.debug('Got command {0} on stack {1}'.format(command, self._stack))
if command == 'CreateOrUpdate':
return self._execute_create_update(
kwargs['template'],
kwargs['mappings'],
kwargs['arguments'],
callback)
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:
template_data = template_file.read()
template_data = conductor.helpers.transform_json(
anyjson.loads(template_data), mappings)
self._update_pending_list.append({
'template': template_data,
'arguments': arguments,
'callback': callback
})
def _execute_delete(self, callback):
self._delete_pending_list.append({
'callback': callback
})
def has_pending_commands(self):
return len(self._update_pending_list) + \
len(self._delete_pending_list) > 0
def execute_pending(self):
r1 = self._execute_pending_updates()
r2 = self._execute_pending_deletes()
return r1 or r2
def _execute_pending_updates(self):
if not len(self._update_pending_list):
return False
template, arguments = self._get_current_template()
stack_exists = (template != {})
for t in self._update_pending_list:
template = conductor.helpers.merge_dicts(
template, t['template'], max_levels=2)
arguments = conductor.helpers.merge_dicts(
arguments, t['arguments'], max_levels=1)
log.info(
'Executing heat template {0} with arguments {1} on stack {2}'
.format(anyjson.dumps(template), arguments, self._stack))
if stack_exists:
self._heat_client.stacks.update(
stack_id=self._stack,
parameters=arguments,
template=template)
log.debug(
'Waiting for the stack {0} to be update'.format(self._stack))
self._wait_state('UPDATE_COMPLETE')
log.info('Stack {0} updated'.format(self._stack))
else:
self._heat_client.stacks.create(
stack_name=self._stack,
parameters=arguments,
template=template)
log.debug('Waiting for the stack {0} to be create'.format(
self._stack))
self._wait_state('CREATE_COMPLETE')
log.info('Stack {0} created'.format(self._stack))
pending_list = self._update_pending_list
self._update_pending_list = []
for item in pending_list:
item['callback'](True)
return True
def _execute_pending_deletes(self):
if not len(self._delete_pending_list):
return False
log.debug('Deleting stack {0}'.format(self._stack))
try:
self._heat_client.stacks.delete(
stack_id=self._stack)
log.debug(
'Waiting for the stack {0} to be deleted'.format(self._stack))
self._wait_state(['DELETE_COMPLETE', ''])
log.info('Stack {0} deleted'.format(self._stack))
except Exception as ex:
log.exception(ex)
pending_list = self._delete_pending_list
self._delete_pending_list = []
for item in pending_list:
item['callback'](True)
return True
def _get_current_template(self):
try:
stack_info = self._heat_client.stacks.get(stack_id=self._stack)
template = self._heat_client.stacks.template(
stack_id='{0}/{1}'.format(stack_info.stack_name, stack_info.id))
return template, stack_info.parameters
except heatclient.exc.HTTPNotFound:
return {}, {}
def _wait_state(self, state):
if isinstance(state, types.ListType):
states = state
else:
states = [state]
while True:
try:
status = self._heat_client.stacks.get(
stack_id=self._stack).stack_status
except heatclient.exc.HTTPNotFound:
status = ''
if 'IN_PROGRESS' in status:
eventlet.sleep(1)
continue
if status not in states:
raise EnvironmentError()
return
# 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 anyjson
import eventlet
import types
import jsonpath
from conductor.openstack.common import log as logging
import conductor.helpers
from command import CommandBase
import conductor.config
from heatclient.client import Client
import heatclient.exc
from keystoneclient.v2_0 import client as ksclient
log = logging.getLogger(__name__)
class HeatExecutor(CommandBase):
def __init__(self, stack, token, tenant_id):
self._update_pending_list = []
self._delete_pending_list = []
self._stack = stack
settings = conductor.config.CONF.heat
client = ksclient.Client(endpoint=settings.auth_url)
auth_data = client.tokens.authenticate(
tenant_id=tenant_id,
token=token)
scoped_token = auth_data.id
heat_url = jsonpath.jsonpath(
auth_data.serviceCatalog,
"$[?(@.name == 'heat')].endpoints[0].publicURL")[0]
self._heat_client = Client(
'1',
heat_url,
token_only=True,
token=scoped_token)
def execute(self, command, callback, **kwargs):
log.debug('Got command {0} on stack {1}'.format(command, self._stack))
if command == 'CreateOrUpdate':
return self._execute_create_update(
kwargs['template'],
kwargs['mappings'],
kwargs['arguments'],
callback)
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:
template_data = template_file.read()
template_data = conductor.helpers.transform_json(
anyjson.loads(template_data), mappings)
self._update_pending_list.append({
'template': template_data,
'arguments': arguments,
'callback': callback
})
def _execute_delete(self, callback):
self._delete_pending_list.append({
'callback': callback
})
def has_pending_commands(self):
return len(self._update_pending_list) + \
len(self._delete_pending_list) > 0
def execute_pending(self):
r1 = self._execute_pending_updates()
r2 = self._execute_pending_deletes()
return r1 or r2
def _execute_pending_updates(self):
if not len(self._update_pending_list):
return False
template, arguments = self._get_current_template()
stack_exists = (template != {})
for t in self._update_pending_list:
template = conductor.helpers.merge_dicts(
template, t['template'], max_levels=2)
arguments = conductor.helpers.merge_dicts(
arguments, t['arguments'], max_levels=1)
log.info(
'Executing heat template {0} with arguments {1} on stack {2}'
.format(anyjson.dumps(template), arguments, self._stack))
if stack_exists:
self._heat_client.stacks.update(
stack_id=self._stack,
parameters=arguments,
template=template)
log.debug(
'Waiting for the stack {0} to be update'.format(self._stack))
self._wait_state('UPDATE_COMPLETE')
log.info('Stack {0} updated'.format(self._stack))
else:
self._heat_client.stacks.create(
stack_name=self._stack,
parameters=arguments,
template=template)
log.debug('Waiting for the stack {0} to be create'.format(
self._stack))
self._wait_state('CREATE_COMPLETE')
log.info('Stack {0} created'.format(self._stack))
pending_list = self._update_pending_list
self._update_pending_list = []
for item in pending_list:
item['callback'](True)
return True
def _execute_pending_deletes(self):
if not len(self._delete_pending_list):
return False
log.debug('Deleting stack {0}'.format(self._stack))
try:
self._heat_client.stacks.delete(
stack_id=self._stack)
log.debug(
'Waiting for the stack {0} to be deleted'.format(self._stack))
self._wait_state(['DELETE_COMPLETE', ''])
log.info('Stack {0} deleted'.format(self._stack))
except Exception as ex:
log.exception(ex)
pending_list = self._delete_pending_list
self._delete_pending_list = []
for item in pending_list:
item['callback'](True)
return True
def _get_current_template(self):
try:
stack_info = self._heat_client.stacks.get(stack_id=self._stack)
template = self._heat_client.stacks.template(
stack_id='{0}/{1}'.format(stack_info.stack_name, stack_info.id))
return template, stack_info.parameters
except heatclient.exc.HTTPNotFound:
return {}, {}
def _wait_state(self, state):
if isinstance(state, types.ListType):
states = state
else:
states = [state]
while True:
try:
status = self._heat_client.stacks.get(
stack_id=self._stack).stack_status
except heatclient.exc.HTTPNotFound:
status = ''
if 'IN_PROGRESS' in status:
eventlet.sleep(1)
continue
if status not in states:
raise EnvironmentError()
return

View File

@ -1,3 +1,19 @@
# 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.
class CommandBase(object):
def execute(self, **kwargs):
pass

View File

@ -1,33 +1,48 @@
import command
import cloud_formation
import windows_agent
class CommandDispatcher(command.CommandBase):
def __init__(self, environment_id, rmqclient, token, tenant_id):
self._command_map = {
'cf': cloud_formation.HeatExecutor(environment_id, token, tenant_id),
'agent': windows_agent.WindowsAgentExecutor(
environment_id, rmqclient)
}
def execute(self, name, **kwargs):
self._command_map[name].execute(**kwargs)
def execute_pending(self):
result = False
for command in self._command_map.values():
result |= command.execute_pending()
return result
def has_pending_commands(self):
result = False
for command in self._command_map.values():
result |= command.has_pending_commands()
return result
def close(self):
for t in self._command_map.values():
t.close()
# 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 command
import cloud_formation
import windows_agent
class CommandDispatcher(command.CommandBase):
def __init__(self, environment, rmqclient, token, tenant_id):
self._command_map = {
'cf': cloud_formation.HeatExecutor(environment, token, tenant_id),
'agent': windows_agent.WindowsAgentExecutor(
environment, rmqclient)
}
def execute(self, name, **kwargs):
self._command_map[name].execute(**kwargs)
def execute_pending(self):
result = False
for command in self._command_map.values():
result |= command.execute_pending()
return result
def has_pending_commands(self):
result = False
for command in self._command_map.values():
result |= command.has_pending_commands()
return result
def close(self):
for t in self._command_map.values():
t.close()

View File

@ -1,213 +1,210 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.
"""
Routines for configuring Glance
"""
import logging
import logging.config
import logging.handlers
import os
import sys
from oslo.config import cfg
from paste import deploy
from conductor.version import version_info as version
from ConfigParser import SafeConfigParser
paste_deploy_opts = [
cfg.StrOpt('flavor'),
cfg.StrOpt('config_file'),
]
rabbit_opts = [
cfg.StrOpt('host', default='localhost'),
cfg.IntOpt('port', default=5672),
cfg.StrOpt('login', default='guest'),
cfg.StrOpt('password', default='guest'),
cfg.StrOpt('virtual_host', default='/'),
]
heat_opts = [
cfg.StrOpt('auth_url'),
]
CONF = cfg.CONF
CONF.register_opts(paste_deploy_opts, group='paste_deploy')
CONF.register_opts(rabbit_opts, group='rabbitmq')
CONF.register_opts(heat_opts, group='heat')
CONF.import_opt('verbose', 'conductor.openstack.common.log')
CONF.import_opt('debug', 'conductor.openstack.common.log')
CONF.import_opt('log_dir', 'conductor.openstack.common.log')
CONF.import_opt('log_file', 'conductor.openstack.common.log')
CONF.import_opt('log_config', 'conductor.openstack.common.log')
CONF.import_opt('log_format', 'conductor.openstack.common.log')
CONF.import_opt('log_date_format', 'conductor.openstack.common.log')
CONF.import_opt('use_syslog', 'conductor.openstack.common.log')
CONF.import_opt('syslog_log_facility', 'conductor.openstack.common.log')
def parse_args(args=None, usage=None, default_config_files=None):
CONF(args=args,
project='conductor',
version=version.cached_version_string(),
usage=usage,
default_config_files=default_config_files)
def setup_logging():
"""
Sets up the logging options for a log with supplied name
"""
if CONF.log_config:
# Use a logging configuration file for all settings...
if os.path.exists(CONF.log_config):
logging.config.fileConfig(CONF.log_config)
return
else:
raise RuntimeError("Unable to locate specified logging "
"config file: %s" % CONF.log_config)
root_logger = logging.root
if CONF.debug:
root_logger.setLevel(logging.DEBUG)
elif CONF.verbose:
root_logger.setLevel(logging.INFO)
else:
root_logger.setLevel(logging.WARNING)
formatter = logging.Formatter(CONF.log_format, CONF.log_date_format)
if CONF.use_syslog:
try:
facility = getattr(logging.handlers.SysLogHandler,
CONF.syslog_log_facility)
except AttributeError:
raise ValueError(_("Invalid syslog facility"))
handler = logging.handlers.SysLogHandler(address='/dev/log',
facility=facility)
elif CONF.log_file:
logfile = CONF.log_file
if CONF.log_dir:
logfile = os.path.join(CONF.log_dir, logfile)
handler = logging.handlers.WatchedFileHandler(logfile)
else:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
root_logger.addHandler(handler)
def _get_deployment_flavor():
"""
Retrieve the paste_deploy.flavor config item, formatted appropriately
for appending to the application name.
"""
flavor = CONF.paste_deploy.flavor
return '' if not flavor else ('-' + flavor)
def _get_paste_config_path():
paste_suffix = '-paste.ini'
conf_suffix = '.conf'
if CONF.config_file:
# Assume paste config is in a paste.ini file corresponding
# to the last config file
path = CONF.config_file[-1].replace(conf_suffix, paste_suffix)
else:
path = CONF.prog + '-paste.ini'
return CONF.find_file(os.path.basename(path))
def _get_deployment_config_file():
"""
Retrieve the deployment_config_file config item, formatted as an
absolute pathname.
"""
path = CONF.paste_deploy.config_file
if not path:
path = _get_paste_config_path()
if not path:
msg = "Unable to locate paste config file for %s." % CONF.prog
raise RuntimeError(msg)
return os.path.abspath(path)
def load_paste_app(app_name=None):
"""
Builds and returns a WSGI app from a paste config file.
We assume the last config file specified in the supplied ConfigOpts
object is the paste config file.
:param app_name: name of the application to load
:raises RuntimeError when config file cannot be located or application
cannot be loaded from config file
"""
if app_name is None:
app_name = CONF.prog
# append the deployment flavor to the application name,
# in order to identify the appropriate paste pipeline
app_name += _get_deployment_flavor()
conf_file = _get_deployment_config_file()
try:
logger = logging.getLogger(__name__)
logger.debug(_("Loading %(app_name)s from %(conf_file)s"),
{'conf_file': conf_file, 'app_name': app_name})
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
# Log the options used when starting if we're in debug mode...
if CONF.debug:
CONF.log_opt_values(logger, logging.DEBUG)
return app
except (LookupError, ImportError), e:
msg = _("Unable to load %(app_name)s from "
"configuration file %(conf_file)s."
"\nGot: %(e)r") % locals()
logger.error(msg)
raise RuntimeError(msg)
class Config(object):
CONFIG_PATH = './etc/app.config'
def __init__(self, filename=None):
self.config = SafeConfigParser()
self.config.read(filename or self.CONFIG_PATH)
def get_setting(self, section, name, default=None):
if not self.config.has_option(section, name):
return default
return self.config.get(section, name)
def __getitem__(self, item):
parts = item.rsplit('.', 1)
return self.get_setting(
parts[0] if len(parts) == 2 else 'DEFAULT', parts[-1])
# Copyright 2011 OpenStack LLC.
# 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.
"""
Routines for configuring Glance
"""
import logging
import logging.config
import logging.handlers
import os
import sys
from oslo.config import cfg
from paste import deploy
from conductor.version import version_info as version
from ConfigParser import SafeConfigParser
paste_deploy_opts = [
cfg.StrOpt('flavor'),
cfg.StrOpt('config_file'),
]
rabbit_opts = [
cfg.StrOpt('host', default='localhost'),
cfg.IntOpt('port', default=5672),
cfg.StrOpt('login', default='guest'),
cfg.StrOpt('password', default='guest'),
cfg.StrOpt('virtual_host', default='/'),
]
heat_opts = [
cfg.StrOpt('auth_url'),
]
CONF = cfg.CONF
CONF.register_opts(paste_deploy_opts, group='paste_deploy')
CONF.register_opts(rabbit_opts, group='rabbitmq')
CONF.register_opts(heat_opts, group='heat')
CONF.import_opt('verbose', 'conductor.openstack.common.log')
CONF.import_opt('debug', 'conductor.openstack.common.log')
CONF.import_opt('log_dir', 'conductor.openstack.common.log')
CONF.import_opt('log_file', 'conductor.openstack.common.log')
CONF.import_opt('log_config', 'conductor.openstack.common.log')
CONF.import_opt('log_format', 'conductor.openstack.common.log')
CONF.import_opt('log_date_format', 'conductor.openstack.common.log')
CONF.import_opt('use_syslog', 'conductor.openstack.common.log')
CONF.import_opt('syslog_log_facility', 'conductor.openstack.common.log')
def parse_args(args=None, usage=None, default_config_files=None):
CONF(args=args,
project='conductor',
version=version.cached_version_string(),
usage=usage,
default_config_files=default_config_files)
def setup_logging():
"""
Sets up the logging options for a log with supplied name
"""
if CONF.log_config:
# Use a logging configuration file for all settings...
if os.path.exists(CONF.log_config):
logging.config.fileConfig(CONF.log_config)
return
else:
raise RuntimeError("Unable to locate specified logging "
"config file: %s" % CONF.log_config)
root_logger = logging.root
if CONF.debug:
root_logger.setLevel(logging.DEBUG)
elif CONF.verbose:
root_logger.setLevel(logging.INFO)
else:
root_logger.setLevel(logging.WARNING)
formatter = logging.Formatter(CONF.log_format, CONF.log_date_format)
if CONF.use_syslog:
try:
facility = getattr(logging.handlers.SysLogHandler,
CONF.syslog_log_facility)
except AttributeError:
raise ValueError(_("Invalid syslog facility"))
handler = logging.handlers.SysLogHandler(address='/dev/log',
facility=facility)
elif CONF.log_file:
logfile = CONF.log_file
if CONF.log_dir:
logfile = os.path.join(CONF.log_dir, logfile)
handler = logging.handlers.WatchedFileHandler(logfile)
else:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
root_logger.addHandler(handler)
def _get_deployment_flavor():
"""
Retrieve the paste_deploy.flavor config item, formatted appropriately
for appending to the application name.
"""
flavor = CONF.paste_deploy.flavor
return '' if not flavor else ('-' + flavor)
def _get_paste_config_path():
paste_suffix = '-paste.ini'
conf_suffix = '.conf'
if CONF.config_file:
# Assume paste config is in a paste.ini file corresponding
# to the last config file
path = CONF.config_file[-1].replace(conf_suffix, paste_suffix)
else:
path = CONF.prog + '-paste.ini'
return CONF.find_file(os.path.basename(path))
def _get_deployment_config_file():
"""
Retrieve the deployment_config_file config item, formatted as an
absolute pathname.
"""
path = CONF.paste_deploy.config_file
if not path:
path = _get_paste_config_path()
if not path:
msg = "Unable to locate paste config file for %s." % CONF.prog
raise RuntimeError(msg)
return os.path.abspath(path)
def load_paste_app(app_name=None):
"""
Builds and returns a WSGI app from a paste config file.
We assume the last config file specified in the supplied ConfigOpts
object is the paste config file.
:param app_name: name of the application to load
:raises RuntimeError when config file cannot be located or application
cannot be loaded from config file
"""
if app_name is None:
app_name = CONF.prog
# append the deployment flavor to the application name,
# in order to identify the appropriate paste pipeline
app_name += _get_deployment_flavor()
conf_file = _get_deployment_config_file()
try:
logger = logging.getLogger(__name__)
logger.debug(_("Loading %(app_name)s from %(conf_file)s"),
{'conf_file': conf_file, 'app_name': app_name})
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
# Log the options used when starting if we're in debug mode...
if CONF.debug:
CONF.log_opt_values(logger, logging.DEBUG)
return app
except (LookupError, ImportError), e:
msg = _("Unable to load %(app_name)s from "
"configuration file %(conf_file)s."
"\nGot: %(e)r") % locals()
logger.error(msg)
raise RuntimeError(msg)
class Config(object):
CONFIG_PATH = './etc/app.config'
def __init__(self, filename=None):
self.config = SafeConfigParser()
self.config.read(filename or self.CONFIG_PATH)
def get_setting(self, section, name, default=None):
if not self.config.has_option(section, name):
return default
return self.config.get(section, name)
def __getitem__(self, item):
parts = item.rsplit('.', 1)
return self.get_setting(
parts[0] if len(parts) == 2 else 'DEFAULT', parts[-1])

View File

@ -1,3 +1,19 @@
# 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.
class Context(object):
def __init__(self, parent=None):
self._parent = parent

View File

@ -1,3 +1,18 @@
# 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 types

View File

@ -1,127 +1,141 @@
from eventlet import patcher
puka = patcher.import_patched('puka')
#import puka
import anyjson
import config
class RmqClient(object):
def __init__(self):
settings = config.CONF.rabbitmq
self._client = puka.Client('amqp://{0}:{1}@{2}:{3}/{4}'.format(
settings.login,
settings.password,
settings.host,
settings.port,
settings.virtual_host
))
self._connected = False
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
return False
def connect(self):
if not self._connected:
promise = self._client.connect()
self._client.wait(promise, timeout=10000)
self._connected = True
def close(self):
if self._connected:
self._client.close()
self._connected = False
def declare(self, queue, exchange=None):
promise = self._client.queue_declare(str(queue), durable=True)
self._client.wait(promise)
if exchange:
promise = self._client.exchange_declare(str(exchange), durable=True)
self._client.wait(promise)
promise = self._client.queue_bind(
str(queue), str(exchange), routing_key=str(queue))
self._client.wait(promise)
def send(self, message, key, exchange='', timeout=None):
if not self._connected:
raise RuntimeError('Not connected to RabbitMQ')
headers = { 'message_id': message.id }
promise = self._client.basic_publish(
exchange=str(exchange),
routing_key=str(key),
body=anyjson.dumps(message.body),
headers=headers)
self._client.wait(promise, timeout=timeout)
def open(self, queue):
if not self._connected:
raise RuntimeError('Not connected to RabbitMQ')
return Subscription(self._client, queue)
class Subscription(object):
def __init__(self, client, queue):
self._client = client
self._queue = queue
self._promise = None
self._lastMessage = None
def __enter__(self):
self._promise = self._client.basic_consume(
queue=self._queue,
prefetch_count=1)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._ack_last()
promise = self._client.basic_cancel(self._promise)
self._client.wait(promise)
return False
def _ack_last(self):
if self._lastMessage:
self._client.basic_ack(self._lastMessage)
self._lastMessage = None
def get_message(self, timeout=None):
if not self._promise:
raise RuntimeError(
"Subscription object must be used within 'with' block")
self._ack_last()
self._lastMessage = self._client.wait(self._promise, timeout=timeout)
#print self._lastMessage
msg = Message()
msg.body = anyjson.loads(self._lastMessage['body'])
msg.id = self._lastMessage['headers'].get('message_id')
return msg
class Message(object):
def __init__(self):
self._body = {}
self._id = ''
@property
def body(self):
return self._body
@body.setter
def body(self, value):
self._body = value
@property
def id(self):
return self._id
@id.setter
def id(self, value):
self._id = value or ''
# 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.
from eventlet import patcher
puka = patcher.import_patched('puka')
#import puka
import anyjson
import config
class RmqClient(object):
def __init__(self):
settings = config.CONF.rabbitmq
self._client = puka.Client('amqp://{0}:{1}@{2}:{3}/{4}'.format(
settings.login,
settings.password,
settings.host,
settings.port,
settings.virtual_host
))
self._connected = False
def __enter__(self):
self.connect()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.close()
return False
def connect(self):
if not self._connected:
promise = self._client.connect()
self._client.wait(promise, timeout=10000)
self._connected = True
def close(self):
if self._connected:
self._client.close()
self._connected = False
def declare(self, queue, exchange=None):
promise = self._client.queue_declare(str(queue), durable=True)
self._client.wait(promise)
if exchange:
promise = self._client.exchange_declare(str(exchange), durable=True)
self._client.wait(promise)
promise = self._client.queue_bind(
str(queue), str(exchange), routing_key=str(queue))
self._client.wait(promise)
def send(self, message, key, exchange='', timeout=None):
if not self._connected:
raise RuntimeError('Not connected to RabbitMQ')
headers = { 'message_id': message.id }
promise = self._client.basic_publish(
exchange=str(exchange),
routing_key=str(key),
body=anyjson.dumps(message.body),
headers=headers)
self._client.wait(promise, timeout=timeout)
def open(self, queue):
if not self._connected:
raise RuntimeError('Not connected to RabbitMQ')
return Subscription(self._client, queue)
class Subscription(object):
def __init__(self, client, queue):
self._client = client
self._queue = queue
self._promise = None
self._lastMessage = None
def __enter__(self):
self._promise = self._client.basic_consume(
queue=self._queue,
prefetch_count=1)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self._ack_last()
promise = self._client.basic_cancel(self._promise)
self._client.wait(promise)
return False
def _ack_last(self):
if self._lastMessage:
self._client.basic_ack(self._lastMessage)
self._lastMessage = None
def get_message(self, timeout=None):
if not self._promise:
raise RuntimeError(
"Subscription object must be used within 'with' block")
self._ack_last()
self._lastMessage = self._client.wait(self._promise, timeout=timeout)
msg = Message()
msg.body = anyjson.loads(self._lastMessage['body'])
msg.id = self._lastMessage['headers'].get('message_id')
return msg
class Message(object):
def __init__(self):
self._body = {}
self._id = ''
@property
def body(self):
return self._body
@body.setter
def body(self, value):
self._body = value
@property
def id(self):
return self._id
@id.setter
def id(self, value):
self._id = value or ''

View File

@ -1,3 +1,18 @@
# 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 xml_code_engine
import rabbitmq

View File

@ -1,5 +1,3 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may

View File

@ -1,29 +1,44 @@
import xml_code_engine
from openstack.common import log as logging
log = logging.getLogger(__name__)
def send_command(engine, context, body, template, service, host, mappings=None,
result=None, **kwargs):
if not mappings:
mappings = {}
command_dispatcher = context['/commandDispatcher']
def callback(result_value):
log.info(
'Received result from {2} for {0}: {1}'.format(
template, result_value, host))
if result is not None:
context[result] = result_value['Result']
success_handler = body.find('success')
if success_handler is not None:
engine.evaluate_content(success_handler, context)
command_dispatcher.execute(
name='agent', template=template, mappings=mappings,
host=host, service=service, callback=callback)
# 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 xml_code_engine
from openstack.common import log as logging
log = logging.getLogger(__name__)
def send_command(engine, context, body, template, service, host, mappings=None,
result=None, **kwargs):
if not mappings:
mappings = {}
command_dispatcher = context['/commandDispatcher']
def callback(result_value):
log.info(
'Received result from {2} for {0}: {1}'.format(
template, result_value, host))
if result is not None:
context[result] = result_value['Result']
success_handler = body.find('success')
if success_handler is not None:
engine.evaluate_content(success_handler, context)
command_dispatcher.execute(
name='agent', template=template, mappings=mappings,
host=host, service=service, callback=callback)
xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command")

View File

@ -1,3 +1,18 @@
# 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 jsonpath
import types
import re
@ -124,9 +139,6 @@ class Workflow(object):
def _rule_func(match, context, body, engine, limit=0, name=None, **kwargs):
position = context['__dataSource_currentPosition'] or []
# data = context['__dataSource_currentObj']
# if data is None:
# data = context['/dataSource']
position, match = Workflow._get_relative_position(match, context)
data = Workflow._get_path(context['/dataSource'], position)
match = re.sub(r'@\.([\w.]+)',

View File

@ -1,4 +1,18 @@
#from lxml import etree
# 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 xml.etree.ElementTree as etree
import types
@ -121,15 +135,3 @@ XmlCodeEngine.register_function(_function_func, "function")
XmlCodeEngine.register_function(_null_func, "null")
XmlCodeEngine.register_function(_true_func, "true")
XmlCodeEngine.register_function(_false_func, "false")
def xprint(context, body, **kwargs):
print "------------------------ start ------------------------"
for arg in kwargs:
print "%s = %s" % (arg, kwargs[arg])
print 'context = ', context
print 'body = %s (%s)' % (body, body.text)
print "------------------------- end -------------------------"
XmlCodeEngine.register_function(xprint, "print")