Merge "Message signing implementation for legacy Windows agent"

This commit is contained in:
Zuul 2018-01-19 04:29:46 +00:00 committed by Gerrit Code Review
commit 5253f40356
18 changed files with 538 additions and 267 deletions

View File

@ -32,14 +32,12 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>

View File

@ -1,19 +1,34 @@
using System;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace Mirantis.Murano
{
class Command
internal class Command
{
public string Name { get; set; }
public Dictionary<string, object> Arguments { get; set; }
}
class ExecutionPlan
internal class ExecutionPlan
{
public List<string> Scripts { get; set; }
public List<Command> Commands { get; set; }
@ -21,9 +36,9 @@ namespace Mirantis.Murano
}
class Program
static class Program
{
static void Main(string[] args)
public static void Main(string[] args)
{
if (args.Length < 1 || args.Length > 2)
{

View File

@ -1,4 +1,19 @@
using System.Reflection;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" />
</packages>

View File

@ -1,10 +1,10 @@
<?xml version="1.0"?>
<?xml version="1.0"?>
<configuration>
<configSections>
<section name="nlog" type="NLog.Config.ConfigSectionHandler, NLog"/>
</configSections>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0,Profile=Client"/>
<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>
@ -19,10 +19,12 @@
<add key="rabbitmq.host" value="localhost"/>
<add key="rabbitmq.user" value="murano"/>
<add key="rabbitmq.password" value="murano"/>
<add key="rabbitmq.vhost" value="murano"/>
<add key="rabbitmq.vhost" value="/"/>
<add key="rabbitmq.resultExchange" value=""/>
<add key="rabbitmq.resultRoutingKey" value="-execution-results"/>
<add key="rabbitmq.resultRoutingKey" value="dd"/>
<add key="rabbitmq.durableMessages" value="true"/>
<add key="engine.key" value=""/>
</appSettings>
</configuration>

View File

@ -1,12 +1,23 @@
using System;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mirantis.Murano.WindowsAgent
{
class ExecutionPlan
internal class ExecutionPlan
{
public class Command
{
@ -17,5 +28,7 @@ namespace Mirantis.Murano.WindowsAgent
public string[] Scripts { get; set; }
public LinkedList<Command> Commands { get; set; }
public int RebootOnCompletion { get; set; }
public long Stamp { get; set; }
}
}

View File

@ -0,0 +1,41 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
namespace Mirantis.Murano.WindowsAgent
{
internal class Message : IDisposable
{
private readonly Action ackFunc;
public Message(Action ackFunc)
{
this.ackFunc = ackFunc;
}
public Message()
{
}
public string Body { get; set; }
public string Id { get; set; }
public void Dispose()
{
ackFunc();
}
}
}

View File

@ -1,25 +1,38 @@
using System;
using System.Collections.Generic;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
using System.Configuration;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading.Tasks;
using NLog;
using RabbitMQ.Client;
namespace Mirantis.Murano.WindowsAgent
{
class RabbitMqClient : IDisposable
internal class MessageSource : IDisposable
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly Logger log = LogManager.GetCurrentClassLogger();
private static readonly ConnectionFactory connectionFactory;
private static readonly string queueName;
private IConnection currentConnecton;
private readonly SignatureVerifier signatureVerifier;
static RabbitMqClient()
static MessageSource()
{
var ssl = new SslOption {
Enabled = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.ssl"] ?? "false"),
@ -45,49 +58,65 @@ namespace Mirantis.Murano.WindowsAgent
RequestedHeartbeat = 10,
Ssl = ssl
};
queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"];
}
public RabbitMqClient()
public MessageSource()
{
this.signatureVerifier = new SignatureVerifier(Encoding.ASCII.GetBytes(queueName));
}
public MqMessage GetMessage()
public Message GetMessage()
{
var queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"] ?? Dns.GetHostName().ToLower();
try
{
IConnection connection = null;
IConnection connection;
lock (this)
{
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
}
var session = connection.CreateModel();
session.BasicQos(0, 1, false);
//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 e = (RabbitMQ.Client.Events.BasicDeliverEventArgs) consumer.Queue.Dequeue();
Action ackFunc = delegate {
session.BasicAck(e.DeliveryTag, false);
session.BasicCancel(consumeTag);
session.Close();
};
return new MqMessage(ackFunc) {
Body = Encoding.UTF8.GetString(e.Body),
Id = e.BasicProperties.MessageId
};
while (true)
{
var e = consumer.Queue.Dequeue();
Action ackFunc = delegate
{
session.BasicAck(e.DeliveryTag, false);
session.BasicCancel(consumeTag);
session.Close();
};
byte[] signature = null;
if (e.BasicProperties.Headers.ContainsKey("signature"))
{
signature = (byte[]) e.BasicProperties.Headers["signature"];
}
if (this.signatureVerifier.Verify(e.Body, signature))
{
return new Message(ackFunc) {
Body = Encoding.UTF8.GetString(e.Body),
Id = e.BasicProperties.MessageId,
};
}
log.Warn("Dropping message with invalid/missing signature");
session.BasicReject(e.DeliveryTag, false);
}
}
catch (Exception exception)
catch (Exception)
{
Dispose();
throw;
if (this.currentConnecton == null) return null;
Dispose();
throw;
}
}
public void SendResult(MqMessage message)
public void SendResult(Message message)
{
var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? "";
var resultRoutingKey = ConfigurationManager.AppSettings["rabbitmq.resultRoutingKey"] ?? "-execution-results";
@ -95,22 +124,14 @@ namespace Mirantis.Murano.WindowsAgent
try
{
IConnection connection = null;
IConnection connection;
lock (this)
{
connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection();
}
var session = connection.CreateModel();
/*if (!string.IsNullOrEmpty(resultQueue))
{
//session.QueueDeclare(resultQueue, true, false, false, null);
if (!string.IsNullOrEmpty(exchangeName))
{
session.ExchangeBind(exchangeName, resultQueue, resultQueue);
}
}*/
var basicProperties = session.CreateBasicProperties();
basicProperties.SetPersistent(durable);
basicProperties.Persistent = durable;
basicProperties.MessageId = message.Id;
basicProperties.ContentType = "application/json";
session.BasicPublish(exchangeName, resultRoutingKey, basicProperties, Encoding.UTF8.GetBytes(message.Body));
@ -129,18 +150,13 @@ namespace Mirantis.Murano.WindowsAgent
{
try
{
if (this.currentConnecton != null)
{
this.currentConnecton.Close();
}
var connection = this.currentConnecton;
this.currentConnecton = null;
connection.Close();
}
catch
{
}
finally
{
this.currentConnecton = null;
}
}
}
}

View File

@ -1,30 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mirantis.Murano.WindowsAgent
{
class MqMessage
{
private readonly Action ackFunc;
public MqMessage(Action ackFunc)
{
this.ackFunc = ackFunc;
}
public MqMessage()
{
}
public string Body { get; set; }
public string Id { get; set; }
public void Ack()
{
ackFunc();
}
}
}

View File

@ -1,4 +1,19 @@
using System;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
@ -12,9 +27,10 @@ using Newtonsoft.Json;
namespace Mirantis.Murano.WindowsAgent
{
class PlanExecutor
internal class PlanExecutor
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly Logger log = LogManager.GetCurrentClassLogger();
private long lastStamp = -1;
class ExecutionResult
{
@ -22,37 +38,45 @@ namespace Mirantis.Murano.WindowsAgent
public object Result { get; set; }
}
private readonly string path;
private readonly string baseDir;
public PlanExecutor(string path)
public PlanExecutor(string baseDir)
{
this.path = path;
this.baseDir = baseDir;
}
public bool RebootNeeded { get; set; }
public void Execute()
public void Execute(string path)
{
RebootNeeded = false;
var resultPath = this.path + ".result";
var resultPath = path + ".result";
var tmpResultPath = resultPath + ".tmp";
Runspace runSpace = null;
try
{
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(this.path));
List<ExecutionResult> currentResults = null;
var plan = JsonConvert.DeserializeObject<ExecutionPlan>(File.ReadAllText(path));
List<ExecutionResult> currentResults;
try
{
currentResults = File.Exists(resultPath) ?
JsonConvert.DeserializeObject<List<ExecutionResult>>(File.ReadAllText(resultPath)) :
currentResults = File.Exists(tmpResultPath) ?
JsonConvert.DeserializeObject<List<ExecutionResult>>(File.ReadAllText(tmpResultPath)) :
new List<ExecutionResult>();
}
catch(Exception exception)
{
Log.WarnException("Cannot deserialize previous execution result", exception);
log.Warn(exception, "Cannot deserialize previous execution result");
currentResults = new List<ExecutionResult>();
}
runSpace = RunspaceFactory.CreateRunspace();
var lastStamp = GetLastStamp();
if (plan.Stamp > 0 && plan.Stamp <= lastStamp)
{
log.Warn("Dropping old/duplicate plan");
return;
}
runSpace = RunspaceFactory.CreateRunspace();
runSpace.Open();
var runSpaceInvoker = new RunspaceInvoke(runSpace);
@ -63,36 +87,39 @@ namespace Mirantis.Murano.WindowsAgent
foreach (var script in plan.Scripts)
{
runSpaceInvoker.Invoke(Encoding.UTF8.GetString(Convert.FromBase64String(script)));
Log.Debug("Loaded script #{0}", ++index);
log.Debug("Loaded script #{0}", ++index);
}
}
while (plan.Commands != null && plan.Commands.Any())
{
var command = plan.Commands.First();
Log.Debug("Preparing to execute command {0}", command.Name);
log.Debug("Preparing to execute command {0}", command.Name);
var pipeline = runSpace.CreatePipeline();
var psCommand = new Command(command.Name);
if (command.Arguments != null)
{
foreach (var kvp in command.Arguments)
{
var value = ConvertArgument(kvp.Value);
psCommand.Parameters.Add(kvp.Key, value);
}
}
if (command.Name != null)
{
var psCommand = new Command(command.Name);
if (command.Arguments != null)
{
foreach (var kvp in command.Arguments)
{
var value = ConvertArgument(kvp.Value);
psCommand.Parameters.Add(kvp.Key, 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()))));
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?.ToString() ?? "null"))));
pipeline.Commands.Add(psCommand);
pipeline.Commands.Add(psCommand);
}
try
try
{
var result = pipeline.Invoke();
Log.Debug("Command {0} executed", command.Name);
log.Debug("Command {0} executed", command.Name);
if (result != null)
{
currentResults.Add(new ExecutionResult {
@ -104,21 +131,18 @@ namespace Mirantis.Murano.WindowsAgent
catch (Exception exception)
{
object additionInfo = null;
if (exception is ActionPreferenceStopException)
{
var apse = exception as ActionPreferenceStopException;
if (apse.ErrorRecord != null)
{
additionInfo = new {
ScriptStackTrace = apse.ErrorRecord.ScriptStackTrace,
PositionMessage = apse.ErrorRecord.InvocationInfo.PositionMessage
};
exception = apse.ErrorRecord.Exception;
}
}
var apse = exception as ActionPreferenceStopException;
if (apse?.ErrorRecord != null)
{
additionInfo = new {
ScriptStackTrace = apse.ErrorRecord.ScriptStackTrace,
PositionMessage = apse.ErrorRecord.InvocationInfo.PositionMessage
};
exception = apse.ErrorRecord.Exception;
}
Log.WarnException("Exception while executing command " + command.Name, exception);
log.Warn(exception, "Exception while executing command " + command.Name);
currentResults.Add(new ExecutionResult
{
IsException = true,
@ -132,10 +156,14 @@ namespace Mirantis.Murano.WindowsAgent
{
plan.Commands.RemoveFirst();
File.WriteAllText(path, JsonConvert.SerializeObject(plan));
File.WriteAllText(resultPath, JsonConvert.SerializeObject(currentResults));
File.WriteAllText(tmpResultPath, JsonConvert.SerializeObject(currentResults));
}
}
runSpace.Close();
if (plan.Stamp > 0)
{
SetLastStamp(plan.Stamp);
}
var executionResult = JsonConvert.SerializeObject(new ExecutionResult {
IsException = false,
Result = currentResults
@ -152,11 +180,12 @@ namespace Mirantis.Murano.WindowsAgent
RebootNeeded = true;
}
}
File.WriteAllText(resultPath, executionResult);
File.Delete(tmpResultPath);
File.WriteAllText(resultPath, executionResult);
}
catch (Exception exception)
{
Log.WarnException("Exception while processing execution plan", exception);
log.Warn(exception, "Exception while processing execution plan");
File.WriteAllText(resultPath, JsonConvert.SerializeObject(new ExecutionResult {
IsException = true,
Result = exception.Message
@ -173,38 +202,33 @@ namespace Mirantis.Murano.WindowsAgent
catch
{}
}
Log.Debug("Finished processing of execution plan");
log.Debug("Finished processing of execution plan");
}
}
private static object ConvertArgument(object arg)
{
if (arg is JArray)
switch (arg)
{
var array = arg as JArray;
return array.Select(ConvertArgument).ToArray();
case JArray array:
return array.Select(ConvertArgument).ToArray();
case JValue value:
return value.Value;
case JObject dict:
var result = new Hashtable();
foreach (var item in dict)
{
result.Add(item.Key, ConvertArgument(item.Value));
}
return result;
}
else if (arg is JValue)
{
var value = (JValue) arg;
return value.Value;
}
else if (arg is JObject)
{
var dict = (JObject)arg;
var result = new Hashtable();
foreach (var item in dict)
{
result.Add(item.Key, ConvertArgument(item.Value));
}
return result;
}
return arg;
return arg;
}
private static object SerializePsObject(PSObject obj)
{
if (obj.BaseObject is PSCustomObject)
if (obj.BaseObject is PSCustomObject)
{
var result = new Dictionary<string, object>();
foreach (var property in obj.Properties.Where(p => p.IsGettable))
@ -219,15 +243,61 @@ namespace Mirantis.Murano.WindowsAgent
}
return result;
}
else if (obj.BaseObject is IEnumerable<PSObject>)
{
return ((IEnumerable<PSObject>) obj.BaseObject).Select(SerializePsObject).ToArray();
}
else
{
return obj.BaseObject;
}
if (obj.BaseObject is IEnumerable<PSObject> objects)
{
return objects.Select(SerializePsObject).ToArray();
}
return obj.BaseObject;
}
private long GetLastStamp()
{
if (this.lastStamp >= 0)
{
return this.lastStamp;
}
var path = Path.Combine(this.baseDir, "stamp.txt");
if (File.Exists(path))
{
try
{
var stampData = File.ReadAllText(path);
this.lastStamp = long.Parse(stampData);
}
catch (Exception e)
{
this.lastStamp = 0;
}
}
else
{
this.lastStamp = 0;
}
return this.lastStamp;
}
private void SetLastStamp(long value)
{
var path = Path.Combine(this.baseDir, "stamp.txt");
try
{
File.WriteAllText(path, value.ToString());
}
catch (Exception e)
{
log.Error(e, "Cannot persist last stamp");
throw;
}
finally
{
this.lastStamp = value;
}
}
}
}

View File

@ -1,55 +1,69 @@
using System;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Management.Automation;
using System.Net;
using System.Text;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using NLog;
namespace Mirantis.Murano.WindowsAgent
{
[DisplayName("Murano Agent")]
sealed public class Program : WindowsService
public sealed class Program : WindowsService
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly Logger log = LogManager.GetCurrentClassLogger();
private volatile bool stop;
private Thread thread;
private RabbitMqClient rabbitMqClient;
private MessageSource messageSource;
private int delayFactor = 1;
private string plansDir;
static void Main(string[] args)
public static void Main(string[] args)
{
Start(new Program(), args);
Start(new Program(), args);
}
protected override void OnStart(string[] args)
{
base.OnStart(args);
base.OnStart(args);
Log.Info("Version 0.5.4");
log.Info("Version 0.6");
this.rabbitMqClient = new RabbitMqClient();
this.messageSource = new MessageSource();
var basePath = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
this.plansDir = Path.Combine(basePath, "plans");
if (!Directory.Exists(plansDir))
{
Directory.CreateDirectory(plansDir);
Directory.CreateDirectory(plansDir);
}
this.thread = new Thread(Loop);
this.thread.Start();
}
void Loop()
private void Loop()
{
const string unknownName = "unknown";
const string unknownName = "unknown";
var executor = new PlanExecutor(this.plansDir);
while (!stop)
{
try
@ -57,40 +71,45 @@ namespace Mirantis.Murano.WindowsAgent
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;
var id = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(file));
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 });
log.Info("Sending results for {0}", id);
messageSource.SendResult(new Message { Body = result, Id = id });
File.Delete(file);
}
var path = Directory.EnumerateFiles(this.plansDir, "*.json").FirstOrDefault();
if (path == null)
{
var message = rabbitMqClient.GetMessage();
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();
using (var message = messageSource.GetMessage())
{
if (message == null)
{
return;
}
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);
}
}
else
{
var id = Path.GetFileNameWithoutExtension(path);
Log.Info("Executing exising plan {0}", id);
log.Info("Executing exising plan {0}", id);
}
var executor = new PlanExecutor(path);
executor.Execute();
executor.Execute(path);
File.Delete(path);
delayFactor = 1;
@ -104,23 +123,13 @@ namespace Mirantis.Murano.WindowsAgent
{
WaitOnException(exception);
}
}
}
private void Reboot()
{
Log.Info("Going for reboot!!");
log.Info("Going for reboot!!");
LogManager.Flush();
/*try
{
System.Diagnostics.Process.Start("shutdown.exe", "-r -t 0");
}
catch (Exception ex)
{
Log.ErrorException("Cannot execute shutdown.exe", ex);
}*/
try
@ -130,24 +139,23 @@ namespace Mirantis.Murano.WindowsAgent
catch (Exception exception)
{
Log.FatalException("Reboot exception", exception);
log.Fatal(exception, "Reboot exception");
}
finally
{
Log.Info("Waiting for reboot");
log.Info("Waiting for reboot");
for (var i = 0; i < 10 * 60 * 5 && !stop; i++)
{
Thread.Sleep(100);
}
Log.Info("Done waiting for reboot");
log.Info("Done waiting for reboot");
}
}
private void WaitOnException(Exception exception)
{
if (stop) return;
Log.WarnException("Exception in main loop", exception);
log.Warn(exception, "Exception in main loop");
var i = 0;
while (!stop && i < 10 * (delayFactor * delayFactor))
{
@ -160,9 +168,8 @@ namespace Mirantis.Murano.WindowsAgent
protected override void OnStop()
{
stop = true;
this.rabbitMqClient.Dispose();
this.messageSource.Dispose();
base.OnStop();
}
}
}

View File

@ -1,4 +1,19 @@
using System.Reflection;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

View File

@ -1,4 +1,19 @@
using System;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
using System.Configuration.Install;
using System.Reflection;
using System.ServiceProcess;
@ -15,7 +30,7 @@ namespace Mirantis.Murano.WindowsAgent
this.serviceName = serviceName;
}
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
private static readonly Logger log = LogManager.GetCurrentClassLogger();
public bool Restart(string[] args, TimeSpan timeout)
{
@ -26,7 +41,7 @@ namespace Mirantis.Murano.WindowsAgent
service.Stop();
service.WaitForStatus(ServiceControllerStatus.Stopped, timeout);
Log.Info("Service is stopped");
log.Info("Service is stopped");
// count the rest of the timeout
var millisec2 = TimeSpan.FromMilliseconds(Environment.TickCount);
@ -34,12 +49,12 @@ namespace Mirantis.Murano.WindowsAgent
service.Start(args);
service.WaitForStatus(ServiceControllerStatus.Running, timeout);
Log.Info("Service has started");
log.Info("Service has started");
return true;
}
catch (Exception ex)
{
Log.ErrorException("Cannot restart service " + serviceName, ex);
log.Error(ex, "Cannot restart service " + serviceName);
return false;
}
}
@ -55,7 +70,7 @@ namespace Mirantis.Murano.WindowsAgent
}
catch (Exception ex)
{
Log.ErrorException("Cannot stop service " + serviceName, ex);
log.Error(ex, "Cannot stop service " + serviceName);
return false;
}
}
@ -71,7 +86,7 @@ namespace Mirantis.Murano.WindowsAgent
}
catch (Exception ex)
{
Log.ErrorException("Cannot start service " + serviceName, ex);
log.Error(ex, "Cannot start service " + serviceName);
return false;
}
}
@ -81,11 +96,11 @@ namespace Mirantis.Murano.WindowsAgent
try
{
ManagedInstallerClass.InstallHelper(
new string[] { Assembly.GetEntryAssembly().Location });
new[] { Assembly.GetEntryAssembly().Location });
}
catch(Exception ex)
{
Log.ErrorException("Cannot install service " + serviceName, ex);
log.Error(ex, "Cannot install service " + serviceName);
return false;
}
return true;
@ -96,11 +111,11 @@ namespace Mirantis.Murano.WindowsAgent
try
{
ManagedInstallerClass.InstallHelper(
new string[] { "/u", Assembly.GetEntryAssembly().Location });
new[] { "/u", Assembly.GetEntryAssembly().Location });
}
catch (Exception ex)
{
Log.ErrorException("Cannot uninstall service " + serviceName, ex);
log.Error(ex, "Cannot uninstall service " + serviceName);
return false;
}
return true;

