diff -Nru murano-agent-3.3.0/AUTHORS murano-agent-3.4.0/AUTHORS --- murano-agent-3.3.0/AUTHORS 2017-08-10 15:59:41.000000000 +0000 +++ murano-agent-3.4.0/AUTHORS 2018-02-05 21:07:00.000000000 +0000 @@ -55,6 +55,7 @@ Victor Ryzhenkin Victor Stinner Yushiro FURUKAWA +Zuul binean deepakmourya ghanshyam @@ -62,7 +63,9 @@ huangtianhua janonymous jeremy.zhang +rajat29 ricolin ting.wang +yatin zhu.rong zhurong diff -Nru murano-agent-3.3.0/ChangeLog murano-agent-3.4.0/ChangeLog --- murano-agent-3.3.0/ChangeLog 2017-08-10 15:59:41.000000000 +0000 +++ murano-agent-3.4.0/ChangeLog 2018-02-05 21:07:00.000000000 +0000 @@ -1,6 +1,29 @@ CHANGES ======= +3.4.0 +----- + +* Updated from global requirements +* Fix: config generation +* Updated from global requirements +* Updated from global requirements +* Message signing implementation for legacy Windows agent +* Murano-agent side implementation of message signing +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements +* Tighten access to runtime agent folders +* Remove setting of version/release from releasenotes +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements +* Fix to use . to source script files +* Updated from global requirements +* Updated from global requirements +* Updated from global requirements +* Update reno for stable/pike + 3.3.0 ----- diff -Nru murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj --- murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/ExecutionPlanGenerator.csproj 2018-02-05 21:04:08.000000000 +0000 @@ -32,14 +32,12 @@ 4 - - ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - diff -Nru murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/packages.config murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/packages.config --- murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/packages.config 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/packages.config 2018-02-05 21:04:08.000000000 +0000 @@ -1,4 +1,4 @@  - + \ No newline at end of file diff -Nru murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/Program.cs murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/Program.cs --- murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/Program.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/Program.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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 Arguments { get; set; } } - class ExecutionPlan + + internal class ExecutionPlan { public List Scripts { get; set; } public List Commands { get; set; } @@ -21,9 +36,9 @@ } - class Program + static class Program { - static void Main(string[] args) + public static void Main(string[] args) { if (args.Length < 1 || args.Length > 2) { diff -Nru murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs --- murano-agent-3.3.0/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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; diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/App.config murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/App.config --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/App.config 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/App.config 2018-02-05 21:04:08.000000000 +0000 @@ -1,10 +1,10 @@ - +
- + @@ -19,10 +19,12 @@ - + - + + + diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/ExecutionPlan.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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 @@ public string[] Scripts { get; set; } public LinkedList Commands { get; set; } public int RebootOnCompletion { get; set; } + + public long Stamp { get; set; } } } diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/Message.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/Message.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/Message.cs 1970-01-01 00:00:00.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/Message.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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(); + } + } +} diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/MessageSource.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/MessageSource.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/MessageSource.cs 1970-01-01 00:00:00.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/MessageSource.cs 2018-02-05 21:04:08.000000000 +0000 @@ -0,0 +1,163 @@ +// 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.Net.Security; +using System.Security.Authentication; +using System.Text; +using NLog; +using RabbitMQ.Client; + +namespace Mirantis.Murano.WindowsAgent +{ + internal class MessageSource : IDisposable + { + 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 MessageSource() + { + var ssl = new SslOption { + Enabled = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.ssl"] ?? "false"), + Version = SslProtocols.Default, + AcceptablePolicyErrors = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.allowInvalidCA"] ?? "true") ? + SslPolicyErrors.RemoteCertificateChainErrors : SslPolicyErrors.None + }; + + var sslServerName = ConfigurationManager.AppSettings["rabbitmq.sslServerName"] ?? ""; + ssl.ServerName = sslServerName; + if (String.IsNullOrWhiteSpace(sslServerName)) + { + ssl.AcceptablePolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch; + } + + connectionFactory = new ConnectionFactory { + HostName = ConfigurationManager.AppSettings["rabbitmq.host"] ?? "localhost", + UserName = ConfigurationManager.AppSettings["rabbitmq.user"] ?? "guest", + Password = ConfigurationManager.AppSettings["rabbitmq.password"] ??"guest", + Protocol = Protocols.DefaultProtocol, + VirtualHost = ConfigurationManager.AppSettings["rabbitmq.vhost"] ?? "/", + Port = int.Parse(ConfigurationManager.AppSettings["rabbitmq.port"] ?? "5672"), + RequestedHeartbeat = 10, + Ssl = ssl + }; + queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"]; + } + + public MessageSource() + { + this.signatureVerifier = new SignatureVerifier(Encoding.ASCII.GetBytes(queueName)); + } + + public Message GetMessage() + { + try + { + IConnection connection; + lock (this) + { + connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection(); + } + var session = connection.CreateModel(); + session.BasicQos(0, 1, false); + var consumer = new QueueingBasicConsumer(session); + var consumeTag = session.BasicConsume(queueName, false, consumer); + + 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) + { + if (this.currentConnecton == null) return null; + Dispose(); + throw; + } + } + + public void SendResult(Message message) + { + var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? ""; + var resultRoutingKey = ConfigurationManager.AppSettings["rabbitmq.resultRoutingKey"] ?? "-execution-results"; + bool durable = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.durableMessages"] ?? "true"); + + try + { + IConnection connection; + lock (this) + { + connection = this.currentConnecton = this.currentConnecton ?? connectionFactory.CreateConnection(); + } + var session = connection.CreateModel(); + var basicProperties = session.CreateBasicProperties(); + basicProperties.Persistent = durable; + basicProperties.MessageId = message.Id; + basicProperties.ContentType = "application/json"; + session.BasicPublish(exchangeName, resultRoutingKey, basicProperties, Encoding.UTF8.GetBytes(message.Body)); + session.Close(); + } + catch (Exception) + { + Dispose(); + throw; + } + } + + public void Dispose() + { + lock (this) + { + try + { + var connection = this.currentConnecton; + this.currentConnecton = null; + connection.Close(); + } + catch + { + } + } + } + } +} diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/MqMessage.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/MqMessage.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/MqMessage.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/MqMessage.cs 1970-01-01 00:00:00.000000000 +0000 @@ -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(); - } - } -} diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/packages.config murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/packages.config --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/packages.config 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/packages.config 2018-02-05 21:04:08.000000000 +0000 @@ -1,6 +1,7 @@  - - - + + + + \ No newline at end of file diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/PlanExecutor.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/PlanExecutor.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/PlanExecutor.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/PlanExecutor.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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 @@ 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 @@ 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(File.ReadAllText(this.path)); - List currentResults = null; + var plan = JsonConvert.DeserializeObject(File.ReadAllText(path)); + List currentResults; try { - currentResults = File.Exists(resultPath) ? - JsonConvert.DeserializeObject>(File.ReadAllText(resultPath)) : + currentResults = File.Exists(tmpResultPath) ? + JsonConvert.DeserializeObject>(File.ReadAllText(tmpResultPath)) : new List(); } catch(Exception exception) { - Log.WarnException("Cannot deserialize previous execution result", exception); + log.Warn(exception, "Cannot deserialize previous execution result"); currentResults = new List(); } - 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 @@ 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); - } - } - - Log.Info("Executing {0} {1}", command.Name, string.Join(" ", - (command.Arguments ?? new Dictionary()).Select( - t => string.Format("{0}={1}", t.Key, t.Value == null ? "null" : t.Value.ToString())))); + 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()).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 @@ 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 @@ { 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 @@ 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 @@ 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(); foreach (var property in obj.Properties.Where(p => p.IsGettable)) @@ -219,15 +243,61 @@ } return result; } - else if (obj.BaseObject is IEnumerable) - { - return ((IEnumerable) obj.BaseObject).Select(SerializePsObject).ToArray(); - } - else - { - return obj.BaseObject; - } + + if (obj.BaseObject is IEnumerable 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; + } + } } + } diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/Program.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/Program.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/Program.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/Program.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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 @@ 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 @@ { 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 @@ 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 @@ protected override void OnStop() { stop = true; - this.rabbitMqClient.Dispose(); + this.messageSource.Dispose(); base.OnStop(); } - } } diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/Properties/AssemblyInfo.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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; diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/RabbitMqClient.cs 1970-01-01 00:00:00.000000000 +0000 @@ -1,147 +0,0 @@ -using System; -using System.Collections.Generic; -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 - { - private static readonly Logger Log = LogManager.GetCurrentClassLogger(); - private static readonly ConnectionFactory connectionFactory; - private IConnection currentConnecton; - - static RabbitMqClient() - { - var ssl = new SslOption { - Enabled = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.ssl"] ?? "false"), - Version = SslProtocols.Default, - AcceptablePolicyErrors = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.allowInvalidCA"] ?? "true") ? - SslPolicyErrors.RemoteCertificateChainErrors : SslPolicyErrors.None - }; - - var sslServerName = ConfigurationManager.AppSettings["rabbitmq.sslServerName"] ?? ""; - ssl.ServerName = sslServerName; - if (String.IsNullOrWhiteSpace(sslServerName)) - { - ssl.AcceptablePolicyErrors |= SslPolicyErrors.RemoteCertificateNameMismatch; - } - - connectionFactory = new ConnectionFactory { - HostName = ConfigurationManager.AppSettings["rabbitmq.host"] ?? "localhost", - UserName = ConfigurationManager.AppSettings["rabbitmq.user"] ?? "guest", - Password = ConfigurationManager.AppSettings["rabbitmq.password"] ??"guest", - Protocol = Protocols.DefaultProtocol, - VirtualHost = ConfigurationManager.AppSettings["rabbitmq.vhost"] ?? "/", - Port = int.Parse(ConfigurationManager.AppSettings["rabbitmq.port"] ?? "5672"), - RequestedHeartbeat = 10, - Ssl = ssl - }; - } - - public RabbitMqClient() - { - - } - - public MqMessage GetMessage() - { - var queueName = ConfigurationManager.AppSettings["rabbitmq.inputQueue"] ?? Dns.GetHostName().ToLower(); - try - { - IConnection connection = null; - 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 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 - }; - } - catch (Exception exception) - { - - Dispose(); - throw; - } - } - - public void SendResult(MqMessage message) - { - var exchangeName = ConfigurationManager.AppSettings["rabbitmq.resultExchange"] ?? ""; - var resultRoutingKey = ConfigurationManager.AppSettings["rabbitmq.resultRoutingKey"] ?? "-execution-results"; - bool durable = bool.Parse(ConfigurationManager.AppSettings["rabbitmq.durableMessages"] ?? "true"); - - try - { - IConnection connection = null; - 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.MessageId = message.Id; - basicProperties.ContentType = "application/json"; - session.BasicPublish(exchangeName, resultRoutingKey, basicProperties, Encoding.UTF8.GetBytes(message.Body)); - session.Close(); - } - catch (Exception) - { - Dispose(); - throw; - } - } - - public void Dispose() - { - lock (this) - { - try - { - if (this.currentConnecton != null) - { - this.currentConnecton.Close(); - } - } - catch - { - } - finally - { - this.currentConnecton = null; - } - } - } - } -} diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/ServiceManager.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/ServiceManager.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/ServiceManager.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/ServiceManager.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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 @@ 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 @@ 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 @@ 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 @@ } catch (Exception ex) { - Log.ErrorException("Cannot stop service " + serviceName, ex); + log.Error(ex, "Cannot stop service " + serviceName); return false; } } @@ -71,7 +86,7 @@ } catch (Exception ex) { - Log.ErrorException("Cannot start service " + serviceName, ex); + log.Error(ex, "Cannot start service " + serviceName); return false; } } @@ -81,11 +96,11 @@ 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 @@ 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; diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/SignatureVerifier.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/SignatureVerifier.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/SignatureVerifier.cs 1970-01-01 00:00:00.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/SignatureVerifier.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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); + } + } +} diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/WindowsAgent.csproj 2018-02-05 21:04:08.000000000 +0000 @@ -1,5 +1,5 @@  - + Debug @@ -9,9 +9,10 @@ Properties Mirantis.Murano.WindowsAgent WindowsAgent - v4.0 + v4.5 512 - Client + + AnyCPU @@ -34,15 +35,22 @@ 4 false + + + + - - ..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll + + ..\packages\BouncyCastle.1.8.1\lib\BouncyCastle.Crypto.dll + + + ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll - - ..\packages\NLog.2.0.0.2000\lib\net40\NLog.dll + + ..\packages\NLog.4.4.12\lib\net45\NLog.dll - - ..\packages\RabbitMQ.Client.3.0.2\lib\net30\RabbitMQ.Client.dll + + ..\packages\RabbitMQ.Client.3.6.9\lib\net45\RabbitMQ.Client.dll @@ -53,22 +61,18 @@ C:\Program Files (x86)\Reference Assemblies\Microsoft\WindowsPowerShell\3.0\System.Management.Automation.dll - - - - - - + Component - + + Component @@ -77,7 +81,9 @@ - + + Designer + diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/WindowsService.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/WindowsService.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/WindowsService.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/WindowsService.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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 @@ { 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 @@ } 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 @@ 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(); } } diff -Nru murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs --- murano-agent-3.3.0/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs 2018-02-05 21:04:08.000000000 +0000 @@ -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; diff -Nru murano-agent-3.3.0/debian/changelog murano-agent-3.4.0/debian/changelog --- murano-agent-3.3.0/debian/changelog 2017-08-17 03:32:55.000000000 +0000 +++ murano-agent-3.4.0/debian/changelog 2018-02-14 21:41:43.000000000 +0000 @@ -1,8 +1,18 @@ -murano-agent (1:3.3.0-0ubuntu1~cloud0) xenial-pike; urgency=medium +murano-agent (1:3.4.0-0ubuntu1~cloud0) xenial-queens; urgency=medium * New upstream release for the Ubuntu Cloud Archive. - -- Openstack Ubuntu Testing Bot Thu, 17 Aug 2017 03:32:55 +0000 + -- Openstack Ubuntu Testing Bot Wed, 14 Feb 2018 21:41:43 +0000 + +murano-agent (1:3.4.0-0ubuntu1) bionic; urgency=medium + + * New upstream release for OpenStack Queens. + * d/*: wrap-and-sort -bast. + * d/control: Update Standards-Version to 4.1.2. + * d/control: Bump debhelper compat to 10 and drop dh-systemd. + * d/control: Align (Build-)Depends with upstream. + + -- Corey Bryant Wed, 14 Feb 2018 12:07:55 -0500 murano-agent (1:3.3.0-0ubuntu1) artful; urgency=medium diff -Nru murano-agent-3.3.0/debian/compat murano-agent-3.4.0/debian/compat --- murano-agent-3.3.0/debian/compat 2017-08-17 01:37:32.000000000 +0000 +++ murano-agent-3.4.0/debian/compat 2018-02-14 17:07:55.000000000 +0000 @@ -1 +1 @@ -9 +10 diff -Nru murano-agent-3.3.0/debian/control murano-agent-3.4.0/debian/control --- murano-agent-3.3.0/debian/control 2017-08-17 01:37:32.000000000 +0000 +++ murano-agent-3.4.0/debian/control 2018-02-14 17:07:55.000000000 +0000 @@ -3,38 +3,41 @@ Priority: extra Maintainer: Ubuntu Developers XSBC-Original-Maintainer: PKG OpenStack -Uploaders: Thomas Goirand , -Build-Depends: debhelper (>= 9), - dh-python, - dh-systemd, - openstack-pkg-tools (>= 23~), - python-all, - python-pbr (>= 2.0.0), - python-setuptools, - python-sphinx, -Build-Depends-Indep: python-anyjson (>= 0.3.3), - python-coverage (>= 4.0), - python-eventlet (>= 0.18.2), - python-git (>= 1.0.1), - python-hacking (>= 0.12.0), - python-kombu (>= 4.0.0), - python-mock (>= 2.0), - python-openstackdocstheme (>= 1.16.0), - python-oslo.config (>= 1:4.0.0), - python-oslo.log (>= 3.22.0), - python-oslo.service (>= 1.10.0), - python-oslo.utils (>= 3.20.0), - python-oslotest (>= 1.10.0), - python-requests (>= 2.14.2), - python-semantic-version (>= 2.3.1), - python-six (>= 1.9.0), - python-testrepository (>= 0.0.18), - python-testtools (>= 1.4.0), - python-unittest2, - python-yaml (>= 3.10.0), - subunit, - testrepository -Standards-Version: 3.9.8 +Uploaders: + Thomas Goirand , +Build-Depends: + debhelper (>= 10~), + dh-python, + openstack-pkg-tools (>= 23~), + python-all, + python-pbr (>= 2.0.0), + python-setuptools, + python-sphinx (>= 1.6.2), +Build-Depends-Indep: + python-anyjson (>= 0.3.3), + python-coverage (>= 4.0), + python-cryptography (>= 1.9), + python-eventlet (>= 0.18.2), + python-git (>= 1.0.1), + python-hacking (>= 0.12.0), + python-kombu (>= 4.0.0), + python-mock (>= 2.0.0), + python-openstackdocstheme (>= 1.18.1), + python-oslo.config (>= 1:5.1.0), + python-oslo.log (>= 3.36.0), + python-oslo.service (>= 1.24.0), + python-oslo.utils (>= 3.33.0), + python-oslotest (>= 1:3.2.0), + python-requests (>= 2.14.2), + python-semantic-version (>= 2.3.1), + python-six (>= 1.10.0), + python-testrepository (>= 0.0.18), + python-testtools (>= 2.2.0), + python-unittest2 (>= 1.1.0), + python-yaml (>= 3.10), + subunit, + testrepository, +Standards-Version: 4.1.2 Vcs-Browser: https://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/murano-agent Vcs-Git: git://git.launchpad.net/~ubuntu-server-dev/ubuntu/+source/murano-agent Homepage: https://github.com/openstack/murano-agent @@ -42,23 +45,26 @@ Package: murano-agent Architecture: all Section: python -Depends: adduser, - python-anyjson (>= 0.3.3), - python-eventlet (>= 0.18.4), - python-git (>= 1.0.1), - python-kombu (>= 4.0.0), - python-oslo.config (>= 1:4.0.0), - python-oslo.log (>= 3.22.0), - python-oslo.service (>= 1.10.0), - python-oslo.utils (>= 3.20.0), - python-pbr (>= 2.0.0), - python-requests (>= 2.14.2), - python-semantic-version (>= 2.3.1), - python-six (>= 1.9.0), - python-yaml, - ${misc:Depends}, - ${python:Depends} -Recommends: python-ldap, +Depends: + adduser, + python-anyjson (>= 0.3.3), + python-cryptography (>= 1.9), + python-eventlet (>= 0.18.2), + python-git (>= 1.0.1), + python-kombu (>= 4.0.0), + python-oslo.config (>= 1:5.1.0), + python-oslo.log (>= 3.36.0), + python-oslo.service (>= 1.24.0), + python-oslo.utils (>= 3.33.0), + python-pbr (>= 2.0.0), + python-requests (>= 2.14.2), + python-semantic-version (>= 2.3.1), + python-six (>= 1.10.0), + python-yaml (>= 3.10), + ${misc:Depends}, + ${python:Depends}, +Recommends: + python-ldap, Description: cloud-ready application catalog - VM agent Murano Project introduces an application catalog, which allows application developers and cloud administrators to publish various cloud-ready diff -Nru murano-agent-3.3.0/muranoagent/app.py murano-agent-3.4.0/muranoagent/app.py --- murano-agent-3.3.0/muranoagent/app.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/app.py 2018-02-05 21:04:08.000000000 +0000 @@ -20,21 +20,17 @@ from oslo_log import log as logging from oslo_service import service from oslo_utils import strutils -import semantic_version -import six -from muranoagent import bunch from muranoagent.common import config from muranoagent.common import messaging -from muranoagent import exceptions as exc from muranoagent import execution_plan_queue from muranoagent import execution_plan_runner from muranoagent import execution_result as ex_result +from muranoagent import validation CONF = config.CONF LOG = logging.getLogger(__name__) -max_format_version = semantic_version.Spec('<=2.2.0') class MuranoAgent(service.Service): @@ -80,11 +76,29 @@ if plan is not None: LOG.debug("Got an execution plan '{0}':".format( strutils.mask_password(str(plan)))) - self._run(plan) + if self._verify_plan(plan): + self._run(plan) return next(msg_iterator) + def _verify_plan(self, plan): + try: + validation.validate_plan(plan) + return True + except Exception as err: + try: + execution_result = ex_result.ExecutionResult.from_error( + err, plan) + if 'ReplyTo' in plan and CONF.enable_dynamic_result_queue: + execution_result['ReplyTo'] = plan.ReplyTo + + self._send_result(execution_result) + except ValueError: + LOG.warning('Execution result is not produced') + finally: + return False + def _run(self, plan): try: with execution_plan_runner.ExecutionPlanRunner(plan) as runner: @@ -135,12 +149,18 @@ prefetch_count=1) as subscription: while True: msg = subscription.get_message(timeout=5) - if msg is not None and isinstance(msg.body, dict): - self._handle_message(msg) + if msg is not None: + try: + self._queue.put_execution_plan( + msg.body, + msg.signature, + msg.id, + msg.reply_to) + finally: + msg.ack() delay = 5 if msg is not None: - msg.ack() yield except KeyboardInterrupt: break @@ -148,138 +168,3 @@ LOG.warning('Communication error', exc_info=True) time.sleep(delay) delay = min(delay * 1.2, 60) - - def _handle_message(self, msg): - if 'ID' not in msg.body and msg.id: - msg.body['ID'] = msg.id - if 'ReplyTo' not in msg.body and msg.reply_to: - msg.body['ReplyTo'] = msg.reply_to - try: - self._verify_plan(msg.body) - self._queue.put_execution_plan(msg.body) - except Exception as err: - try: - execution_result = ex_result.ExecutionResult.from_error( - err, bunch.Bunch(msg.body)) - if ('ReplyTo' in msg.body) and \ - CONF.enable_dynamic_result_queue: - execution_result['ReplyTo'] = msg.body.get('ReplyTo') - - self._send_result(execution_result) - except ValueError: - LOG.warning('Execution result is not produced') - - def _verify_plan(self, plan): - plan_format_version = semantic_version.Version( - plan.get('FormatVersion', '1.0.0')) - - if plan_format_version not in max_format_version: - # NOTE(kazitsev) this is Version in Spec not str in str - raise exc.IncorrectFormat( - 9, - "Unsupported format version {0} " - "(I support versions {1})".format( - plan_format_version, max_format_version)) - - for attr in ('Scripts', 'Files'): - if attr not in plan: - raise exc.IncorrectFormat( - 2, '{0} is not in the execution plan'.format(attr)) - - for attr in ('Scripts', 'Files', 'Options'): - if attr in plan and not isinstance( - plan[attr], dict): - raise exc.IncorrectFormat( - 2, '{0} is not a dictionary'.format(attr)) - - for name, script in plan.get('Scripts', {}).items(): - self._validate_script(name, script, plan_format_version, plan) - - for key, plan_file in plan.get('Files', {}).items(): - self._validate_file(plan_file, key, plan_format_version) - - def _validate_script(self, name, script, plan_format_version, plan): - for attr in ('Type', 'EntryPoint'): - if attr not in script or not isinstance(script[attr], - six.string_types): - raise exc.IncorrectFormat( - 2, 'Incorrect {0} entry in script {1}'.format( - attr, name)) - - if plan_format_version in semantic_version.Spec('>=2.0.0,<2.1.0'): - if script['Type'] != 'Application': - raise exc.IncorrectFormat( - 2, 'Type {0} is not valid for format {1}'.format( - script['Type'], plan_format_version)) - if script['EntryPoint'] not in plan.get('Files', {}): - raise exc.IncorrectFormat( - 2, 'Script {0} misses entry point {1}'.format( - name, script['EntryPoint'])) - - if plan_format_version in semantic_version.Spec('>=2.1.0'): - if script['Type'] not in ('Application', 'Chef', 'Puppet'): - raise exc.IncorrectFormat( - 2, 'Script has not a valid type {0}'.format( - script['Type'])) - if (script['Type'] == 'Application' and script['EntryPoint'] - not in plan.get('Files', {})): - raise exc.IncorrectFormat( - 2, 'Script {0} misses entry point {1}'.format( - name, script['EntryPoint'])) - elif (script['Type'] != 'Application' and - "::" not in script['EntryPoint']): - raise exc.IncorrectFormat( - 2, 'Wrong EntryPoint {0} for Puppet/Chef ' - 'executors. :: needed'.format(script['EntryPoint'])) - - for option in script['Options']: - if option in ('useBerkshelf', 'berksfilePath'): - if plan_format_version in semantic_version.Spec('<2.2.0'): - raise exc.IncorrectFormat( - 2, 'Script has an option {0} invalid ' - 'for version {1}'.format(option, - plan_format_version)) - elif script['Type'] != 'Chef': - raise exc.IncorrectFormat( - 2, 'Script has an option {0} invalid ' - 'for type {1}'.format(option, script['Type'])) - - for additional_file in script.get('Files', []): - mns_error = ('Script {0} misses file {1}'. - format(name, additional_file)) - if isinstance(additional_file, dict): - if (list(additional_file.keys())[0] not in - plan.get('Files', {}).keys()): - raise exc.IncorrectFormat(2, mns_error) - elif additional_file not in plan.get('Files', {}): - raise exc.IncorrectFormat(2, mns_error) - - def _validate_file(self, plan_file, key, format_version): - if format_version in semantic_version.Spec('>=2.0.0,<2.1.0'): - for plan in plan_file.keys(): - if plan in ('Type', 'URL'): - raise exc.IncorrectFormat( - 2, 'Download file is {0} not valid for this ' - 'version {1}'.format(key, format_version)) - - if 'Type' in plan_file: - for attr in ('Type', 'URL', 'Name'): - if attr not in plan_file: - raise exc.IncorrectFormat( - 2, - 'Incorrect {0} entry in file {1}'.format(attr, key)) - - elif 'Body' in plan_file: - for attr in ('BodyType', 'Body', 'Name'): - if attr not in plan_file: - raise exc.IncorrectFormat( - 2, 'Incorrect {0} entry in file {1}'.format( - attr, key)) - - if plan_file['BodyType'] not in ('Text', 'Base64'): - raise exc.IncorrectFormat( - 2, 'Incorrect BodyType in file {1}'.format(key)) - else: - raise exc.IncorrectFormat( - 2, 'Invalid file {0}: {1}'.format( - key, plan_file)) diff -Nru murano-agent-3.3.0/muranoagent/common/config.py murano-agent-3.4.0/muranoagent/common/config.py --- murano-agent-3.3.0/muranoagent/common/config.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/common/config.py 2018-02-05 21:04:08.000000000 +0000 @@ -24,18 +24,21 @@ CONF = cfg.CONF -storage_opt = [ +opts = [ cfg.StrOpt('storage', default='/var/murano/plans', - help='Directory to store execution plans') -] + help='Directory to store execution plans'), -message_routing_opt = [ - cfg.BoolOpt('enable_dynamic_result_queue', help='Enable taking dynamic ' - 'result queue from task field reply_to', - default=False) + cfg.StrOpt('engine_key', + help='Public key of murano-engine') ] +message_routing_opt = cfg.BoolOpt( + 'enable_dynamic_result_queue', + help='Enable taking dynamic result queue from task field reply_to', + default=False) + + rabbit_opts = [ cfg.HostAddressOpt('host', help='The RabbitMQ broker address which used for ' @@ -79,8 +82,8 @@ ] -CONF.register_cli_opts(storage_opt) -CONF.register_cli_opts(message_routing_opt) +CONF.register_opts(opts) +CONF.register_cli_opt(message_routing_opt) CONF.register_opts(rabbit_opts, group='rabbitmq') logging.register_options(CONF) diff -Nru murano-agent-3.3.0/muranoagent/common/messaging/message.py murano-agent-3.4.0/muranoagent/common/messaging/message.py --- murano-agent-3.3.0/muranoagent/common/messaging/message.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/common/messaging/message.py 2018-02-05 21:04:08.000000000 +0000 @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -import anyjson from oslo_log import log as logging LOG = logging.getLogger("murano-common.messaging") @@ -27,20 +26,17 @@ if message_handle: self.id = message_handle.properties.get('message_id') self._reply_to = message_handle.properties.get('reply_to') + self._signature = message_handle.headers.get('signature') else: self.id = None self._reply_to = None + self._signature = None - try: - if message_handle: - if isinstance(message_handle.body, bytes): - message_handle.body = message_handle.body.decode('utf-8') - self.body = anyjson.loads(message_handle.body) - else: - self.body = None - except ValueError: + if message_handle: + self.body = message_handle.body + + else: self.body = None - LOG.exception('Message is not in JSON format') @property def body(self): @@ -64,3 +60,7 @@ def ack(self): self._message_handle.ack() + + @property + def signature(self): + return self._signature diff -Nru murano-agent-3.3.0/muranoagent/execution_plan_queue.py murano-agent-3.4.0/muranoagent/execution_plan_queue.py --- murano-agent-3.3.0/muranoagent/execution_plan_queue.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/execution_plan_queue.py 2018-02-05 21:04:08.000000000 +0000 @@ -13,36 +13,58 @@ # See the License for the specific language governing permissions and # limitations under the License. +import base64 import json import os import shutil import time +from cryptography.hazmat import backends +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from oslo_log import log as logging from muranoagent import bunch from muranoagent.common import config CONF = config.CONF +LOG = logging.getLogger(__name__) class ExecutionPlanQueue(object): plan_filename = 'plan.json' result_filename = 'result.json' + stamp_filename = 'stamp' def __init__(self): self._plans_folder = os.path.join(CONF.storage, 'plans') if not os.path.exists(self._plans_folder): - os.makedirs(self._plans_folder) + os.makedirs(self._plans_folder, 0o700) + else: + try: + os.chmod(self._plans_folder, 0o700) + except OSError: + pass + self._key = None if not CONF.engine_key \ + else serialization.load_pem_public_key( + CONF.engine_key, backends.default_backend()) + self._load_stamp() - def put_execution_plan(self, execution_plan): + def put_execution_plan(self, execution_plan, signature, msg_id, reply_to): timestamp = str(int(time.time() * 10000)) # execution_plan['_timestamp'] = timestamp folder_path = os.path.join(self._plans_folder, timestamp) os.mkdir(folder_path) - file_path = os.path.join( + plan_file_path = os.path.join( folder_path, ExecutionPlanQueue.plan_filename) - with open(file_path, 'w') as out_file: - out_file.write(json.dumps(execution_plan)) + with open(plan_file_path, 'wb') as out_file: + out_file.write(json.dumps({ + 'Data': base64.b64encode(execution_plan), + 'Signature': base64.b64encode(signature or ''), + 'ID': msg_id, + 'ReplyTo': reply_to + })) def _get_first_timestamp(self, filename): def predicate(folder): @@ -65,11 +87,46 @@ return json.loads(json_file.read()), timestamp def get_execution_plan(self): - ep, timestamp = self._get_first_file(ExecutionPlanQueue.plan_filename) - if ep is None: - return None - ep['_timestamp'] = timestamp - return bunch.Bunch(ep) + while True: + ep_info, timestamp = self._get_first_file( + ExecutionPlanQueue.plan_filename) + if ep_info is None: + return None + + try: + data = base64.b64decode(ep_info['Data']) + if self._key: + signature = base64.b64decode(ep_info['Signature']) + self._verify_signature(data, signature) + + ep = json.loads(data) + if not isinstance(ep, dict): + raise ValueError('Message is not a document') + + stamp = ep.get('Stamp', -1) + if stamp >= 0: + if stamp <= self._last_stamp: + raise ValueError('Dropping old/duplicate message') + self._save_stamp(stamp) + + if 'ID' not in ep: + ep['ID'] = ep_info['ID'] + if 'ReplyTo' not in ep: + ep['ReplyTo'] = ep_info['ReplyTo'] + + ep['_timestamp'] = timestamp + return bunch.Bunch(ep) + except Exception as ex: + LOG.exception(ex) + self.remove(timestamp) + + def _verify_signature(self, data, signature): + if not signature: + raise ValueError("Required signature was not found") + self._key.verify( + signature, + CONF.rabbitmq.input_queue + data, + padding.PKCS1v15(), hashes.SHA256()) def put_execution_result(self, result, execution_plan): timestamp = execution_plan['_timestamp'] @@ -88,3 +145,19 @@ def get_execution_plan_result(self): return self._get_first_file( ExecutionPlanQueue.result_filename) + + def _load_stamp(self): + plan_file_path = os.path.join( + self._plans_folder, ExecutionPlanQueue.stamp_filename) + if os.path.exists(plan_file_path): + with open(plan_file_path) as f: + self._last_stamp = int(f.read()) + else: + self._last_stamp = 0 + + def _save_stamp(self, stamp): + plan_file_path = os.path.join( + self._plans_folder, ExecutionPlanQueue.stamp_filename) + with open(plan_file_path, 'w') as f: + f.write(str(stamp)) + self._last_stamp = stamp diff -Nru murano-agent-3.3.0/muranoagent/files_manager.py murano-agent-3.4.0/muranoagent/files_manager.py --- murano-agent-3.3.0/muranoagent/files_manager.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/files_manager.py 2018-02-05 21:04:08.000000000 +0000 @@ -40,7 +40,7 @@ CONF.storage, 'files', execution_plan.ID) if os.path.exists(self._cache_folder): self.clear() - os.makedirs(self._cache_folder) + os.makedirs(self._cache_folder, 0o700) def put_file(self, file_id, script): if type(file_id) is dict: diff -Nru murano-agent-3.3.0/muranoagent/opts.py murano-agent-3.4.0/muranoagent/opts.py --- murano-agent-3.3.0/muranoagent/opts.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/opts.py 2018-02-05 21:04:08.000000000 +0000 @@ -28,7 +28,7 @@ _opt_lists = [ ('rabbitmq', muranoagent.common.config.rabbit_opts), (None, build_list([ - muranoagent.common.config.storage_opt, + muranoagent.common.config.opts, ])) ] diff -Nru murano-agent-3.3.0/muranoagent/tests/unit/test_app.py murano-agent-3.4.0/muranoagent/tests/unit/test_app.py --- murano-agent-3.3.0/muranoagent/tests/unit/test_app.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/tests/unit/test_app.py 2018-02-05 21:04:08.000000000 +0000 @@ -16,8 +16,6 @@ import mock import ssl as ssl_module -from oslo_service import sslutils - from muranoagent import app from muranoagent import bunch from muranoagent.common import config as cfg @@ -25,20 +23,26 @@ from muranoagent import exceptions as exc from muranoagent.tests.unit import base from muranoagent.tests.unit import execution_plan as ep +from muranoagent import validation CONF = cfg.CONF class TestApp(base.MuranoAgentTestCase, fixtures.FunctionFixture): + @mock.patch('os.chmod') @mock.patch('os.path.exists') - def setUp(self, mock_path): + def setUp(self, mock_path, mock_chmod): super(TestApp, self).setUp() - mock_path.return_value = True + mock_path.side_effect = self._exists self.agent = app.MuranoAgent() CONF.set_override('storage', 'cache') self.addCleanup(CONF.clear_override, 'storage') + @staticmethod + def _exists(path): + return 'stamp' not in path + def test_verify_execution_plan_downloable(self): template = self.useFixture(ep.ExPlanDownloable()).execution_plan self.agent._verify_plan(template) @@ -49,13 +53,13 @@ FormatVersion='0.0.0', ) self.assertRaises(exc.IncorrectFormat, - self.agent._verify_plan, template) + validation.validate_plan, template) def test_verify_over_max_execution_plan(self): template = self.useFixture(ep.ExPlanApplication()).execution_plan template['FormatVersion'] = '1000.0.0' self.assertRaises(exc.IncorrectFormat, - self.agent._verify_plan, template) + validation.validate_plan, template) def test_verify_execution_application(self): template = self.useFixture(ep.ExPlanApplication()).execution_plan @@ -70,12 +74,12 @@ } template['FormatVersion'] = '2.0.0' self.assertRaises(exc.IncorrectFormat, - self.agent._verify_plan, template) + validation.validate_plan, template) def test_verify_execution_plan_no_files(self): template = self.useFixture(ep.ExPlanDownloableNoFiles()).execution_plan self.assertRaises(exc.IncorrectFormat, - self.agent._verify_plan, template) + validation.validate_plan, template) def test_verify_execution_plan_berkshelf(self): template = self.useFixture(ep.ExPlanBerkshelf()).execution_plan @@ -84,7 +88,7 @@ def test_verify_execution_plan_berkshelf_wrong_version(self): template = self.useFixture(ep.ExPlanBerkWrongVersion()).execution_plan self.assertRaises(exc.IncorrectFormat, - self.agent._verify_plan, template) + validation.validate_plan, template) @mock.patch.object(mqclient, 'random', autospec=True) @mock.patch.object(mqclient, 'kombu', autospec=True) diff -Nru murano-agent-3.3.0/muranoagent/validation.py murano-agent-3.4.0/muranoagent/validation.py --- murano-agent-3.3.0/muranoagent/validation.py 1970-01-01 00:00:00.000000000 +0000 +++ murano-agent-3.4.0/muranoagent/validation.py 2018-02-05 21:04:08.000000000 +0000 @@ -0,0 +1,140 @@ +# Copyright (c) 2017 Mirantis Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import semantic_version +import six + +from muranoagent import exceptions as exc + + +max_format_version = semantic_version.Spec('<=2.2.0') + + +def validate_plan(plan): + plan_format_version = semantic_version.Version( + plan.get('FormatVersion', '1.0.0')) + + if plan_format_version not in max_format_version: + # NOTE(kazitsev) this is Version in Spec not str in str + raise exc.IncorrectFormat( + 9, + "Unsupported format version {0} " + "(I support versions {1})".format( + plan_format_version, max_format_version)) + + for attr in ('Scripts', 'Files'): + if attr not in plan: + raise exc.IncorrectFormat( + 2, '{0} is not in the execution plan'.format(attr)) + + for attr in ('Scripts', 'Files', 'Options'): + if attr in plan and not isinstance( + plan[attr], dict): + raise exc.IncorrectFormat( + 2, '{0} is not a dictionary'.format(attr)) + + for name, script in plan.get('Scripts', {}).items(): + _validate_script(name, script, plan_format_version, plan) + + for key, plan_file in plan.get('Files', {}).items(): + _validate_file(plan_file, key, plan_format_version) + + +def _validate_script(name, script, plan_format_version, plan): + for attr in ('Type', 'EntryPoint'): + if attr not in script or not isinstance(script[attr], + six.string_types): + raise exc.IncorrectFormat( + 2, 'Incorrect {0} entry in script {1}'.format( + attr, name)) + + if plan_format_version in semantic_version.Spec('>=2.0.0,<2.1.0'): + if script['Type'] != 'Application': + raise exc.IncorrectFormat( + 2, 'Type {0} is not valid for format {1}'.format( + script['Type'], plan_format_version)) + if script['EntryPoint'] not in plan.get('Files', {}): + raise exc.IncorrectFormat( + 2, 'Script {0} misses entry point {1}'.format( + name, script['EntryPoint'])) + + if plan_format_version in semantic_version.Spec('>=2.1.0'): + if script['Type'] not in ('Application', 'Chef', 'Puppet'): + raise exc.IncorrectFormat( + 2, 'Script has not a valid type {0}'.format( + script['Type'])) + if (script['Type'] == 'Application' and script['EntryPoint'] + not in plan.get('Files', {})): + raise exc.IncorrectFormat( + 2, 'Script {0} misses entry point {1}'.format( + name, script['EntryPoint'])) + elif (script['Type'] != 'Application' and + "::" not in script['EntryPoint']): + raise exc.IncorrectFormat( + 2, 'Wrong EntryPoint {0} for Puppet/Chef ' + 'executors. :: needed'.format(script['EntryPoint'])) + + for option in script['Options']: + if option in ('useBerkshelf', 'berksfilePath'): + if plan_format_version in semantic_version.Spec('<2.2.0'): + raise exc.IncorrectFormat( + 2, 'Script has an option {0} invalid ' + 'for version {1}'.format(option, + plan_format_version)) + elif script['Type'] != 'Chef': + raise exc.IncorrectFormat( + 2, 'Script has an option {0} invalid ' + 'for type {1}'.format(option, script['Type'])) + + for additional_file in script.get('Files', []): + mns_error = ('Script {0} misses file {1}'.format( + name, additional_file)) + if isinstance(additional_file, dict): + if (list(additional_file.keys())[0] not in + plan.get('Files', {}).keys()): + raise exc.IncorrectFormat(2, mns_error) + elif additional_file not in plan.get('Files', {}): + raise exc.IncorrectFormat(2, mns_error) + + +def _validate_file(plan_file, key, format_version): + if format_version in semantic_version.Spec('>=2.0.0,<2.1.0'): + for plan in plan_file.keys(): + if plan in ('Type', 'URL'): + raise exc.IncorrectFormat( + 2, 'Download file is {0} not valid for this ' + 'version {1}'.format(key, format_version)) + + if 'Type' in plan_file: + for attr in ('Type', 'URL', 'Name'): + if attr not in plan_file: + raise exc.IncorrectFormat( + 2, + 'Incorrect {0} entry in file {1}'.format(attr, key)) + + elif 'Body' in plan_file: + for attr in ('BodyType', 'Body', 'Name'): + if attr not in plan_file: + raise exc.IncorrectFormat( + 2, 'Incorrect {0} entry in file {1}'.format( + attr, key)) + + if plan_file['BodyType'] not in ('Text', 'Base64'): + raise exc.IncorrectFormat( + 2, 'Incorrect BodyType in file {0}'.format(key)) + else: + raise exc.IncorrectFormat( + 2, 'Invalid file {0}: {1}'.format( + key, plan_file)) diff -Nru murano-agent-3.3.0/murano_agent.egg-info/pbr.json murano-agent-3.4.0/murano_agent.egg-info/pbr.json --- murano-agent-3.3.0/murano_agent.egg-info/pbr.json 2017-08-10 15:59:41.000000000 +0000 +++ murano-agent-3.4.0/murano_agent.egg-info/pbr.json 2018-02-05 21:07:00.000000000 +0000 @@ -1 +1 @@ -{"git_version": "0430611", "is_release": true} \ No newline at end of file +{"git_version": "4cce39a", "is_release": true} \ No newline at end of file diff -Nru murano-agent-3.3.0/murano_agent.egg-info/PKG-INFO murano-agent-3.4.0/murano_agent.egg-info/PKG-INFO --- murano-agent-3.3.0/murano_agent.egg-info/PKG-INFO 2017-08-10 15:59:41.000000000 +0000 +++ murano-agent-3.4.0/murano_agent.egg-info/PKG-INFO 2018-02-05 21:07:00.000000000 +0000 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: murano-agent -Version: 3.3.0 +Version: 3.4.0 Summary: Python Murano Agent Home-page: https://docs.openstack.org/murano/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache License, Version 2.0 +Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== diff -Nru murano-agent-3.3.0/murano_agent.egg-info/requires.txt murano-agent-3.4.0/murano_agent.egg-info/requires.txt --- murano-agent-3.3.0/murano_agent.egg-info/requires.txt 2017-08-10 15:59:41.000000000 +0000 +++ murano-agent-3.4.0/murano_agent.egg-info/requires.txt 2018-02-05 21:07:00.000000000 +0000 @@ -3,11 +3,12 @@ eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 GitPython>=1.0.1 kombu!=4.0.2,>=4.0.0 -oslo.config!=4.3.0,!=4.4.0,>=4.0.0 -oslo.log>=3.22.0 -oslo.service>=1.10.0 -oslo.utils>=3.20.0 -PyYAML>=3.10.0 -six>=1.9.0 +oslo.config>=5.1.0 +oslo.log>=3.36.0 +oslo.service!=1.28.1,>=1.24.0 +oslo.utils>=3.33.0 +PyYAML>=3.10 +six>=1.10.0 semantic-version>=2.3.1 requests>=2.14.2 +cryptography!=2.0,>=1.9 diff -Nru murano-agent-3.3.0/murano_agent.egg-info/SOURCES.txt murano-agent-3.4.0/murano_agent.egg-info/SOURCES.txt --- murano-agent-3.3.0/murano_agent.egg-info/SOURCES.txt 2017-08-10 15:59:42.000000000 +0000 +++ murano-agent-3.4.0/murano_agent.egg-info/SOURCES.txt 2018-02-05 21:07:00.000000000 +0000 @@ -40,12 +40,13 @@ contrib/windows-agent/ExecutionPlanGenerator/Properties/AssemblyInfo.cs contrib/windows-agent/WindowsAgent/App.config contrib/windows-agent/WindowsAgent/ExecutionPlan.cs -contrib/windows-agent/WindowsAgent/MqMessage.cs +contrib/windows-agent/WindowsAgent/Message.cs +contrib/windows-agent/WindowsAgent/MessageSource.cs contrib/windows-agent/WindowsAgent/PlanExecutor.cs contrib/windows-agent/WindowsAgent/Program.cs -contrib/windows-agent/WindowsAgent/RabbitMqClient.cs contrib/windows-agent/WindowsAgent/SampleExecutionPlan.json contrib/windows-agent/WindowsAgent/ServiceManager.cs +contrib/windows-agent/WindowsAgent/SignatureVerifier.cs contrib/windows-agent/WindowsAgent/WindowsAgent.csproj contrib/windows-agent/WindowsAgent/WindowsService.cs contrib/windows-agent/WindowsAgent/WindowsServiceInstaller.cs @@ -73,6 +74,7 @@ muranoagent/files_manager.py muranoagent/opts.py muranoagent/script_runner.py +muranoagent/validation.py muranoagent/version.py muranoagent/win32.py muranoagent/cmd/__init__.py @@ -109,6 +111,7 @@ releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst +releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder diff -Nru murano-agent-3.3.0/PKG-INFO murano-agent-3.4.0/PKG-INFO --- murano-agent-3.3.0/PKG-INFO 2017-08-10 15:59:42.000000000 +0000 +++ murano-agent-3.4.0/PKG-INFO 2018-02-05 21:07:00.000000000 +0000 @@ -1,11 +1,12 @@ Metadata-Version: 1.1 Name: murano-agent -Version: 3.3.0 +Version: 3.4.0 Summary: Python Murano Agent Home-page: https://docs.openstack.org/murano/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: Apache License, Version 2.0 +Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== diff -Nru murano-agent-3.3.0/releasenotes/source/conf.py murano-agent-3.4.0/releasenotes/source/conf.py --- murano-agent-3.3.0/releasenotes/source/conf.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/releasenotes/source/conf.py 2018-02-05 21:04:08.000000000 +0000 @@ -58,16 +58,12 @@ project = u'Murano Agent Release Notes' copyright = u'2015, Murano Developers' -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -from muranoagent.version import version_info as murano_version +# Release notes do not need a version number in the title, they +# cover multiple releases. # The full version, including alpha/beta/rc tags. -release = murano_version.version_string_with_vcs() +release = '' # The short X.Y version. -version = murano_version.canonical_version_string() +version = '' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff -Nru murano-agent-3.3.0/releasenotes/source/index.rst murano-agent-3.4.0/releasenotes/source/index.rst --- murano-agent-3.3.0/releasenotes/source/index.rst 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/releasenotes/source/index.rst 2018-02-05 21:04:08.000000000 +0000 @@ -6,6 +6,7 @@ :maxdepth: 2 unreleased + pike ocata newton mitaka diff -Nru murano-agent-3.3.0/releasenotes/source/pike.rst murano-agent-3.4.0/releasenotes/source/pike.rst --- murano-agent-3.3.0/releasenotes/source/pike.rst 1970-01-01 00:00:00.000000000 +0000 +++ murano-agent-3.4.0/releasenotes/source/pike.rst 2018-02-05 21:04:08.000000000 +0000 @@ -0,0 +1,6 @@ +=================================== + Pike Series Release Notes +=================================== + +.. release-notes:: + :branch: stable/pike diff -Nru murano-agent-3.3.0/requirements.txt murano-agent-3.4.0/requirements.txt --- murano-agent-3.3.0/requirements.txt 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/requirements.txt 2018-02-05 21:04:08.000000000 +0000 @@ -6,11 +6,12 @@ eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT GitPython>=1.0.1 # BSD License (3 clause) kombu!=4.0.2,>=4.0.0 # BSD -oslo.config!=4.3.0,!=4.4.0,>=4.0.0 # Apache-2.0 -oslo.log>=3.22.0 # Apache-2.0 -oslo.service>=1.10.0 # Apache-2.0 -oslo.utils>=3.20.0 # Apache-2.0 -PyYAML>=3.10.0 # MIT -six>=1.9.0 # MIT +oslo.config>=5.1.0 # Apache-2.0 +oslo.log>=3.36.0 # Apache-2.0 +oslo.service!=1.28.1,>=1.24.0 # Apache-2.0 +oslo.utils>=3.33.0 # Apache-2.0 +PyYAML>=3.10 # MIT +six>=1.10.0 # MIT semantic-version>=2.3.1 # BSD requests>=2.14.2 # Apache-2.0 +cryptography!=2.0,>=1.9 # BSD/Apache-2.0 diff -Nru murano-agent-3.3.0/test-requirements.txt murano-agent-3.4.0/test-requirements.txt --- murano-agent-3.3.0/test-requirements.txt 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/test-requirements.txt 2018-02-05 21:04:08.000000000 +0000 @@ -2,14 +2,14 @@ # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 -unittest2 # BSD +unittest2>=1.1.0 # BSD coverage!=4.4,>=4.0 # Apache-2.0 -mock>=2.0 # BSD -testtools>=1.4.0 # MIT +mock>=2.0.0 # BSD +testtools>=2.2.0 # MIT testrepository>=0.0.18 # Apache-2.0/BSD -oslotest>=1.10.0 # Apache-2.0 +oslotest>=3.2.0 # Apache-2.0 # doc build requirements -openstackdocstheme>=1.16.0 # Apache-2.0 -sphinx>=1.6.2 # BSD -reno!=2.3.1,>=1.8.0 # Apache-2.0 +openstackdocstheme>=1.18.1 # Apache-2.0 +sphinx!=1.6.6,>=1.6.2 # BSD +reno>=2.5.0 # Apache-2.0 diff -Nru murano-agent-3.3.0/tools/install_venv.py murano-agent-3.4.0/tools/install_venv.py --- murano-agent-3.3.0/tools/install_venv.py 2017-08-10 15:55:54.000000000 +0000 +++ murano-agent-3.4.0/tools/install_venv.py 2018-02-05 21:04:08.000000000 +0000 @@ -34,7 +34,7 @@ To activate the Murano agent virtualenv for the extent of your current shell session you can run: - $ source %s/bin/activate + $ . %s/bin/activate Or, if you prefer, you can run commands in the virtualenv on a case by case basis by running: