Skip to content

Commit fcd860f

Browse files
committed
✨ Add IBM MQ transport support
Integrate NServiceBus.Transport.IBMMQ 1.0.0-alpha.7 into ServiceControl with full transport plugin, monitoring, custom checks, CI, and container support.
1 parent e3649dc commit fcd860f

24 files changed

+715
-7
lines changed

.github/workflows/ci.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ jobs:
1818
strategy:
1919
matrix:
2020
os: [windows-latest, ubuntu-latest]
21-
test-category: [ Default, SqlServer, AzureServiceBus, RabbitMQ, AzureStorageQueues, MSMQ, SQS, PrimaryRavenAcceptance, PrimaryRavenPersistence, PostgreSQL ]
21+
test-category: [ Default, SqlServer, AzureServiceBus, RabbitMQ, AzureStorageQueues, MSMQ, SQS, PrimaryRavenAcceptance, PrimaryRavenPersistence, PostgreSQL, IBMMQ ]
2222
include:
2323
- os: windows-latest
2424
os-name: Windows
@@ -27,6 +27,8 @@ jobs:
2727
exclude:
2828
- os: ubuntu-latest
2929
test-category: MSMQ
30+
- os: windows-latest
31+
test-category: IBMMQ
3032
fail-fast: false
3133
steps:
3234
- name: Check for secrets
@@ -103,6 +105,18 @@ jobs:
103105
connection-string-name: ServiceControl_TransportTests_ASQ_ConnectionString
104106
azure-credentials: ${{ secrets.AZURE_ACI_CREDENTIALS }}
105107
tag: ServiceControl
108+
- name: Setup IBM MQ
109+
if: matrix.test-category == 'IBMMQ'
110+
run: |
111+
docker run --name ibmmq -d -p 1414:1414 -p 9443:9443 `
112+
--health-cmd "dspmq" --health-interval 10s --health-timeout 5s --health-retries 10 --health-start-period 30s `
113+
-e LICENSE=accept -e MQ_QMGR_NAME=QM1 -e MQ_ADMIN_PASSWORD=passw0rd `
114+
icr.io/ibm-messaging/mq:latest
115+
# Wait for container health check to pass
116+
while ((docker inspect --format '{{.State.Health.Status}}' ibmmq) -ne 'healthy') {
117+
Start-Sleep -Seconds 2
118+
}
119+
echo "ServiceControl_TransportTests_IBMMQ_ConnectionString=mq://admin:passw0rd@localhost:1414/QM1?channel=DEV.ADMIN.SVRCONN&topicprefix=DEV" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append
106120
- name: Setup SQS environment variables
107121
if: matrix.test-category == 'SQS'
108122
run: |

nuget.config

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,5 @@
1111
<packageSource key="particular packages">
1212
<package pattern="*" />
1313
</packageSource>
14-
<packageSource key="local packages">
15-
<package pattern="*" />
16-
</packageSource>
1714
</packageSourceMapping>
1815
</configuration>

src/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
<PackageVersion Include="NServiceBus.Transport.Msmq.Sources" Version="4.0.0" />
5353
<PackageVersion Include="NServiceBus.Transport.PostgreSql" Version="9.0.0" />
5454
<PackageVersion Include="NServiceBus.Transport.SqlServer" Version="9.0.0" />
55+
<PackageVersion Include="NServiceBus.Transport.IBMMQ" Version="1.0.0-alpha.7" />
5556
<PackageVersion Include="NuGet.Versioning" Version="7.3.0" />
5657
<PackageVersion Include="NUnit" Version="4.5.1" />
5758
<PackageVersion Include="NUnit.Analyzers" Version="4.12.0" />

src/ProjectReferences.Transports.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<ItemGroup Label="Transports">
44
<ProjectReference Include="..\ServiceControl.Transports.ASBS\ServiceControl.Transports.ASBS.csproj" ReferenceOutputAssembly="false" Private="false" />
55
<ProjectReference Include="..\ServiceControl.Transports.ASQ\ServiceControl.Transports.ASQ.csproj" ReferenceOutputAssembly="false" Private="false" />
6+
<ProjectReference Include="..\ServiceControl.Transports.IBMMQ\ServiceControl.Transports.IBMMQ.csproj" ReferenceOutputAssembly="false" Private="false" />
67
<ProjectReference Include="..\ServiceControl.Transports.Learning\ServiceControl.Transports.Learning.csproj" ReferenceOutputAssembly="false" Private="false" />
78
<ProjectReference Include="..\ServiceControl.Transports.Msmq\ServiceControl.Transports.Msmq.csproj" ReferenceOutputAssembly="false" Private="false" SkipGetTargetFrameworkProperties="true" UndefineProperties="TargetFramework" />
89
<ProjectReference Include="..\ServiceControl.Transports.PostgreSql\ServiceControl.Transports.PostgreSql.csproj" ReferenceOutputAssembly="false" Private="false" />
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
namespace ServiceControl.Transport.Tests;
2+
3+
using System;
4+
using System.Collections;
5+
using System.Threading.Tasks;
6+
using System.Web;
7+
using IBM.WMQ;
8+
using NUnit.Framework;
9+
using Transports;
10+
using Transports.IBMMQ;
11+
12+
[TestFixture]
13+
class DeadLetterQueueCheckTests
14+
{
15+
[Test]
16+
public async Task Should_pass_when_custom_checks_disabled()
17+
{
18+
var settings = new TransportSettings
19+
{
20+
ConnectionString = ConnectionString,
21+
RunCustomChecks = false
22+
};
23+
24+
var check = new DeadLetterQueueCheck(settings);
25+
var result = await check.PerformCheck().ConfigureAwait(false);
26+
27+
Assert.That(result.HasFailed, Is.False);
28+
}
29+
30+
[Test]
31+
public async Task Should_pass_when_dlq_is_empty()
32+
{
33+
DrainDeadLetterQueue();
34+
35+
var settings = new TransportSettings
36+
{
37+
ConnectionString = ConnectionString,
38+
RunCustomChecks = true
39+
};
40+
41+
var check = new DeadLetterQueueCheck(settings);
42+
var result = await check.PerformCheck().ConfigureAwait(false);
43+
44+
Assert.That(result.HasFailed, Is.False);
45+
}
46+
47+
[Test]
48+
public async Task Should_fail_when_dlq_has_messages()
49+
{
50+
DrainDeadLetterQueue();
51+
PutMessageOnDeadLetterQueue();
52+
53+
try
54+
{
55+
var settings = new TransportSettings
56+
{
57+
ConnectionString = ConnectionString,
58+
RunCustomChecks = true
59+
};
60+
61+
var check = new DeadLetterQueueCheck(settings);
62+
var result = await check.PerformCheck().ConfigureAwait(false);
63+
64+
Assert.That(result.HasFailed, Is.True);
65+
Assert.That(result.FailureReason, Does.Contain("messages in the Dead Letter Queue"));
66+
}
67+
finally
68+
{
69+
DrainDeadLetterQueue();
70+
}
71+
}
72+
73+
[Test]
74+
public async Task Should_fail_when_connection_is_invalid()
75+
{
76+
var settings = new TransportSettings
77+
{
78+
ConnectionString = "mq://admin:passw0rd@localhost:19999/BOGUS",
79+
RunCustomChecks = true
80+
};
81+
82+
var check = new DeadLetterQueueCheck(settings);
83+
var result = await check.PerformCheck().ConfigureAwait(false);
84+
85+
Assert.That(result.HasFailed, Is.True);
86+
Assert.That(result.FailureReason, Does.Contain("Unable to check Dead Letter Queue"));
87+
Assert.That(result.FailureReason, Does.Contain("RC="));
88+
}
89+
90+
static void PutMessageOnDeadLetterQueue()
91+
{
92+
var (qmName, props) = ParseConnectionString();
93+
using var qm = new MQQueueManager(qmName, props);
94+
var dlqName = qm.DeadLetterQueueName.Trim();
95+
using var dlq = qm.AccessQueue(dlqName, MQC.MQOO_OUTPUT);
96+
var msg = new MQMessage();
97+
msg.WriteString("DLQ test message");
98+
dlq.Put(msg);
99+
}
100+
101+
static void DrainDeadLetterQueue()
102+
{
103+
var (qmName, props) = ParseConnectionString();
104+
using var qm = new MQQueueManager(qmName, props);
105+
var dlqName = qm.DeadLetterQueueName.Trim();
106+
using var dlq = qm.AccessQueue(dlqName, MQC.MQOO_INPUT_SHARED | MQC.MQOO_FAIL_IF_QUIESCING);
107+
var gmo = new MQGetMessageOptions { WaitInterval = 0, Options = MQC.MQGMO_NO_WAIT };
108+
while (true)
109+
{
110+
try
111+
{
112+
dlq.Get(new MQMessage(), gmo);
113+
}
114+
catch (MQException e) when (e.ReasonCode == MQC.MQRC_NO_MSG_AVAILABLE)
115+
{
116+
break;
117+
}
118+
}
119+
}
120+
121+
static (string queueManagerName, Hashtable properties) ParseConnectionString()
122+
{
123+
var uri = new Uri(ConnectionString);
124+
var query = HttpUtility.ParseQueryString(uri.Query);
125+
126+
var qmName = uri.AbsolutePath.Trim('/') is { Length: > 0 } path
127+
? Uri.UnescapeDataString(path)
128+
: "QM1";
129+
130+
var props = new Hashtable
131+
{
132+
[MQC.TRANSPORT_PROPERTY] = MQC.TRANSPORT_MQSERIES_MANAGED,
133+
[MQC.HOST_NAME_PROPERTY] = uri.Host,
134+
[MQC.PORT_PROPERTY] = uri.Port > 0 ? uri.Port : 1414,
135+
[MQC.CHANNEL_PROPERTY] = query["channel"] ?? "DEV.ADMIN.SVRCONN",
136+
[MQC.USE_MQCSP_AUTHENTICATION_PROPERTY] = true,
137+
[MQC.USER_ID_PROPERTY] = Uri.UnescapeDataString(uri.UserInfo.Split(':')[0]),
138+
[MQC.PASSWORD_PROPERTY] = Uri.UnescapeDataString(uri.UserInfo.Split(':')[1])
139+
};
140+
141+
return (qmName, props);
142+
}
143+
144+
static readonly string ConnectionString =
145+
Environment.GetEnvironmentVariable("ServiceControl_TransportTests_IBMMQ_ConnectionString")
146+
?? Environment.GetEnvironmentVariable("SERVICECONTROL_TRANSPORTTESTS_IBMMQ_CONNECTIONSTRING")
147+
?? "mq://admin:passw0rd@localhost:1414";
148+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>net10.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<ProjectReference Include="..\ServiceControl.Transports.IBMMQ\ServiceControl.Transports.IBMMQ.csproj" />
9+
<!-- Needed to bring the dependencies that the transport plugin excludes -->
10+
<ProjectReference Include="..\ServiceControl.Transports\ServiceControl.Transports.csproj" />
11+
<ProjectReference Include="..\TestHelper\TestHelper.csproj" />
12+
</ItemGroup>
13+
14+
<ItemGroup>
15+
<PackageReference Include="GitHubActionsTestLogger" />
16+
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
17+
<PackageReference Include="Microsoft.NET.Test.Sdk" />
18+
<PackageReference Include="NServiceBus.AcceptanceTesting" />
19+
<PackageReference Include="NServiceBus.Persistence.NonDurable" />
20+
<PackageReference Include="NUnit" />
21+
<PackageReference Include="NUnit.Analyzers" />
22+
<PackageReference Include="NUnit3TestAdapter" />
23+
<PackageReference Include="Particular.Approvals" />
24+
</ItemGroup>
25+
26+
<ItemGroup>
27+
<Compile Include="..\ServiceControl.Transports.Tests\*.cs" LinkBase="Shared" />
28+
<Compile Remove="..\ServiceControl.Transports.Tests\TransportManifestLibraryTests.cs" />
29+
<Compile Remove="..\ServiceControl.Transports.Tests\TestsFilter.cs" />
30+
</ItemGroup>
31+
32+
</Project>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ServiceControl.Transport.Tests;
2+
3+
using System;
4+
5+
partial class ServiceControlAuditEndpointTests
6+
{
7+
private static partial int GetTransportDefaultConcurrency() => 32;
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ServiceControl.Transport.Tests;
2+
3+
using System;
4+
5+
partial class ServiceControlMonitoringEndpointTests
6+
{
7+
private static partial int GetTransportDefaultConcurrency() => 32;
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace ServiceControl.Transport.Tests;
2+
3+
using System;
4+
5+
partial class ServiceControlPrimaryEndpointTests
6+
{
7+
private static partial int GetTransportDefaultConcurrency() => 10;
8+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[assembly: IncludeInIBMMQTests()]

0 commit comments

Comments
 (0)