Workflows, ExecutionPlanGenerator, Reporting, UserData, conductor improvements

This commit is contained in:
Stan Lagun 2013-03-11 19:11:27 +04:00
parent 9d0b7e39fe
commit 28bc7ff1d5
34 changed files with 852 additions and 276 deletions

View File

@ -3,6 +3,8 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012 # Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WindowsAgent", "WindowsAgent\WindowsAgent.csproj", "{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExecutionPlanGenerator", "ExecutionPlanGenerator\ExecutionPlanGenerator.csproj", "{501BE151-4B8C-4355-88DC-3AEF1921B2D7}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -13,6 +15,10 @@ Global
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.Build.0 = Debug|Any CPU {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.ActiveCfg = Release|Any CPU {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.Build.0 = Release|Any CPU {F7E2A8D5-6D24-4651-A4BC-1024D59F4903}.Release|Any CPU.Build.0 = Release|Any CPU
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{501BE151-4B8C-4355-88DC-3AEF1921B2D7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<configuration> <configuration>
<configSections> <configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/> <section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
@ -11,20 +11,23 @@
<targets> <targets>
<target name="file" xsi:type="File" fileName="${basedir}/log.txt" <target name="file" xsi:type="File" fileName="${basedir}/log.txt"
layout="${date} ${level}: &lt;${logger:shortName=true}&gt; ${message} ${exception:format=tostring}"/> layout="${date} ${level}: &lt;${logger:shortName=true}&gt; ${message} ${exception:format=tostring}"/>
<target name="console" xsi:type="Console"
layout="${date} ${level}: &lt;${logger:shortName=true}&gt; ${message} ${exception:format=tostring}"/>
</targets> </targets>
<rules> <rules>
<logger name="*" minlevel="Debug" writeTo="file" /> <logger name="*" minlevel="Debug" writeTo="file" />
<logger name="*" minlevel="Debug" writeTo="console" />
</rules> </rules>
</nlog> </nlog>
<appSettings> <appSettings>
<add key="rabbitmq.host" value="localhost"/> <add key="rabbitmq.host" value="localhost"/>
<add key="rabbitmq.user" value="guest"/> <add key="rabbitmq.user" value="keero"/>
<add key="rabbitmq.password" value="guest"/> <add key="rabbitmq.password" value="keero"/>
<add key="rabbitmq.vhost" value="/"/> <add key="rabbitmq.vhost" value="keero"/>
<add key="rabbitmq.resultExchange" value=""/> <add key="rabbitmq.resultExchange" value=""/>
<add key="rabbitmq.resultQueue" value="-execution-results"/> <add key="rabbitmq.resultRoutingKey" value="-execution-results"/>
<add key="rabbitmq.durableMessages" value="trueтест"/>
</appSettings> </appSettings>
</configuration> </configuration>

View File

@ -14,8 +14,13 @@ namespace Mirantis.Keero.WindowsAgent
{ {
this.ackFunc = ackFunc; this.ackFunc = ackFunc;
} }
public MqMessage()
{
}
public string Body { get; set; } public string Body { get; set; }
public string Id { get; set; }
public void Ack() public void Ack()
{ {

View File

@ -5,12 +5,15 @@ using System.Linq;
using System.Management.Automation; using System.Management.Automation;
using System.Management.Automation.Runspaces; using System.Management.Automation.Runspaces;
using System.Text; using System.Text;
using NLog;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Mirantis.Keero.WindowsAgent namespace Mirantis.Keero.WindowsAgent
{ {
class PlanExecutor class PlanExecutor
{ {
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
class ExecutionResult class ExecutionResult
{ {
public bool IsException { get; set; } public bool IsException { get; set; }
@ -26,13 +29,14 @@ namespace Mirantis.Keero.WindowsAgent
public bool RebootNeeded { get; set; } public bool RebootNeeded { get; set; }
public string Execute() public void Execute()
{ {
RebootNeeded = false; RebootNeeded = false;
var resultPath = this.path + ".result";
Runspace runSpace = null;
try try
{ {
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path)); var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path));
var resultPath = this.path + ".result";
List<ExecutionResult> currentResults = null; List<ExecutionResult> currentResults = null;
try try
{ {
@ -44,7 +48,7 @@ namespace Mirantis.Keero.WindowsAgent
} }
var runSpace = RunspaceFactory.CreateRunspace(); runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open(); runSpace.Open();
var runSpaceInvoker = new RunspaceInvoke(runSpace); var runSpaceInvoker = new RunspaceInvoke(runSpace);
@ -70,6 +74,11 @@ namespace Mirantis.Keero.WindowsAgent
psCommand.Parameters.Add(kvp.Key, kvp.Value); psCommand.Parameters.Add(kvp.Key, kvp.Value);
} }
} }
Log.Info("Executing {0} {1}", command.Name, string.Join(" ",
(command.Arguments ?? new Dictionary<string, object>()).Select(
t => string.Format("{0}={1}", t.Key, t.Value == null ? "null" : t.Value.ToString()))));
pipeline.Commands.Add(psCommand); pipeline.Commands.Add(psCommand);
try try
{ {
@ -90,6 +99,7 @@ namespace Mirantis.Keero.WindowsAgent
exception.GetType().FullName, exception.Message exception.GetType().FullName, exception.Message
} }
}); });
break;
} }
finally finally
{ {
@ -115,16 +125,27 @@ namespace Mirantis.Keero.WindowsAgent
RebootNeeded = true; RebootNeeded = true;
} }
} }
File.WriteAllText(resultPath, executionResult);
File.Delete(resultPath);
return executionResult;
} }
catch (Exception ex) catch (Exception ex)
{ {
return JsonConvert.SerializeObject(new ExecutionResult { File.WriteAllText(resultPath, JsonConvert.SerializeObject(new ExecutionResult {
IsException = true, IsException = true,
Result = ex.Message Result = ex.Message
}, Formatting.Indented); }, Formatting.Indented));
}
finally
{
if (runSpace != null)
{
try
{
runSpace.Close();
}
catch
{}
}
} }
} }

View File

