Commit e0720682 authored by Mark van Renswoude's avatar Mark van Renswoude

Implemented removing of exchanges and queues

Added unit tests
parent 4b04dfaf
*.user
*.suo
<?xml version="1.0" encoding="UTF-8"?>
<Topology xmlns="http://schema.x2software.net/RabbitMetaQueue" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schema.x2software.net/RabbitMetaQueue ..\Schema\Topology.xsd">
<Exchanges>
<Exchange name="RMetaQ.test1" type="Topic" />
<Exchange name="RMetaQ.test2" type="Topic" />
</Exchanges>
<Queues>
<Queue name="RMetaQ.queue1">
<Arguments>
<Argument name="x-dead-letter-exchange">metatest2</Argument>
</Arguments>
<Bindings>
<Binding exchange="RMetaQ.test1" routingKey="some.routing.key" />
</Bindings>
</Queue>
<Queue name="RMetaQ.deadletter">
<Bindings>
<Binding exchange="RMetaQ.test2" routingKey="#" />
</Bindings>
</Queue>
</Queues>
</Topology>
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using RabbitMetaQueue.Domain;
using RabbitMetaQueue.Model;
using RabbitMetaQueue.Tests.Mock;
namespace RabbitMetaQueue.Tests.Infrastructure
{
[TestFixture]
public class TopologyComparatorTest
{
private Topology definedTopology;
private Topology existingTopology;
[SetUp]
public void SetUp()
{
definedTopology = new Topology();
existingTopology = new Topology();
}
[Test]
public void CreateExchange()
{
definedTopology.AddExchange1();
TestCompare(new List<string>
{
{ "ce:e1:Topic:True" }
});
}
[Test]
public void CreateQueue()
{
definedTopology.AddQueue1();
TestCompare(new List<string>
{
{ "cq:q1:True" }
});
}
[Test]
public void CreateBinding()
{
definedTopology.AddExchange1();
definedTopology
.AddQueue1()
.BindToExchange1("mock.key");
TestCompare(new List<string>
{
{ "ce:e1:Topic:True" },
{ "cq:q1:True" },
{ "cb:q1:e1:mock.key" }
});
}
[Test]
public void NothingChanged()
{
definedTopology.AddExchange1();
definedTopology.AddQueue1();
existingTopology.AddExchange1();
existingTopology.AddQueue1();
TestCompare(new List<string>());
}
[Test]
public void RemoveExchange()
{
existingTopology.AddExchange1();
TestCompare(new List<string>()
{
{ "de:e1" }
});
}
[Test]
public void RemoveQueue()
{
existingTopology.AddQueue1();
TestCompare(new List<string>()
{
{ "dq:q1" }
});
}
private void TestCompare(List<string> expectedActions)
{
var writer = new MockTopologyWriter();
var comparator = new TopologyComparator(writer)
{
AllowDelete = true,
AllowRecreate = true,
AllowUnbind = true
};
comparator.Compare(existingTopology, definedTopology);
var hasUnexpectedActions = false;
var results = new StringBuilder();
results.AppendLine("Expected actions not executed:");
foreach (var line in expectedActions.Except(writer.Actions))
{
results.AppendLine(" " + line);
hasUnexpectedActions = true;
}
results.AppendLine("Executed action not expected:");
foreach (var line in writer.Actions.Except(expectedActions))
{
results.AppendLine(" " + line);
hasUnexpectedActions = true;
}
if (hasUnexpectedActions)
{
results.AppendLine("Full log:");
foreach (var line in writer.Actions)
results.AppendLine(" " + line);
Assert.Fail(results.ToString());
}
}
}
class MockTopologyWriter : ITopologyWriter
{
public List<string> Actions { get; private set; }
public MockTopologyWriter()
{
Actions = new List<string>();
}
public void CreateExchange(Exchange exchange)
{
Actions.Add("ce:" + exchange.Name + ":" + exchange.ExchangeType + ":" + exchange.Durable);
}
public void DeleteExchange(Exchange exchange)
{
Actions.Add("de:" + exchange.Name);
}
public void CreateQueue(Queue queue)
{
Actions.Add("cq:" + queue.Name + ":" + queue.Durable);
}
public void DeleteQueue(Queue queue)
{
Actions.Add("dq:" + queue.Name);
}
public void CreateBinding(Queue queue, Binding binding)
{
Actions.Add("cb:" + queue.Name + ":" + binding.Exchange + ":" + binding.RoutingKey);
}
public void DeleteBinding(Queue queue, Binding binding)
{
Actions.Add("db:" + queue.Name + ":" + binding.Exchange + ":" + binding.RoutingKey);
}
}
}
using System.IO;
using NUnit.Framework;
using RabbitMetaQueue.Infrastructure;
using RabbitMetaQueue.Model;
namespace RabbitMetaQueue.Tests.Infrastructure
{
[TestFixture]
public class XmlTopologyReaderTest
{
[Test]
public void BasicDefinition()
{
var topology = Parse("BasicDefinition.xml");
Assert.AreEqual(2, topology.Exchanges.Count, "Exchange count");
TestExchange(topology.Exchanges[0], "RMetaQ.test1", ExchangeType.Topic);
TestExchange(topology.Exchanges[1], "RMetaQ.test2", ExchangeType.Topic);
Assert.AreEqual(2, topology.Queues.Count, "Queue count");
TestQueue(topology.Queues[0], "RMetaQ.queue1");
TestQueue(topology.Queues[1], "RMetaQ.deadletter");
var arguments = topology.Queues[0].Arguments;
Assert.AreEqual(1, arguments.Count, "Queue Argument Count");
Assert.IsTrue(arguments.ContainsKey("x-dead-letter-exchange"), "Dead Letter Exchange argument");
Assert.AreEqual("metatest2", arguments["x-dead-letter-exchange"], "Dead Letter Exchange value");
}
private static void TestExchange(Exchange exchange, string expectedName, ExchangeType expectedType, bool expectedDurable = true)
{
Assert.AreEqual(expectedName, exchange.Name, "Exchange Name");
Assert.AreEqual(expectedType, exchange.ExchangeType, "Exchange Type");
Assert.AreEqual(expectedDurable, exchange.Durable, "Exchange Durable");
}
private static void TestQueue(Queue queue, string expectedName, bool expectedDurable = true)
{
Assert.AreEqual(expectedName, queue.Name, "Queue Name");
Assert.AreEqual(expectedDurable, queue.Durable, "Queue Durable");
}
private Topology Parse(string fileName)
{
using (var stream = GetType().Assembly.GetManifestResourceStream("RabbitMetaQueue.Tests.Data." + fileName))
{
return new XmlTopologyReader().Parse(stream);
}
}
}
}
using RabbitMetaQueue.Model;
namespace RabbitMetaQueue.Tests.Mock
{
public static class TopologyMockExtensions
{
public static Exchange AddExchange1(this Topology topology)
{
var exchange = new Exchange
{
Name = "e1",
ExchangeType = ExchangeType.Topic
};
topology.Exchanges.Add(exchange);
return exchange;
}
public static Queue AddQueue1(this Topology topology)
{
var queue = new Queue
{
Name = "q1"
};
topology.Queues.Add(queue);
return queue;
}
public static Binding BindToExchange1(this Queue queue, string routingKey)
{
var binding = new Binding()
{
Exchange = "e1",
RoutingKey = routingKey
};
queue.Bindings.Add(binding);
return binding;
}
}
}
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("RabbitMetaQueue.Tests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("RabbitMetaQueue.Tests")]
[assembly: AssemblyCopyright("")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("19465285-3d93-450d-95d8-9953194cc919")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
<?xml version="1.0" encoding="utf-8"?>
<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>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{233DB210-B52D-47B9-87B9-7156CD16045D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>RabbitMetaQueue.Tests</RootNamespace>
<AssemblyName>RabbitMetaQueue.Tests</AssemblyName>
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<SolutionDir Condition="$(SolutionDir) == '' Or $(SolutionDir) == '*Undefined*'">..\</SolutionDir>
<RestorePackages>true</RestorePackages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
<Private>True</Private>
<HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.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>
<ItemGroup>
<Compile Include="Infrastructure\TopologyComparatorTest.cs" />
<Compile Include="Infrastructure\XmlTopologyReaderTest.cs" />
<Compile Include="Mock\TopologyMockExtensions.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RabbitMetaQueue\RabbitMetaQueue.csproj">
<Project>{a8a7db04-f231-4833-998a-a4ceed2bdaab}</Project>
<Name>RabbitMetaQueue</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<EmbeddedResource Include="Data\BasicDefinition.xml" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)\.nuget\NuGet.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)\.nuget\NuGet.targets'))" />
</Target>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="NUnit" version="2.6.4" targetFramework="net45" />
</packages>
\ No newline at end of file
......@@ -5,6 +5,8 @@ VisualStudioVersion = 12.0.31101.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RabbitMetaQueue", "RabbitMetaQueue\RabbitMetaQueue.csproj", "{A8A7DB04-F231-4833-998A-A4CEED2BDAAB}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RabbitMetaQueue.Tests", "RabbitMetaQueue.Tests\RabbitMetaQueue.Tests.csproj", "{233DB210-B52D-47B9-87B9-7156CD16045D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
......@@ -15,6 +17,10 @@ Global
{A8A7DB04-F231-4833-998A-A4CEED2BDAAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8A7DB04-F231-4833-998A-A4CEED2BDAAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A8A7DB04-F231-4833-998A-A4CEED2BDAAB}.Release|Any CPU.Build.0 = Release|Any CPU
{233DB210-B52D-47B9-87B9-7156CD16045D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{233DB210-B52D-47B9-87B9-7156CD16045D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{233DB210-B52D-47B9-87B9-7156CD16045D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{233DB210-B52D-47B9-87B9-7156CD16045D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
......
/obj/
/bin/
/packages/
*.suo
*.user
......@@ -2,7 +2,7 @@
namespace RabbitMetaQueue.Domain
{
interface ITopologyWriter
public interface ITopologyWriter
{
void CreateExchange(Exchange exchange);
void DeleteExchange(Exchange exchange);
......
......@@ -2,17 +2,18 @@
using System.Collections.Generic;
using System.Linq;
using RabbitMetaQueue.Model;
using RabbitMQ.Client.Framing.Impl;
namespace RabbitMetaQueue.Domain
{
class TopologyComparator
public class TopologyComparator
{
public bool AllowDelete { get; set; }
public bool AllowRecreate { get; set; }
public bool AllowUnbind { get; set; }
private readonly ITopologyWriter topologyWriter;
private List<string> volatileExchanges = new List<string>();
private readonly List<string> volatileExchanges = new List<string>();
public TopologyComparator()
......@@ -43,7 +44,9 @@ namespace RabbitMetaQueue.Domain
CreateExchange(exchange);
}
// ToDo removed exchanges
// Removed exchanges
foreach (var exchange in existingTopology.Exchanges.Except(definedTopology.Exchanges, new ExchangeComparer()))
DeleteExchange(exchange);
// Added or updated queues
foreach (var queue in definedTopology.Queues)
......@@ -55,7 +58,9 @@ namespace RabbitMetaQueue.Domain
CreateQueue(queue);
}
// ToDo removed queues
// Removed queues
foreach (var queue in existingTopology.Queues.Except(definedTopology.Queues, new QueueComparer()))
DeleteQueue(queue);
}
private void CreateExchange(Exchange exchange)
......@@ -77,6 +82,13 @@ namespace RabbitMetaQueue.Domain
}
private void DeleteExchange(Exchange exchange)
{
if (AllowDelete)
topologyWriter.DeleteExchange(exchange);
}
private void CreateQueue(Queue queue)
{
topologyWriter.CreateQueue(queue);
......@@ -110,6 +122,13 @@ namespace RabbitMetaQueue.Domain
}
private void DeleteQueue(Queue queue)
{
if (AllowDelete)
topologyWriter.DeleteQueue(queue);
}
private void CreateBinding(Queue queue, Binding binding)
{
topologyWriter.CreateBinding(queue, binding);
......@@ -160,4 +179,32 @@ namespace RabbitMetaQueue.Domain
value.Equals(a.Value, StringComparison.InvariantCulture));
}
}
class ExchangeComparer : IEqualityComparer<Exchange>
{
public bool Equals(Exchange x, Exchange y)
{
return (String.Compare(x.Name, y.Name, StringComparison.Ordinal) == 0);
}
public int GetHashCode(Exchange obj)
{
return obj.Name.GetHashCode();
}
}
class QueueComparer : IEqualityComparer<Queue>
{
public bool Equals(Queue x, Queue y)
{
return (String.Compare(x.Name, y.Name, StringComparison.Ordinal) == 0);
}
public int GetHashCode(Queue obj)
{
return obj.Name.GetHashCode();
}
}
}
......@@ -5,7 +5,7 @@ using RabbitMetaQueue.Model;
namespace RabbitMetaQueue.Infrastructure
{
// ToDo arguments
class ConsoleTopologyWriter : ITopologyWriter
public class ConsoleTopologyWriter : ITopologyWriter
{
public void CreateExchange(Exchange exchange)
{
......
......@@ -4,7 +4,7 @@ using RabbitMetaQueue.Model;
namespace RabbitMetaQueue.Infrastructure
{
class MulticastTopologyWriter : ITopologyWriter
public class MulticastTopologyWriter : ITopologyWriter
{
private readonly List<ITopologyWriter> topologyWriters = new List<ITopologyWriter>();
......
......@@ -6,7 +6,7 @@ using RabbitMetaQueue.Model;
namespace RabbitMetaQueue.Infrastructure
{
class RabbitMQTopologyReader
public class RabbitMQTopologyReader
{
private static readonly Dictionary<string, Model.ExchangeType> ExchangeTypeMap = new Dictionary<string, Model.ExchangeType>
{
......
......@@ -5,7 +5,7 @@ using RabbitMetaQueue.Domain;
namespace RabbitMetaQueue.Infrastructure
{
class RabbitMQTopologyWriter : ITopologyWriter
public class RabbitMQTopologyWriter : ITopologyWriter
{
private readonly IManagementClient client;
private readonly Vhost virtualHost;
......
......@@ -4,18 +4,21 @@ using System.Xml.Serialization;
namespace RabbitMetaQueue.Infrastructure
{
class XmlTopologyReader
public class XmlTopologyReader
{
public Model.Topology Parse(string filename)
{
Schema.Topology definition;
using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read))
{
var serializer = new XmlSerializer(typeof(Schema.Topology));
definition = (Schema.Topology)serializer.Deserialize(stream);
return Parse(stream);
}
}
public Model.Topology Parse(Stream stream)
{
var serializer = new XmlSerializer(typeof(Schema.Topology));
var definition = (Schema.Topology)serializer.Deserialize(stream);
var model = new Model.Topology();
if (definition.Exchanges != null)
......
......@@ -2,5 +2,5 @@
namespace RabbitMetaQueue.Model
{
class Arguments : Dictionary<string, string> { }
public class Arguments : Dictionary<string, string> { }
}
namespace RabbitMetaQueue.Model
{
class Binding
public class Binding
{
public string Exchange { get; set; }
public string RoutingKey { get; set; }
......
namespace RabbitMetaQueue.Model
{
class ConnectionParams
public class ConnectionParams
{
public string Host { get; set; }
public string VirtualHost { get; set; }
......
......@@ -8,7 +8,7 @@
Headers
}
class Exchange
public class Exchange
{
public string Name { get; set; }
public ExchangeType ExchangeType { get; set; }
......@@ -18,6 +18,8 @@
public Exchange()
{
ExchangeType = ExchangeType.Direct;
Durable = true;
Arguments = new Arguments();
}
}
......
......@@ -2,7 +2,7 @@
namespace RabbitMetaQueue.Model
{
class Queue
public class Queue
{
public string Name { get; set; }
public bool Durable { get; set; }
......@@ -11,6 +11,7 @@ namespace RabbitMetaQueue.Model
public Queue()
{
Durable = true;
Arguments = new Arguments();
Bindings = new List<Binding>();
}
......