View File

@ -0,0 +1,60 @@
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System.Configuration;
using System.IO;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
namespace Mirantis.Murano.WindowsAgent
{
internal class SignatureVerifier
{
private readonly ISigner signer;
private readonly byte[] salt;
public SignatureVerifier(byte[] salt)
{
var keyStr = ConfigurationManager.AppSettings["engine.key"];
if (string.IsNullOrEmpty(keyStr)) return;
var reader = new StringReader(keyStr);
var key = (RsaKeyParameters) new PemReader(reader).ReadObject();
this.signer = SignerUtilities.GetSigner("SHA256withRSA");
this.signer.Init(false, key);
this.salt = salt;
}
public bool Verify(byte[] data, byte[] signature)
{
if (this.signer == null)
{
return true;
}
if (signature == null)
{
return false;
}
this.signer.Reset();
this.signer.BlockUpdate(this.salt, 0, this.salt.Length);
this.signer.BlockUpdate(data, 0, data.Length);
return this.signer.VerifySignature(signature);
}
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project ToolsVersion="12.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -9,9 +9,10 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Mirantis.Murano.WindowsAgent</RootNamespace>
<AssemblyName>WindowsAgent</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<TargetFrameworkProfile>Client</TargetFrameworkProfile>
<TargetFrameworkProfile>
</TargetFrameworkProfile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
@ -34,15 +35,22 @@
<WarningLevel>4</WarningLevel>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup>
<StartupObject>
</StartupObject>
</PropertyGroup>
<ItemGroup>
<Reference Include="Newtonsoft.Json">
<HintPath>..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll</HintPath>
<Reference Include="BouncyCastle.Crypto, Version=1.8.1.0, Culture=neutral, PublicKeyToken=0e99375e54769942">
<HintPath>..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll</HintPath>
</Reference>
<Reference Include="NLog">
<HintPath>..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll</HintPath>
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="RabbitMQ.Client">
<HintPath>..\packages\RabbitMQ.Client.3.0.2\lib\net30\RabbitMQ.Client.dll</HintPath>
<Reference Include="NLog, Version=4.0.0.0, Culture=neutral, PublicKeyToken=5120e14c03d0593c, processorArchitecture=MSIL">
<HintPath>..\packages\NLog.4.4.12\lib\net45\NLog.dll</HintPath>
</Reference>
<Reference Include="RabbitMQ.Client, Version=3.6.9.0, Culture=neutral, PublicKeyToken=89e7d7c5feba84ce, processorArchitecture=MSIL">
<HintPath>..\packages\RabbitMQ.Client.3.6.9\lib\net45\RabbitMQ.Client.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Configuration" />
@ -53,22 +61,18 @@
<HintPath>C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll</HintPath>
</Reference>
<Reference Include="System.ServiceProcess" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="ExecutionPlan.cs" />
<Compile Include="MqMessage.cs" />
<Compile Include="Message.cs" />
<Compile Include="PlanExecutor.cs" />
<Compile Include="Program.cs">
<SubType>Component</SubType>
</Compile>
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="RabbitMqClient.cs" />
<Compile Include="MessageSource.cs" />
<Compile Include="ServiceManager.cs" />
<Compile Include="SignatureVerifier.cs" />
<Compile Include="WindowsService.cs">
<SubType>Component</SubType>
</Compile>
@ -77,7 +81,9 @@
</Compile>
</ItemGroup>
<ItemGroup>
<None Include="App.config" />
<None Include="App.config">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
<None Include="SampleExecutionPlan.json" />
</ItemGroup>

View File

@ -1,4 +1,19 @@
using System;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
@ -10,8 +25,7 @@ namespace Mirantis.Murano.WindowsAgent
{
public abstract class WindowsService : ServiceBase
{
private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public bool RunningAsService { get; private set; }
private static readonly Logger log = LogManager.GetCurrentClassLogger();
protected static void Start(WindowsService service, string[] arguments)
{
@ -39,14 +53,12 @@ namespace Mirantis.Murano.WindowsAgent
}
else if (!arguments.Contains("/console", StringComparer.OrdinalIgnoreCase))
{
service.RunningAsService = true;
Run(service);
}
else
{
try
{
service.RunningAsService = false;
Console.Title = service.ServiceName;
service.OnStart(Environment.GetCommandLineArgs());
service.WaitForExitSignal();
@ -81,14 +93,14 @@ namespace Mirantis.Murano.WindowsAgent
protected override void OnStart(string[] args)
{
Log.Info("Service {0} started", ServiceName);
log.Info("Service {0} started", ServiceName);
base.OnStart(args);
}
protected override void OnStop()
{
Log.Info("Service {0} exited", ServiceName);
log.Info("Service {0} exited", ServiceName);
base.OnStop();
}
}

View File

@ -1,4 +1,19 @@
using System.ComponentModel;
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you 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.
using System.ComponentModel;
using System.Configuration.Install;
using System.Linq;
using System.Reflection;

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="4.5.11" targetFramework="net45" />
<package id="NLog" version="2.0.0.2000" targetFramework="net45" />
<package id="RabbitMQ.Client" version="3.0.2" targetFramework="net45" />
<package id="BouncyCastle" version="1.8.1" targetFramework="net451" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net45" />
<package id="NLog" version="4.4.12" targetFramework="net45" />
<package id="RabbitMQ.Client" version="3.6.9" targetFramework="net45" />
</packages>