@ -1,7 +1,11 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Net; using System.Net;
using System.Text;
using System.Threading; using System.Threading;
using NLog; using NLog;
@ -14,6 +18,8 @@ namespace Mirantis.Keero.WindowsAgent
private volatile bool stop; private volatile bool stop;
private Thread thread; private Thread thread;
private RabbitMqClient rabbitMqClient; private RabbitMqClient rabbitMqClient;
private int delayFactor = 1;
private string plansDir;
static void Main(string[] args) static void Main(string[] args)
{ {
@ -23,34 +29,75 @@ namespace Mirantis.Keero.WindowsAgent
protected override void OnStart(string[] args) protected override void OnStart(string[] args)
{ {
base.OnStart(args); base.OnStart(args);
Log.Info("Version 0.3");
this.rabbitMqClient = new RabbitMqClient(); this.rabbitMqClient = new RabbitMqClient();
var basePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
this.plansDir = Path.Combine(basePath, "plans");
if (!Directory.Exists(plansDir))
{
Directory.CreateDirectory(plansDir);
}
this.thread = new Thread(Loop); this.thread = new Thread(Loop);
this.thread.Start(); this.thread.Start();
} }
void Loop() void Loop()
{ {
var doReboot = false; const string unknownName = "unknown";
const string filePath = "data.json";
while (!stop) while (!stop)
{ {
try try
{ {
if (!File.Exists(filePath)) foreach (var file in Directory.GetFiles(this.plansDir, "*.json.result")
.Where(file => !File.Exists(Path.Combine(this.plansDir, Path.GetFileNameWithoutExtension(file)))))
{
var id = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file)) ?? unknownName;
if (id.Equals(unknownName, StringComparison.InvariantCultureIgnoreCase))
{
id = "";
}
var result = File.ReadAllText(file);
Log.Info("Sending results for {0}", id ?? unknownName);
rabbitMqClient.SendResult(new MqMessage { Body = result, Id = id });
File.Delete(file);
}
var path = Directory.EnumerateFiles(this.plansDir, "*.json").FirstOrDefault();
if (path == null)
{ {
var message = rabbitMqClient.GetMessage(); var message = rabbitMqClient.GetMessage();
File.WriteAllText(filePath, message.Body); var id = message.Id;
if(string.IsNullOrEmpty(id))
{
id = unknownName;
}
path = Path.Combine(this.plansDir, string.Format("{0}.json", id));
File.WriteAllText(path, message.Body);
Log.Info("Received new execution plan {0}", id);
message.Ack(); message.Ack();
} }
var executor = new PlanExecutor(filePath); else
var result = executor.Execute(); {
if(stop) break; var id = Path.GetFileNameWithoutExtension(path);
rabbitMqClient.SendResult(result); Log.Info("Executing exising plan {0}", id);
File.Delete(filePath); }
var executor = new PlanExecutor(path);
executor.Execute();
File.Delete(path);
delayFactor = 1;
if (stop) break;
if (executor.RebootNeeded) if (executor.RebootNeeded)
{ {
doReboot = true; Reboot();
break;
} }
} }
catch (Exception exception) catch (Exception exception)
@ -59,16 +106,40 @@ namespace Mirantis.Keero.WindowsAgent
} }
} }
if (doReboot)
}
private void Reboot()
{
Log.Info("Going for reboot!!");
LogManager.Flush();
/*try
{ {
try System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
}
catch (Exception ex)
{
Log.ErrorException("Cannot execute shutdown.exe", ex);
}*/
try
{
PowerShell.Create().AddCommand("Restart-Computer").AddParameter("Force").Invoke();
}
catch (Exception exception)
{
Log.FatalException("Reboot exception", exception);
}
finally
{
Log.Info("Waiting for reboot");
for (var i = 0; i < 10 * 60 * 5 && !stop; i++)
{ {
System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0"); Thread.Sleep(100);
}
catch (Exception ex)
{
Log.ErrorException("Cannot execute shutdown.exe", ex);
} }
Log.Info("Done waiting for reboot");
} }
} }
@ -78,18 +149,18 @@ namespace Mirantis.Keero.WindowsAgent
if (stop) return; if (stop) return;
Log.WarnException("Exception in main loop", exception); Log.WarnException("Exception in main loop", exception);
var i = 0; var i = 0;
while (!stop && i < 10) while (!stop && i < 10 * (delayFactor * delayFactor))
{ {
Thread.Sleep(100); Thread.Sleep(100);
i++; i++;
} }
delayFactor = Math.Min(delayFactor + 1, 6);
} }
protected override void OnStop() protected override void OnStop()
{ {
stop = true; stop = true;
this.rabbitMqClient.Dispose(); this.rabbitMqClient.Dispose();
Console.WriteLine("Stop");
base.OnStop(); base.OnStop();
} }

View File

@ -5,12 +5,14 @@ using System.Linq;
using System.Net; using System.Net;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using NLog;
using RabbitMQ.Client; using RabbitMQ.Client;
namespace Mirantis.Keero.WindowsAgent namespace Mirantis.Keero.WindowsAgent
{ {
class RabbitMqClient : IDisposable class RabbitMqClient : IDisposable
{ {
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly ConnectionFactory connectionFactory; private static readonly ConnectionFactory connectionFactory;
private IConnection currentConnecton; private IConnection currentConnecton;
@ -43,21 +45,22 @@ namespace Mirantis.Keero.WindowsAgent
} }
var session = connection.CreateModel(); var session = connection.CreateModel();
session.BasicQos(0, 1, false); session.BasicQos(0, 1, false);
session.QueueDeclare(queueName, true, false, false, null); //session.QueueDeclare(queueName, true, false, false, null);
var consumer = new QueueingBasicConsumer(session); var consumer = new QueueingBasicConsumer(session);
var consumeTag = session.BasicConsume(queueName, false, consumer); var consumeTag = session.BasicConsume(queueName, false, consumer);
var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs)consumer.Queue.Dequeue(); var e = (RabbitMQ.Client.Events.BasicDeliverEventArgs) consumer.Queue.Dequeue();
Action ackFunc = delegate { Action ackFunc = delegate {
session.BasicAck(e.DeliveryTag, false); session.BasicAck(e.DeliveryTag, false);
session.BasicCancel(consumeTag); session.BasicCancel(consumeTag);
session.Close(); session.Close();
}; };
return new MqMessage(ackFunc) { return new MqMessage(ackFunc) {
Body = Encoding.UTF8.GetString(e.Body) Body = Encoding.UTF8.GetString(e.Body),
Id = e.BasicProperties.MessageId
}; };
} }
catch (Exception) catch (Exception exception)
{ {
Dispose(); Dispose();
@ -65,10 +68,11 @@ namespace Mirantis.Keero.WindowsAgent
} }
} }
public void SendResult(string text) public void SendResult(MqMessage message)
{ {
var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? ""; var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? "";
var resultQueue = ConfigurationManager.AppSettings["rabbitmq.resultQueue"] ?? "-execution-results"; var resultRoutingKey = ConfigurationManager.AppSettings["rabbitmq.resultRoutingKey"] ?? "-execution-results";
bool durable = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.durableMessages"] ?? "true");
try try
{ {
@ -78,18 +82,19 @@ namespace Mirantis.Keero.WindowsAgent
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection(); connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
} }
var session = connection.CreateModel(); var session = connection.CreateModel();
if (!string.IsNullOrEmpty(resultQueue)) /*if (!string.IsNullOrEmpty(resultQueue))
{ {
session.QueueDeclare(resultQueue, true, false, false, null); //session.QueueDeclare(resultQueue, true, false, false, null);
if (!string.IsNullOrEmpty(exchangeName)) if (!string.IsNullOrEmpty(exchangeName))
{ {
session.ExchangeBind(exchangeName, resultQueue, resultQueue); session.ExchangeBind(exchangeName, resultQueue, resultQueue);
} }
} }*/
var basicProperties = session.CreateBasicProperties(); var basicProperties = session.CreateBasicProperties();
basicProperties.SetPersistent(true); basicProperties.SetPersistent(durable);
basicProperties.MessageId = message.Id;
basicProperties.ContentType = "application/json"; basicProperties.ContentType = "application/json";
session.BasicPublish(exchangeName, resultQueue, basicProperties, Encoding.UTF8.GetBytes(text)); session.BasicPublish(exchangeName, resultRoutingKey, basicProperties, Encoding.UTF8.GetBytes(message.Body));
session.Close(); session.Close();
} }
catch (Exception) catch (Exception)

View File

@ -21,6 +21,7 @@
<DefineConstants>DEBUG;TRACE</DefineConstants> <DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport> <ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>

View File

@ -1,17 +1,17 @@
import datetime
import glob import glob
import json import json
import time import time
import sys import sys
import tornado.ioloop import tornado.ioloop
import rabbitmq import rabbitmq
from workflow import Workflow from workflow import Workflow
import cloud_formation import cloud_formation
import windows_agent import windows_agent
from commands.dispatcher import CommandDispatcher from commands.dispatcher import CommandDispatcher
from config import Config from config import Config
import reporting
config = Config(sys.argv[1] if len(sys.argv) > 1 else None) config = Config(sys.argv[1] if len(sys.argv) > 1 else None)
@ -21,16 +21,21 @@ rmqclient = rabbitmq.RabbitMqClient(
password=config.get_setting('rabbitmq', 'password', 'guest'), password=config.get_setting('rabbitmq', 'password', 'guest'),
host=config.get_setting('rabbitmq', 'host', 'localhost')) host=config.get_setting('rabbitmq', 'host', 'localhost'))
def schedule(callback, *args, **kwargs): def schedule(callback, *args, **kwargs):
tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 0.1, tornado.ioloop.IOLoop.instance().add_timeout(time.time() + 0.1,
lambda args=args, kwargs = kwargs: callback(*args, **kwargs)) lambda args=args, kwargs=kwargs: callback(*args, **kwargs))
def task_received(task, message_id):
print 'Starting at', datetime.datetime.now()
reporter = reporting.Reporter(rmqclient, message_id, task['id'])
def task_received(task):
command_dispatcher = CommandDispatcher(task['name'], rmqclient) command_dispatcher = CommandDispatcher(task['name'], rmqclient)
workflows = [] workflows = []
for path in glob.glob("data/workflows/*.xml"): for path in glob.glob("data/workflows/*.xml"):
print "loading", path print "loading", path
workflow = Workflow(path, task, command_dispatcher, config) workflow = Workflow(path, task, command_dispatcher, config, reporter)
workflows.append(workflow) workflows.append(workflow)
def loop(callback): def loop(callback):
@ -41,14 +46,14 @@ def task_received(task):
def shutdown(): def shutdown():
command_dispatcher.close() command_dispatcher.close()
rmqclient.send('task-results', json.dumps(task)) rmqclient.send('task-results', json.dumps(task), message_id=message_id)
print "Done!!!!!!!!!!" print 'Finished at', datetime.datetime.now()
loop(shutdown) loop(shutdown)
def message_received(body): def message_received(body, message_id, **kwargs):
task_received(json.loads(body)) task_received(json.loads(body), message_id)
def start(): def start():

View File

@ -1,10 +1,39 @@
import base64
import xml_code_engine import xml_code_engine
def update_cf_stack(engine, context, body, template, mappings, arguments, **kwargs):
def update_cf_stack(engine, context, body, template,
mappings, arguments, **kwargs):
command_dispatcher = context['/commandDispatcher'] command_dispatcher = context['/commandDispatcher']
print "update-cf", template
callback = lambda result: engine.evaluate_content(body.find('success'), context) callback = lambda result: engine.evaluate_content(
command_dispatcher.execute(name='cf', template=template, mappings=mappings, arguments=arguments, callback=callback) body.find('success'), context)
command_dispatcher.execute(
name='cf', template=template, mappings=mappings,
arguments=arguments, callback=callback)
xml_code_engine.XmlCodeEngine.register_function(update_cf_stack, "update-cf-stack") def prepare_user_data(context, template='Default', **kwargs):
config = context['/config']
with open('data/init.ps1') as init_script_file:
with open('data/templates/agent-config/%s.template'
% template) as template_file:
init_script = init_script_file.read()
template_data = template_file.read().replace(
'%RABBITMQ_HOST%',
config.get_setting('rabbitmq', 'host') or 'localhost')
return init_script.replace(
'%WINDOWS_AGENT_CONFIG_BASE64%',
base64.b64encode(template_data))
xml_code_engine.XmlCodeEngine.register_function(
update_cf_stack, "update-cf-stack")
xml_code_engine.XmlCodeEngine.register_function(
prepare_user_data, "prepare_user_data")

View File

@ -6,6 +6,7 @@ import conductor.helpers
from command import CommandBase from command import CommandBase
from subprocess import call from subprocess import call
class HeatExecutor(CommandBase): class HeatExecutor(CommandBase):
def __init__(self, stack): def __init__(self, stack):
self._pending_list = [] self._pending_list = []
@ -15,7 +16,8 @@ class HeatExecutor(CommandBase):
with open('data/templates/cf/%s.template' % template) as template_file: with open('data/templates/cf/%s.template' % template) as template_file:
template_data = template_file.read() template_data = template_file.read()
template_data = conductor.helpers.transform_json(json.loads(template_data), mappings) template_data = conductor.helpers.transform_json(
json.loads(template_data), mappings)
self._pending_list.append({ self._pending_list.append({
'template': template_data, 'template': template_data,
@ -27,32 +29,37 @@ class HeatExecutor(CommandBase):
return len(self._pending_list) > 0 return len(self._pending_list) > 0
def execute_pending(self, callback): def execute_pending(self, callback):
if not self._pending_list: if not self.has_pending_commands():
return False return False
template = {} template = {}
arguments = {} arguments = {}
for t in self._pending_list: for t in self._pending_list:
template = conductor.helpers.merge_dicts(template, t['template'], max_levels=2) template = conductor.helpers.merge_dicts(
arguments = conductor.helpers.merge_dicts(arguments, t['arguments'], max_levels=1) template, t['template'], max_levels=2)
arguments = conductor.helpers.merge_dicts(
arguments, t['arguments'], max_levels=1)
print 'Executing heat template', json.dumps(template), 'with arguments', arguments, 'on stack', self._stack print 'Executing heat template', json.dumps(template), \
'with arguments', arguments, 'on stack', self._stack
if not os.path.exists("tmp"): if not os.path.exists("tmp"):
os.mkdir("tmp") os.mkdir("tmp")
file_name = "tmp/"+str(uuid.uuid4()) file_name = "tmp/" + str(uuid.uuid4())
print "Saving template to", file_name print "Saving template to", file_name
with open(file_name, "w") as f: with open(file_name, "w") as f:
f.write(json.dumps(template)) f.write(json.dumps(template))
arguments_str = ";".join(['%s=%s' % (key, value) for (key, value) in arguments.items()]) arguments_str = ';'.join(['%s=%s' % (key, value)
for (key, value) in arguments.items()])
call([ call([
"./heat_run","stack-create", "./heat_run", "stack-create",
"-f" + file_name, "-f" + file_name,
"-P" + arguments_str, "-P" + arguments_str,
self._stack self._stack
]) ])
callbacks = [] callbacks = []
for t in self._pending_list: for t in self._pending_list:
if t['callback']: if t['callback']:
@ -66,10 +73,3 @@ class HeatExecutor(CommandBase):
callback() callback()
return True return True

View File

@ -1,4 +1,3 @@
class CommandBase(object): class CommandBase(object):
def execute(self, **kwargs): def execute(self, **kwargs):
pass pass

View File

@ -2,11 +2,13 @@ import command
import cloud_formation import cloud_formation
import windows_agent import windows_agent
class CommandDispatcher(command.CommandBase): class CommandDispatcher(command.CommandBase):
def __init__(self, environment_name, rmqclient): def __init__(self, environment_name, rmqclient):
self._command_map = { self._command_map = {
'cf': cloud_formation.HeatExecutor(environment_name), 'cf': cloud_formation.HeatExecutor(environment_name),
'agent': windows_agent.WindowsAgentExecutor(environment_name, rmqclient) 'agent': windows_agent.WindowsAgentExecutor(
environment_name, rmqclient)
} }
def execute(self, name, **kwargs): def execute(self, name, **kwargs):
@ -28,7 +30,6 @@ class CommandDispatcher(command.CommandBase):
count[0] -= 1 count[0] -= 1
result -= 1 result -= 1
return result > 0 return result > 0

View File

@ -1,34 +1,42 @@
import json import json
import uuid
import conductor.helpers import conductor.helpers
from command import CommandBase from command import CommandBase
class WindowsAgentExecutor(CommandBase): class WindowsAgentExecutor(CommandBase):
def __init__(self, stack, rmqclient): def __init__(self, stack, rmqclient):
self._pending_list = []
self._stack = stack self._stack = stack
self._rmqclient = rmqclient self._rmqclient = rmqclient
self._callback = None self._callback = None
self._pending_list = []
self._current_pending_list = []
rmqclient.subscribe('-execution-results', self._on_message) rmqclient.subscribe('-execution-results', self._on_message)
print "--------------------"
def execute(self, template, mappings, host, callback): def execute(self, template, mappings, host, callback):
with open('data/templates/agent/%s.template' % template) as template_file: with open('data/templates/agent/%s.template' %
template) as template_file:
template_data = template_file.read() template_data = template_file.read()
template_data = json.dumps(conductor.helpers.transform_json(json.loads(template_data), mappings)) template_data = json.dumps(conductor.helpers.transform_json(
json.loads(template_data), mappings))
self._pending_list.append({ self._pending_list.append({
'id': str(uuid.uuid4()).lower(),
'template': template_data, 'template': template_data,
'host': ('%s-%s' % (self._stack, host)).lower().replace(' ', '-'), 'host': ('%s-%s' % (self._stack, host)).lower().replace(' ', '-'),
'callback': callback 'callback': callback
}) })
def _on_message(self, body): def _on_message(self, body, message_id, **kwargs):
if self._pending_list: msg_id = message_id.lower()
item = self._pending_list.pop() item, index = conductor.helpers.find(lambda t: t['id'] == msg_id,
self._current_pending_list)
if item:
self._current_pending_list.pop(index)
item['callback'](json.loads(body)) item['callback'](json.loads(body))
if self._callback and not self._pending_list: if self._callback and not self._current_pending_list:
cb = self._callback cb = self._callback
self._callback = None self._callback = None
cb() cb()
@ -37,19 +45,19 @@ class WindowsAgentExecutor(CommandBase):
return len(self._pending_list) > 0 return len(self._pending_list) > 0
def execute_pending(self, callback): def execute_pending(self, callback):
if not self._pending_list: if not self.has_pending_commands():
return False return False
self._current_pending_list = self._pending_list
self._pending_list = []
self._callback = callback self._callback = callback
for t in self._pending_list: for rec in self._current_pending_list:
self._rmqclient.send(queue=t['host'], data=t['template']) self._rmqclient.send(
print 'Sending RMQ message %s to %s' % (t['template'], t['host']) queue=rec['host'], data=rec['template'], message_id=rec['id'])
print 'Sending RMQ message %s to %s' % (
callbacks = [] rec['template'], rec['host'])
for t in self._pending_list:
if t['callback']:
callbacks.append(t['callback'])
return True return True

View File

@ -1,5 +1,6 @@
from ConfigParser import SafeConfigParser from ConfigParser import SafeConfigParser
class Config(object): class Config(object):
CONFIG_PATH = './etc/app.config' CONFIG_PATH = './etc/app.config'
@ -14,4 +15,5 @@ class Config(object):
def __getitem__(self, item): def __getitem__(self, item):
parts = item.rsplit('.', 1) parts = item.rsplit('.', 1)
return self.get_setting(parts[0] if len(parts) == 2 else 'DEFAULT', parts[-1]) return self.get_setting(
parts[0] if len(parts) == 2 else 'DEFAULT', parts[-1])

View File

@ -5,7 +5,8 @@ class Context(object):
def _get_data(self): def _get_data(self):
if self._data is None: if self._data is None:
self._data = {} if self._parent is None else self._parent._get_data().copy() self._data = {} if self._parent is None \
else self._parent._get_data().copy()
return self._data return self._data
def __getitem__(self, item): def __getitem__(self, item):

View File

@ -1,16 +1,15 @@
import types import types
def transform_json(json, mappings): def transform_json(json, mappings):
if isinstance(json, types.ListType): if isinstance(json, types.ListType):
result=[] return [transform_json(t, mappings) for t in json]
for t in json:
result.append(transform_json(t, mappings))
return result
if isinstance(json, types.DictionaryType): if isinstance(json, types.DictionaryType):
result = {} result = {}
for key, value in json.items(): for key, value in json.items():
result[transform_json(key, mappings)] = transform_json(value, mappings) result[transform_json(key, mappings)] = \
transform_json(value, mappings)
return result return result
if isinstance(json, types.StringTypes) and json.startswith('$'): if isinstance(json, types.StringTypes) and json.startswith('$'):
@ -20,17 +19,30 @@ def transform_json(json, mappings):
return json return json
def merge_dicts(dict1, dict2, max_levels=0): def merge_dicts(dict1, dict2, max_levels=0):
result = {} result = {}
for key, value in dict1.items(): for key, value in dict1.items():
result[key] = value result[key] = value
if key in dict2: if key in dict2:
other_value = dict2[key] other_value = dict2[key]
if max_levels == 1 or not isinstance(other_value, types.DictionaryType): if max_levels == 1 or not isinstance(
other_value, types.DictionaryType):
result[key] = other_value result[key] = other_value
else: else:
result[key] = merge_dicts(value, other_value, 0 if max_levels == 0 else max_levels-1) result[key] = merge_dicts(
value, other_value,
0 if max_levels == 0 else max_levels - 1)
for key, value in dict2.items(): for key, value in dict2.items():
if key not in result: if key not in result:
result[key] = value result[key] = value
return result return result
def find(f, seq):
"""Return first item in sequence where f(item) == True."""
index = 0
for item in seq:
if f(item):
return item, index
index += 1
return None, -1

View File

@ -5,22 +5,24 @@ import time
try: try:
import tornado.ioloop import tornado.ioloop
IOLoop = tornado.ioloop.IOLoop IOLoop = tornado.ioloop.IOLoop
except ImportError: except ImportError:
IOLoop = None IOLoop = None
class RabbitMqClient(object): class RabbitMqClient(object):
def __init__(self, host='localhost', login='guest', password='guest', virtual_host='/'): def __init__(self, host='localhost', login='guest',
password='guest', virtual_host='/'):
credentials = pika.PlainCredentials(login, password) credentials = pika.PlainCredentials(login, password)
self._connection_parameters = pika.ConnectionParameters( self._connection_parameters = pika.ConnectionParameters(
credentials = credentials, host = host, virtual_host = virtual_host) credentials=credentials, host=host, virtual_host=virtual_host)
self._subscriptions = {} self._subscriptions = {}
def _create_connection(self): def _create_connection(self):
self.connection = TornadoConnection( self.connection = TornadoConnection(
parameters = self._connection_parameters, parameters=self._connection_parameters,
on_open_callback = self._on_connected) on_open_callback=self._on_connected)
def _on_connected(self, connection): def _on_connected(self, connection):
self._channel = connection.channel(self._on_channel_open) self._channel = connection.channel(self._on_channel_open)
@ -32,31 +34,39 @@ class RabbitMqClient(object):
def _on_queue_declared(self, frame, queue, callback, ctag): def _on_queue_declared(self, frame, queue, callback, ctag):
def invoke_callback(ch, method_frame, header_frame, body): def invoke_callback(ch, method_frame, header_frame, body):
callback(body) callback(body=body,
message_id=header_frame.message_id or "")
self._channel.basic_consume(invoke_callback, queue=queue, no_ack=True, consumer_tag=ctag) self._channel.basic_consume(invoke_callback, queue=queue,
no_ack=True, consumer_tag=ctag)
def subscribe(self, queue, callback): def subscribe(self, queue, callback):
ctag = str(uuid.uuid4()) ctag = str(uuid.uuid4())
self._subscriptions[queue] = ctag self._subscriptions[queue] = ctag
self._channel.queue_declare(queue=queue, durable=True, self._channel.queue_declare(
callback= lambda frame, ctag=ctag : self._on_queue_declared(frame, queue, callback, ctag)) queue=queue, durable=True,
callback=lambda frame, ctag=ctag: self._on_queue_declared(
frame, queue, callback, ctag))
def unsubscribe(self, queue): def unsubscribe(self, queue):
self._channel.basic_cancel(consumer_tag=self._subscriptions[queue]) self._channel.basic_cancel(consumer_tag=self._subscriptions[queue])
del self._subscriptions[queue] del self._subscriptions[queue]
def start(self, callback=None): def start(self, callback=None):
if IOLoop is None: raise ImportError("Tornado not installed") if IOLoop is None: raise ImportError("Tornado not installed")
self._started_callback = callback self._started_callback = callback
ioloop = IOLoop.instance() ioloop = IOLoop.instance()
self.timeout_id = ioloop.add_timeout(time.time() + 0.1, self._create_connection) self.timeout_id = ioloop.add_timeout(time.time() + 0.1,
self._create_connection)
def send(self, queue, data, exchange=""): def send(self, queue, data, exchange="", message_id=""):
self._channel.queue_declare(queue=queue, durable=True, properties = pika.BasicProperties(message_id=message_id)
callback = lambda frame: self._channel.basic_publish(exchange=exchange, routing_key=queue, body=data)) self._channel.queue_declare(
queue=queue, durable=True,
callback=lambda frame: self._channel.basic_publish(
exchange=exchange, routing_key=queue,
body=data, properties=properties))

View File

@ -0,0 +1,29 @@
import xml_code_engine
import json
class Reporter(object):
def __init__(self, rmqclient, task_id, environment_id):
self._rmqclient = rmqclient
self._task_id = task_id
self._environment_id = environment_id
def _report_func(self, id, entity, text, **kwargs):
msg = json.dumps({
'id': id,
'entity': entity,
'text': text,
'environment_id': self._environment_id
})
self._rmqclient.send(
queue='task-reports', data=msg, message_id=self._task_id)
def _report_func(context, id, entity, text, **kwargs):
reporter = context['/reporter']
return reporter._report_func(id, entity, text, **kwargs)
xml_code_engine.XmlCodeEngine.register_function(_report_func, "report")

View File

@ -1,13 +1,25 @@
import xml_code_engine import xml_code_engine
def send_command(engine, context, body, template, mappings, host, **kwargs):
def send_command(engine, context, body, template, host, mappings=None,
result=None, **kwargs):
if not mappings: mappings = {}
command_dispatcher = context['/commandDispatcher'] command_dispatcher = context['/commandDispatcher']
def callback(result): def callback(result_value):
print "Received ", result print "Received result for %s: %s. Body is %s" % (template, result_value, body)
engine.evaluate_content(body.find('success'), context) if result is not None:
context[result] = result_value['Result']
command_dispatcher.execute(name='agent', template=template, mappings=mappings, host=host, callback=callback) 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,
callback=callback)
xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command") xml_code_engine.XmlCodeEngine.register_function(send_command, "send-command")

View File

@ -6,13 +6,14 @@ import xml_code_engine
import function_context import function_context
class Workflow(object): class Workflow(object):
def __init__(self, filename, data, command_dispatcher, config): def __init__(self, filename, data, command_dispatcher, config, reporter):
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:
self._engine.load(xml) self._engine.load(xml)
self._command_dispatcher = command_dispatcher self._command_dispatcher = command_dispatcher
self._config = config self._config = config
self._reporter = reporter
def execute(self): def execute(self):
while True: while True:
@ -20,11 +21,16 @@ class Workflow(object):
context['/dataSource'] = self._data context['/dataSource'] = self._data
context['/commandDispatcher'] = self._command_dispatcher context['/commandDispatcher'] = self._command_dispatcher
context['/config'] = self._config context['/config'] = self._config
context['/reporter'] = self._reporter
if not self._engine.execute(context): if not self._engine.execute(context):
break break
@staticmethod @staticmethod
def _get_path(obj, path, create_non_existing=False): def _get_path(obj, path, create_non_existing=False):
# result = jsonpath.jsonpath(obj, '.'.join(path))
# if not result or len(result) < 1:
# return None
# return result[0]
current = obj current = obj
for part in path: for part in path:
if isinstance(current, types.ListType): if isinstance(current, types.ListType):
@ -52,65 +58,87 @@ class Workflow(object):
raise ValueError() raise ValueError()
@staticmethod @staticmethod
def _select_func(path, context, **kwargs): def _get_relative_position(path, context):
position = context['__dataSource_currentPosition'] or []
index = 0
for c in path:
if c == ':':
if len(position) > 0:
position = position[:-1]
elif c == '/':
position = []
else:
break
index += 1
return position, path[index:]
@staticmethod
def _correct_position(path, context):
position, suffix = Workflow._get_relative_position(path, context)
if not suffix:
return position
else:
return position + suffix.split('.')
@staticmethod
def _select_func(context, path='', source=None, **kwargs):
if path.startswith('##'): if path.startswith('##'):
config = context['/config'] config = context['/config']
return config[path[2:]] return config[path[2:]]
elif path.startswith('#'): elif path.startswith('#'):
return context[path[1:]] return context[path[1:]]
position = context['dataSource_currentPosition'] or [] if source is not None:
data = context['dataSource'] return Workflow._get_path(
context[source], path.split('.'))
index = 0 else:
for c in path: return Workflow._get_path(
if c == ':': context['/dataSource'],
if len(position) > 0: Workflow._correct_position(path, context))
position = position[:-1]
elif c == '/':
position = []
else:
break
index += 1
return Workflow._get_path(data, position + path[index:].split('.'))
@staticmethod @staticmethod
def _set_func(path, context, body, engine, **kwargs): def _set_func(path, context, body, engine, target=None, **kwargs):
body_data = engine.evaluate_content(body, context) body_data = engine.evaluate_content(body, context)
if path[0] == '#':
context[path[1:]] = body_data
return
position = context['dataSource_currentPosition'] or [] if path.startswith('##'):
data = context['dataSource'] raise RuntimeError('Cannot modify config from XML-code')
elif path.startswith('#'):
context[':' + path[1:]] = body_data
return
index = 0 if target:
for c in path: data = context[target]
if c == ':': position = path.split('.')
if len(position) > 0: if Workflow._get_path(data, position) != body_data:
position = position[:-1] Workflow._set_path(data, position, body_data)
elif c == '/': context['/hasSideEffects'] = True
position = [] else:
else: data = context['/dataSource']
break new_position = Workflow._correct_position(path, context)
if Workflow._get_path(data, new_position) != body_data:
index += 1 Workflow._set_path(data, new_position, body_data)
context['/hasSideEffects'] = True
new_position = position + path[index:].split('.')
if Workflow._get_path(data, new_position) != body_data:
Workflow._set_path(data, new_position, body_data)
context['/hasSideEffects'] = True
@staticmethod @staticmethod
def _rule_func(match, context, body, engine, limit = 0, **kwargs): def _rule_func(match, context, body, engine, limit=0, name=None, **kwargs):
position = context['dataSource_currentPosition'] or [] position = context['__dataSource_currentPosition'] or []
data = context['dataSource_currentObj']
if data is None: if name == 'marker':
data = context['dataSource'] print "!"
match = re.sub(r'@\.([\w.]+)', r"Workflow._get_path(@, '\1'.split('.'))", match) # 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.]+)',
r"Workflow._get_path(@, '\1'.split('.'))", match)
selected = jsonpath.jsonpath(data, match, 'IPATH') or [] selected = jsonpath.jsonpath(data, match, 'IPATH') or []
index = 0 index = 0
@ -119,8 +147,9 @@ class Workflow(object):
break break
index += 1 index += 1
new_position = position + found_match new_position = position + found_match
context['dataSource_currentPosition'] = new_position context['__dataSource_currentPosition'] = new_position
context['dataSource_currentObj'] = Workflow._get_path(context['dataSource'], new_position) context['__dataSource_currentObj'] = Workflow._get_path(
context['/dataSource'], new_position)
for element in body: for element in body:
engine.evaluate(element, context) engine.evaluate(element, context)
if element.tag == 'rule' and context['/hasSideEffects']: if element.tag == 'rule' and context['/hasSideEffects']:
@ -136,7 +165,14 @@ class Workflow(object):
return False return False
xml_code_engine.XmlCodeEngine.register_function(Workflow._rule_func, 'rule') xml_code_engine.XmlCodeEngine.register_function(
xml_code_engine.XmlCodeEngine.register_function(Workflow._workflow_func, 'workflow') Workflow._rule_func, 'rule')
xml_code_engine.XmlCodeEngine.register_function(Workflow._set_func, 'set')
xml_code_engine.XmlCodeEngine.register_function(Workflow._select_func, 'select') xml_code_engine.XmlCodeEngine.register_function(
Workflow._workflow_func, 'workflow')
xml_code_engine.XmlCodeEngine.register_function(
Workflow._set_func, 'set')
xml_code_engine.XmlCodeEngine.register_function(
Workflow._select_func, 'select')

View File

@ -1,7 +1,10 @@
#from lxml import etree #from lxml import etree
import xml.etree.ElementTree as etree import xml.etree.ElementTree as etree
import types
import function_context import function_context
class XmlCodeEngine(object): class XmlCodeEngine(object):
_functionMap = {} _functionMap = {}
@ -24,13 +27,14 @@ class XmlCodeEngine(object):
definition = self._functionMap[name] definition = self._functionMap[name]
context = function_context.Context(parent_context) context = function_context.Context(parent_context)
args = { 'engine': self, 'body': element, 'context': context } args = {'engine': self, 'body': element, 'context': context}
for key, value in element.items(): for key, value in element.items():
args[key] = value args[key] = value
for parameter in element.findall('parameter'): for parameter in element.findall('parameter'):
args[parameter.get('name')] = self.evaluate_content(parameter, context) args[parameter.get('name')] = self.evaluate_content(
parameter, context)
return definition(**args) return definition(**args)
@ -44,21 +48,22 @@ class XmlCodeEngine(object):
if sub_element.tag == 'parameter': if sub_element.tag == 'parameter':
continue continue
do_strip = True do_strip = True
parts.append(self._execute_function(sub_element.tag, sub_element, context)) parts.append(self._execute_function(
sub_element.tag, sub_element, context))
parts.append(sub_element.tail or '') parts.append(sub_element.tail or '')
result = [] result = []
for t in parts: for t in parts:
if not isinstance(t, (str, unicode)): if not isinstance(t, types.StringTypes):
result.append(t) result.append(t)
return_value = result
if len(result) == 0: if len(result) == 0:
return_value = ''.join(parts) return_value = ''.join(parts)
if do_strip: return_value = return_value.strip() if do_strip: return_value = return_value.strip()
elif len(result) == 1: elif len(result) == 1:
return_value = result[0] return_value = result[0]
else:
return_value = result
return return_value return return_value
@ -75,27 +80,34 @@ def _dict_func(engine, body, context, **kwargs):
result[key] = value result[key] = value
return result return result
def _array_func(engine, body, context, **kwargs): def _array_func(engine, body, context, **kwargs):
result = [] result = []
for item in body: for item in body:
result.append(engine.evaluate(item, context)) result.append(engine.evaluate(item, context))
return result return result
def _text_func(engine, body, context, **kwargs): def _text_func(engine, body, context, **kwargs):
return str(engine.evaluate_content(body, context)) return str(engine.evaluate_content(body, context))
def _int_func(engine, body, context, **kwargs): def _int_func(engine, body, context, **kwargs):
return int(engine.evaluate_content(body, context)) return int(engine.evaluate_content(body, context))
def _function_func(engine, body, context, **kwargs): def _function_func(engine, body, context, **kwargs):
return lambda: engine.evaluate_content(body, context) return lambda: engine.evaluate_content(body, context)
def _null_func(**kwargs): def _null_func(**kwargs):
return None return None
def _true_func(**kwargs): def _true_func(**kwargs):
return True return True
def _false_func(**kwargs): def _false_func(**kwargs):
return False return False
@ -115,27 +127,8 @@ def xprint(context, body, **kwargs):
for arg in kwargs: for arg in kwargs:
print "%s = %s" % (arg, kwargs[arg]) print "%s = %s" % (arg, kwargs[arg])
print 'context = ', context print 'context = ', context
print 'body = %s (%s)' %(body, body.text) print 'body = %s (%s)' % (body, body.text)
print "------------------------- end -------------------------" print "------------------------- end -------------------------"
XmlCodeEngine.register_function(xprint, "print") XmlCodeEngine.register_function(xprint, "print")
# parser = XmlCodeEngine()
# file = open('test2.xml')
# parser.load(file)
#
#
#
#
# context = functioncontext.Context()
# context['test'] = 'xxx'
#
# parser.execute(context)
#
# print etree.xpath('/')
# root = parser._document.getroot()
# print root.items()
# print root.text.lstrip() + "|"
# for x in root:
# print x, type(x), x.tail

14
conductor/data/init.ps1 Normal file
View File

@ -0,0 +1,14 @@
#ps1
$WindowsAgentConfigBase64 = '%WINDOWS_AGENT_CONFIG_BASE64%'
$WindowsAgentConfigFile = "C:\Keero\Agent\WindowsAgent.exe.config"
Import-Module CoreFunctions
Stop-Service "Keero Agent"
Backup-File $WindowsAgentConfigFile
Remove-Item $WindowsAgentConfigFile -Force
ConvertFrom-Base64String -Base64String $WindowsAgentConfigBase64 -Path $WindowsAgentConfigFile
Exec sc.exe 'config','"Keero Agent"','start=','delayed-auto'
Start-Service 'Keero Agent'
Write-Log 'All done!'

View File

@ -0,0 +1,30 @@
<?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}: &lt;${logger:shortName=true}&gt; ${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.user" value="keero"/>
<add key="rabbitmq.password" value="keero"/>
<add key="rabbitmq.vhost" value="keero"/>
<add key="rabbitmq.resultExchange" value=""/>
<add key="rabbitmq.resultRoutingKey" value="-execution-results"/>
<add key="rabbitmq.durableMessages" value="true"/>
</appSettings>
</configuration>

View File

@ -0,0 +1,12 @@
{
"Scripts": [
"ZnVuY3Rpb24gR2V0LURuc0xpc3RlbmluZ0lwQWRkcmVzc2VzIHsNCiAgICAoR2V0LUROU1NlcnZlciAtQ29tcHV0ZXJOYW1lIGxvY2FsaG9zdCkuU2VydmVyU2V0dGluZy5MaXN0ZW5pbmdJcEFkZHJlc3MNCn0NCg=="
],
"Commands": [
{
"Name": "Get-DnsListeningIpAddress",
"Arguments": {}
}
],
"RebootOnCompletion": 0
}

View File

@ -12,8 +12,8 @@
{ {
"Name": "Install-RolePrimaryDomainController", "Name": "Install-RolePrimaryDomainController",
"Arguments": { "Arguments": {
"DomainName": "$dc_name", "DomainName": "$domain",
"SafeModePassword": "$recovery_password" "SafeModePassword": "$recoveryPassword"
} }
} }
], ],

View File

@ -0,0 +1,23 @@
{
"Scripts": [
"RnVuY3Rpb24gSW5zdGFsbC1Sb2xlU2Vjb25kYXJ5RG9tYWluQ29udHJvbGxlcg0Kew0KPCMNCi5TWU5PUFNJUw0KSW5zdGFsbCBhZGRpdGlvbmFsIChzZWNvbmRhcnkpIGRvbWFpbiBjb250cm9sbGVyLg0KDQojPg0KCXBhcmFtDQoJKA0KCQlbU3RyaW5nXQ0KCQkjIERvbWFpbiBuYW1lIHRvIGpvaW4gdG8uDQoJCSREb21haW5OYW1lLA0KCQkNCgkJW1N0cmluZ10NCgkJIyBEb21haW4gdXNlciB3aG8gaXMgYWxsb3dlZCB0byBqb2luIGNvbXB1dGVyIHRvIGRvbWFpbi4NCgkJJFVzZXJOYW1lLA0KCQkNCgkJW1N0cmluZ10NCgkJIyBVc2VyJ3MgcGFzc3dvcmQuDQoJCSRQYXNzd29yZCwNCgkJDQoJCVtTdHJpbmddDQoJCSMgRG9tYWluIGNvbnRyb2xsZXIgcmVjb3ZlcnkgbW9kZSBwYXNzd29yZC4NCgkJJFNhZmVNb2RlUGFzc3dvcmQNCgkpDQoNCgl0cmFwIHsgU3RvcC1FeGVjdXRpb24gJF8gfQ0KCQ0KCSRDcmVkZW50aWFsID0gTmV3LUNyZWRlbnRpYWwgLVVzZXJOYW1lICIkRG9tYWluTmFtZVwkVXNlck5hbWUiIC1QYXNzd29yZCAkUGFzc3dvcmQNCgkJDQoJIyBBZGQgcmVxdWlyZWQgd2luZG93cyBmZWF0dXJlcw0KCUFkZC1XaW5kb3dzRmVhdHVyZVdyYXBwZXIgYA0KCQktTmFtZSAiRE5TIiwiQUQtRG9tYWluLVNlcnZpY2VzIiwiUlNBVC1ERlMtTWdtdC1Db24iIGANCgkJLUluY2x1ZGVNYW5hZ2VtZW50VG9vbHMgYA0KICAgICAgICAgICAgICAgIC1Ob3RpZnlSZXN0YXJ0DQoJCQ0KCQ0KICAgICAgICBXcml0ZS1Mb2cgIkFkZGluZyBzZWNvbmRhcnkgZG9tYWluIGNvbnRyb2xsZXIgLi4uIg0KICAgIA0KCSRTTUFQID0gQ29udmVydFRvLVNlY3VyZVN0cmluZyAtU3RyaW5nICRTYWZlTW9kZVBhc3N3b3JkIC1Bc1BsYWluVGV4dCAtRm9yY2UNCg0KCUluc3RhbGwtQUREU0RvbWFpbkNvbnRyb2xsZXIgYA0KCQktRG9tYWluTmFtZSAkRG9tYWluTmFtZSBgDQoJCS1TYWZlTW9kZUFkbWluaXN0cmF0b3JQYXNzd29yZCAkU01BUCBgDQoJCS1DcmVkZW50aWFsICRDcmVkZW50aWFsIGANCgkJLU5vUmVib290T25Db21wbGV0aW9uIGANCgkJLUZvcmNlIGANCgkJLUVycm9yQWN0aW9uIFN0b3AgfCBPdXQtTnVsbA0KDQoJV3JpdGUtTG9nICJXYWl0aW5nIGZvciByZXN0YXJ0IC4uLiINCiMJU3RvcC1FeGVjdXRpb24gLUV4aXRDb2RlIDMwMTAgLUV4aXRTdHJpbmcgIkNvbXB1dGVyIG11c3QgYmUgcmVzdGFydGVkIHRvIGZpbmlzaCBkb21haW4gY29udHJvbGxlciBwcm9tb3Rpb24uIg0KIwlXcml0ZS1Mb2cgIlJlc3RhcnRpbmcgY29tcHV0ZXIgLi4uIg0KIwlSZXN0YXJ0LUNvbXB1dGVyIC1Gb3JjZQ0KfQ0K"
],
"Commands": [
{
"Name": "Import-Module",
"Arguments": {
"Name": "CoreFunctions"
}
},
{
"Name": "Install-RoleSecondaryDomainController",
"Arguments": {
"DomainName": "$domain",
"UserName": "Administrator",
"Password": "$domainPassword",
"SafeModePassword": "$recoveryPassword"
}
}
],
"RebootOnCompletion": 1
}

View File

@ -0,0 +1,12 @@
{
"Scripts": [
"ZnVuY3Rpb24gSW5zdGFsbC1XZWJTZXJ2ZXIgew0KICAgIEltcG9ydC1Nb2R1bGUgU2VydmVyTWFuYWdlcg0KICAgIEluc3RhbGwtV2luZG93c0ZlYXR1cmUgV2ViLVNlcnZlciAtSW5jbHVkZU1hbmFnZW1lbnRUb29scw0KfQ0K"
],
"Commands": [
{
"Name": "Install-WebServer",
"Arguments": {}
}
],
"RebootOnCompletion": 0
}

View File

@ -0,0 +1,27 @@
{
"Scripts": [],
"Commands": [
{
"Name": "Import-Module",
"Arguments": {
"Name": "CoreFunctions"
}
},
{
"Name": "Set-NetworkAdapterConfiguration",
"Arguments": {
"FirstAvailable": true,
"DNSServer": "$dnsIp"
}
},
{
"Name": "Join-Domain",
"Arguments": {
"DomainName": "$domain",
"Username": "Administrator",
"Password": "$domainPassword"
}
}
],
"RebootOnCompletion": 1
}

View File

@ -13,7 +13,7 @@
"Name": "Set-LocalUserPassword", "Name": "Set-LocalUserPassword",
"Arguments": { "Arguments": {
"UserName": "Administrator", "UserName": "Administrator",
"Password": "$adm_password", "Password": "$adminPassword",
"Force": true "Force": true
} }
} }

View File

@ -51,7 +51,8 @@
"Properties": { "Properties": {
"InstanceType" : { "Ref" : "InstanceType" }, "InstanceType" : { "Ref" : "InstanceType" },
"ImageId" : { "Ref" : "ImageName" }, "ImageId" : { "Ref" : "ImageName" },
"KeyName" : { "Ref" : "KeyName" } "KeyName" : { "Ref" : "KeyName" },
"UserData": "$userData"
} }
} }
}, },

View File

@ -0,0 +1,199 @@
<workflow>
<rule match="$.services.activeDirectories[?(@.domain)].units[?(not @.isMaster)]">
<set path="domain">
<select path="::domain"/>
</set>
</rule>
<rule match="$.services.activeDirectories[*].units[?(@.state.instanceName is None)]">
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Creating instance <select path="name"/></parameter>
</report>
<update-cf-stack template="Windows">
<parameter name="mappings">
<map>
<mapping name="instanceName">
<select path="name"/>
</mapping>
<mapping name="userData">
<prepare_user_data/>
</mapping>
</map>
</parameter>
<parameter name="arguments">
<map>
<argument name="KeyName">keero-linux-keys</argument>
<argument name="InstanceType">m1.medium</argument>
<argument name="ImageName">ws-2012-full-agent</argument>
</map>
</parameter>
<success>
<set path="state.instanceName"><select path="name"/></set>
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Instance <select path="name"/> created</parameter>
</report>
</success>
</update-cf-stack>
</rule>
<rule match="$.services.activeDirectories[*].units[?(@.state.instanceName and @.adminPassword and @.adminPassword != @.state.adminPassword)]">
<send-command template="SetPassword">
<parameter name="host">
<select path="name"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="adminPassword">
<select path="adminPassword"/>
</mapping>
</map>
</parameter>
<success>
<set path="state.adminPassword">
<select path="adminPassword"/>
</set>
</success>
</send-command>
</rule>
<rule match="$.services.activeDirectories[?(@.adminPassword and @.adminPassword != @.state.domainAdminPassword)].units[?(@.state.instanceName and @.isMaster)]">
<send-command template="SetPassword">
<parameter name="host">
<select path="name"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="adminPassword">
<select path="::adminPassword"/>
</mapping>
</map>
</parameter>
<success>
<set path="::state.domainAdminPassword">
<select path="::adminPassword"/>
</set>
</success>
</send-command>
</rule>
<rule match="$.services.activeDirectories[?(@.state.primaryDc is None)].units[?(@.state.instanceName and @.isMaster)]">
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Creating Primary Domain Controller on unit <select path="name"/></parameter>
</report>
<send-command template="CreatePrimaryDC">
<parameter name="host">
<select path="name"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="domain">
<select path="::domain"/>
</mapping>
<mapping name="recoveryPassword">
<select path="recoveryPassword"/>
</mapping>
</map>
</parameter>
<success>
<set path="::state.primaryDc"><select path="name"/></set>
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Primary Domain Controller created</parameter>
</report>
</success>
</send-command>
</rule>
<rule match="$.services.activeDirectories[?(@.state.primaryDc and not @.state.primaryDcIp)].units[?(@.state.instanceName and @.isMaster)]">
<send-command template="AskDnsIp" result="ip">
<parameter name="host">
<select path="name"/>
</parameter>
<success>
<set path="::state.primaryDcIp">
<select source="ip" path="0.Result.0"/>
</set>
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">DNS IP = <select source="ip" path="0.Result.0"/></parameter>
</report>
</success>
</send-command>
</rule>
<rule match="$..units[?(@.state.instanceName and @.domain and @.domain != @.state.domain)]">
<set path="#unit">
<select/>
</set>
<rule>
<parameter name="match">/$.services.activeDirectories[?(@.domain == '<select path="domain"/>' and @.state.primaryDcIp)]</parameter>
<send-command template="JoinDomain">
<parameter name="host">
<select path="name" source="unit"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="domain">
<select path="domain"/>
</mapping>
<mapping name="domainPassword">
<select path="adminPassword"/>
</mapping>
<mapping name="dnsIp">
<select path="state.primaryDcIp"/>
</mapping>
</map>
</parameter>
<success>
<set path="state.domain" target="unit">
<select path="domain"/>
</set>
<report entity="unit">
<parameter name="id"><select path="id" source="unit"/></parameter>
<parameter name="text">Unit <select path="name" source="unit"/> has joined domain <select path="domain"/></parameter>
</report>
</success>
</send-command>
</rule>
</rule>
<rule match="$.services.activeDirectories[*].units[?(@.state.domain and not @.isMaster and not @.state.installed)]">
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Creating Secondary Domain Controller on unit <select path="name"/></parameter>
</report>
<send-command template="CreateSecondaryDC">
<parameter name="host">
<select path="name"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="recoveryPassword">
<select path="recoveryPassword"/>
</mapping>
<mapping name="domainPassword">
<select path="::adminPassword"/>
</mapping>
</map>
</parameter>
<success marker="1">
<set path="state.installed"><true/></set>
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Primary Domain Controller created</parameter>
</report>
<report entity="service">
<parameter name="id"><select path="::id"/></parameter>
<parameter name="text">Primary Domain Controller created</parameter>
</report>
</success>
</send-command>
</rule>
</workflow>

View File

@ -0,0 +1,60 @@
<workflow>
<rule match="$.services.webServers[?(@.domain)].units[*]">
<set path="domain">
<select path="::domain"/>
</set>
</rule>
<rule match="$.services.webServers[*].units[?(@.state.instanceName is None)]">
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Creating instance <select path="name"/></parameter>
</report>
<update-cf-stack template="Windows">
<parameter name="mappings">
<map>
<mapping name="instanceName">
<select path="name"/>
</mapping>
<mapping name="userData">
<prepare_user_data/>
</mapping>
</map>
</parameter>
<parameter name="arguments">
<map>
<argument name="KeyName">keero-linux-keys</argument>
<argument name="InstanceType">m1.medium</argument>
<argument name="ImageName">ws-2012-full-agent</argument>
</map>
</parameter>
<success>
<set path="state.instanceName"><select path="name"/></set>
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Instance <select path="name"/> created</parameter>
</report>
</success>
</update-cf-stack>
</rule>
<rule match="$.services.webServers[*].units[?(@.state.instanceName and not @.state.iisInstalled)]">
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">Creating IIS Web Server on unit <select path="name"/></parameter>
</report>
<send-command template="InstallIIS">
<parameter name="host">
<select path="name"/>
</parameter>
<success>
<set path="state.iisInstalled"><true/></set>
<report entity="unit">
<parameter name="id"><select path="id"/></parameter>
<parameter name="text">IIS <select path="name"/> has started</parameter>
</report>
</success>
</send-command>
</rule>
</workflow>

View File

@ -1,69 +0,0 @@
<workflow>
<rule match="$.services.activeDirectory[*].units[?(@.state.instanceName is None)]">
<update-cf-stack template="Windows">
<parameter name="mappings">
<map>
<mapping name="instanceName">
<select path="name"/>
</mapping>
</map>
</parameter>
<parameter name="arguments">
<map>
<argument name="KeyName">keero-linux-keys</argument>
<argument name="InstanceType">m1.medium</argument>
<argument name="ImageName">ws-2012-full-agent</argument>
</map>
</parameter>
<success>
<set path="state.instanceName"><select path="name"/></set>
</success>
</update-cf-stack>
</rule>
<rule match="$.services.activeDirectory[*].units[?(@.state.instanceName is not None and @.adminPassword is not None)]">
<send-command template="SetPassword">
<parameter name="host">
<select path="name"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="adm_password">
<select path="adminPassword"/>
</mapping>
</map>
</parameter>
<success>
<set path="adminPassword"><null/></set>
</success>
</send-command>
</rule>
<rule match="$.services.activeDirectory[*].units[?(@.state.instanceName is not None and not @.state.isInRole and @.isMaster)]">
<send-command template="CreatePrimaryDC">
<parameter name="host">
<select path="name"/>
</parameter>
<parameter name="mappings">
<map>
<mapping name="dc_name">
<select path="::domain"/>
</mapping>
<mapping name="recovery_password">
<select path="recoveryPassword"/>
</mapping>
</map>
</parameter>
<success>
<set path="recoveryPassword"><null/></set>
<set path="state.isInRole"><true/></set>
<set path="::state.WeHavePrimaryDC"><true/></set>
</success>
</send-command>
</rule>
<rule match="$.services.activeDirectory[?(@state.WeHavePrimaryDC == True)].units[?(@.state.instanceName is not None and not @.state.isInRole and @.isMaster = False)]">
</rule>
</workflow>

View File

@ -1,19 +1,37 @@
{ {
"name": "MyDataCenter", "name": "MyDataCenter",
"id": "adc6d143f9584d10808c7ef4d07e4802",
"services": { "services": {
"activeDirectory": [ "activeDirectories": [
{ {
"id": "1234567890", "id": "9571747991184642B95F430A014616F9",
"domain": "acme.loc", "domain": "acme.loc",
"adminPassword": "SuperP@ssw0rd!",
"units": [ "units": [
{ {
"id": "273c9183b6e74c9c9db7fdd532c5eb25",
"name": "dc01", "name": "dc01",
"isMaster": true, "isMaster": true,
"recoveryPassword": "2SuperP@ssw0rd2" "recoveryPassword": "2SuperP@ssw0rd2"
}, },
{ {
"id": "377c6f16d17a416791f80724dab360c6",
"name": "dc02", "name": "dc02",
"isMaster": false, "isMaster": false,
"adminPassword": "SuperP@ssw0rd",
"recoveryPassword": "2SuperP@ssw0rd2"
}
]
}
],
"webServers": [
{
"id": "e9657ceef84a4e669e31795040080262",
"domain": "acme.loc",
"units": [
{
"id": "e6f9cfd07ced48fba64e6bd9e65aba64",
"name": "iis01",
"adminPassword": "SuperP@ssw0rd" "adminPassword": "SuperP@ssw0rd"
} }
] ]