Skip to content

Commit c05c49a

Browse files
committed
gh-165 Update CLI with cecho command
Signed-off-by: Victor Chang <[email protected]>
1 parent fb5ae6a commit c05c49a

16 files changed

+163
-48
lines changed

src/Api/Monai.Deploy.InformaticsGateway.Api.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="6.0.9" />
3333
<PackageReference Include="Monai.Deploy.Messaging" Version="0.1.5" />
3434
<PackageReference Include="Monai.Deploy.Storage" Version="0.2.7" />
35-
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
3635
</ItemGroup>
3736

3837
<ItemGroup>

src/CLI/Commands/DestinationCommand.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,19 @@ public DestinationCommand() : base("dst", "Configure DICOM destinations")
4343
SetupAddDestinationCommand();
4444
SetupRemoveDestinationCommand();
4545
SetupListDestinationCommand();
46+
SetupCEchoCommand();
47+
}
48+
49+
private void SetupCEchoCommand()
50+
{
51+
var cechoCommand = new Command("cecho", "C-ECHO a DICOM destination");
52+
cechoCommand.AddAlias("cecho");
53+
AddCommand(cechoCommand);
54+
55+
var nameOption = new Option<string>(new string[] { "-n", "--name" }, "Name of the DICOM destination") { IsRequired = true };
56+
cechoCommand.AddOption(nameOption);
57+
58+
cechoCommand.Handler = CommandHandler.Create<string, IHost, bool, CancellationToken>(CEchoDestinationHandlerAsync);
4659
}
4760

4861
private void SetupListDestinationCommand()
@@ -149,6 +162,42 @@ private async Task<int> ListDestinationHandlerAsync(DestinationApplicationEntity
149162
return ExitCodes.Success;
150163
}
151164

165+
private async Task<int> CEchoDestinationHandlerAsync(string name, IHost host, bool verbose, CancellationToken cancellationToken)
166+
{
167+
Guard.Against.NullOrWhiteSpace(name, nameof(name));
168+
Guard.Against.Null(host, nameof(host));
169+
170+
LogVerbose(verbose, host, "Configuring services...");
171+
var configService = host.Services.GetRequiredService<IConfigurationService>();
172+
var client = host.Services.GetRequiredService<IInformaticsGatewayClient>();
173+
var logger = CreateLogger<DestinationCommand>(host);
174+
175+
Guard.Against.Null(logger, nameof(logger), "Logger is unavailable.");
176+
Guard.Against.Null(configService, nameof(configService), "Configuration service is unavailable.");
177+
Guard.Against.Null(client, nameof(client), $"{Strings.ApplicationName} client is unavailable.");
178+
179+
try
180+
{
181+
CheckConfiguration(configService);
182+
client.ConfigureServiceUris(configService.Configurations.InformaticsGatewayServerUri);
183+
LogVerbose(verbose, host, $"Connecting to {Strings.ApplicationName} at {configService.Configurations.InformaticsGatewayServerEndpoint}...");
184+
LogVerbose(verbose, host, $"Deleting DICOM destination {name}...");
185+
await client.DicomDestinations.CEcho(name, cancellationToken).ConfigureAwait(false);
186+
logger.DicomCEchoSuccessful(name);
187+
}
188+
catch (ConfigurationException ex)
189+
{
190+
logger.ConfigurationException(ex.Message);
191+
return ExitCodes.Config_NotConfigured;
192+
}
193+
catch (Exception ex)
194+
{
195+
logger.ErrorCEchogDicomDestination(name, ex.Message);
196+
return ExitCodes.DestinationAe_ErrorCEcho;
197+
}
198+
return ExitCodes.Success;
199+
}
200+
152201
private async Task<int> RemoveDestinationHandlerAsync(string name, IHost host, bool verbose, CancellationToken cancellationToken)
153202
{
154203
Guard.Against.NullOrWhiteSpace(name, nameof(name));

src/CLI/ExitCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public static class ExitCodes
3232
public const int DestinationAe_ErrorList = 300;
3333
public const int DestinationAe_ErrorDelete = 301;
3434
public const int DestinationAe_ErrorCreate = 302;
35+
public const int DestinationAe_ErrorCEcho = 303;
3536

3637
public const int SourceAe_ErrorList = 400;
3738
public const int SourceAe_ErrorDelete = 401;

src/CLI/Logging/Log.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ public static partial class Log
163163
[LoggerMessage(EventId = 30053, Level = LogLevel.Information, Message = "\n\nFound {count} items.")]
164164
public static partial void ListedNItems(this ILogger logger, int count);
165165

166+
[LoggerMessage(EventId = 30054, Level = LogLevel.Information, Message = "C-ECHO to {name} completed successfully.")]
167+
public static partial void DicomCEchoSuccessful(this ILogger logger, string name);
168+
169+
[LoggerMessage(EventId = 30055, Level = LogLevel.Critical, Message = "C-ECHO to {name} failed: {error}.")]
170+
public static partial void ErrorCEchogDicomDestination(this ILogger logger, string name, string error);
171+
166172
// Docker Runner
167173
[LoggerMessage(EventId = 31000, Level = LogLevel.Debug, Message = "Checking for existing {applicationName} ({version}) containers...")]
168174
public static partial void CheckingExistingAppContainer(this ILogger logger, string applicationName, string version);

src/CLI/Monai.Deploy.InformaticsGateway.CLI.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
<SelfContained>true</SelfContained>
2424
<PublishTrimmed>false</PublishTrimmed>
2525
<PublishReadyToRun>true</PublishReadyToRun>
26-
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
26+
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
2727
<RuntimeIdentifiers>win-x64;linux-x64</RuntimeIdentifiers>
2828
<AssemblyName>mig-cli</AssemblyName>
2929
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>

src/CLI/Test/DestinationCommandTest.cs

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public DestinationCommandTest()
7979
_logger.Setup(p => p.IsEnabled(It.IsAny<LogLevel>())).Returns(true);
8080
}
8181

82-
[Fact(DisplayName = "dst comand")]
82+
[Fact(DisplayName = "dst command")]
8383
public async Task Dst_Command()
8484
{
8585
var command = "dst";
@@ -90,7 +90,7 @@ public async Task Dst_Command()
9090
Assert.Equal(ExitCodes.Success, exitCode);
9191
}
9292

93-
[Fact(DisplayName = "dst add comand")]
93+
[Fact(DisplayName = "dst add command")]
9494
public async Task DstAdd_Command()
9595
{
9696
var command = "dst add -n MyName -a MyAET -h MyHost -p 100";
@@ -124,7 +124,7 @@ public async Task DstAdd_Command()
124124
It.IsAny<CancellationToken>()), Times.Once());
125125
}
126126

127-
[Fact(DisplayName = "dst add comand exception")]
127+
[Fact(DisplayName = "dst add command exception")]
128128
public async Task DstAdd_Command_Exception()
129129
{
130130
var command = "dst add -n MyName -a MyAET --apps App MyCoolApp TheApp";
@@ -141,7 +141,7 @@ public async Task DstAdd_Command_Exception()
141141
_logger.VerifyLoggingMessageBeginsWith("Error creating DICOM destination", LogLevel.Critical, Times.Once());
142142
}
143143

144-
[Fact(DisplayName = "dst add comand configuration exception")]
144+
[Fact(DisplayName = "dst add command configuration exception")]
145145
public async Task DstAdd_Command_ConfigurationException()
146146
{
147147
var command = "dst add -n MyName -a MyAET --apps App MyCoolApp TheApp";
@@ -157,7 +157,7 @@ public async Task DstAdd_Command_ConfigurationException()
157157
_logger.VerifyLoggingMessageBeginsWith("Please execute `testhost config init` to intialize Informatics Gateway.", LogLevel.Critical, Times.Once());
158158
}
159159

160-
[Fact(DisplayName = "dst remove comand")]
160+
[Fact(DisplayName = "dst remove command")]
161161
public async Task DstRemove_Command()
162162
{
163163
var command = "dst rm -n MyName";
@@ -177,7 +177,7 @@ public async Task DstRemove_Command()
177177
_informaticsGatewayClient.Verify(p => p.DicomDestinations.Delete(It.Is<string>(o => o.Equals(name)), It.IsAny<CancellationToken>()), Times.Once());
178178
}
179179

180-
[Fact(DisplayName = "dst remove comand exception")]
180+
[Fact(DisplayName = "dst remove command exception")]
181181
public async Task DstRemove_Command_Exception()
182182
{
183183
var command = "dst rm -n MyName";
@@ -194,7 +194,7 @@ public async Task DstRemove_Command_Exception()
194194
_logger.VerifyLoggingMessageBeginsWith("Error deleting DICOM destination", LogLevel.Critical, Times.Once());
195195
}
196196

197-
[Fact(DisplayName = "dst remove comand configuration exception")]
197+
[Fact(DisplayName = "dst remove command configuration exception")]
198198
public async Task DstRemove_Command_ConfigurationException()
199199
{
200200
var command = "dst rm -n MyName";
@@ -210,7 +210,7 @@ public async Task DstRemove_Command_ConfigurationException()
210210
_logger.VerifyLoggingMessageBeginsWith("Please execute `testhost config init` to intialize Informatics Gateway.", LogLevel.Critical, Times.Once());
211211
}
212212

213-
[Fact(DisplayName = "dst list comand")]
213+
[Fact(DisplayName = "dst list command")]
214214
public async Task DstList_Command()
215215
{
216216
var command = "dst list";
@@ -236,7 +236,7 @@ public async Task DstList_Command()
236236
_informaticsGatewayClient.Verify(p => p.DicomDestinations.List(It.IsAny<CancellationToken>()), Times.Once());
237237
}
238238

239-
[Fact(DisplayName = "dst list comand exception")]
239+
[Fact(DisplayName = "dst list command exception")]
240240
public async Task DstList_Command_Exception()
241241
{
242242
var command = "dst list";
@@ -253,7 +253,7 @@ public async Task DstList_Command_Exception()
253253
_logger.VerifyLoggingMessageBeginsWith("Error retrieving DICOM destinations", LogLevel.Critical, Times.Once());
254254
}
255255

256-
[Fact(DisplayName = "dst list comand configuration exception")]
256+
[Fact(DisplayName = "dst list command configuration exception")]
257257
public async Task DstList_Command_ConfigurationException()
258258
{
259259
var command = "dst list";
@@ -269,7 +269,7 @@ public async Task DstList_Command_ConfigurationException()
269269
_logger.VerifyLoggingMessageBeginsWith("Please execute `testhost config init` to intialize Informatics Gateway.", LogLevel.Critical, Times.Once());
270270
}
271271

272-
[Fact(DisplayName = "dst list comand empty")]
272+
[Fact(DisplayName = "dst list command empty")]
273273
public async Task DstList_Command_Empty()
274274
{
275275
var command = "dst list";
@@ -285,5 +285,58 @@ public async Task DstList_Command_Empty()
285285

286286
_logger.VerifyLogging("No DICOM destinations configured.", LogLevel.Warning, Times.Once());
287287
}
288+
289+
[Fact(DisplayName = "dst cecho command")]
290+
public async Task DstCEcho_Command()
291+
{
292+
var command = "dst cecho -n MyName";
293+
var result = _paser.Parse(command);
294+
Assert.Equal(ExitCodes.Success, result.Errors.Count);
295+
296+
var name = result.CommandResult.Children[0].Tokens[0].Value;
297+
Assert.Equal("MyName", name);
298+
299+
_informaticsGatewayClient.Setup(p => p.DicomDestinations.Delete(It.IsAny<string>(), It.IsAny<CancellationToken>()));
300+
301+
int exitCode = await _paser.InvokeAsync(command);
302+
303+
Assert.Equal(ExitCodes.Success, exitCode);
304+
305+
_informaticsGatewayClient.Verify(p => p.ConfigureServiceUris(It.IsAny<Uri>()), Times.Once());
306+
_informaticsGatewayClient.Verify(p => p.DicomDestinations.CEcho(It.Is<string>(o => o.Equals(name)), It.IsAny<CancellationToken>()), Times.Once());
307+
}
308+
309+
[Fact(DisplayName = "dst cecho command exception")]
310+
public async Task DstCEcho_Command_Exception()
311+
{
312+
var command = "dst cecho -n MyName";
313+
_informaticsGatewayClient.Setup(p => p.DicomDestinations.CEcho(It.IsAny<string>(), It.IsAny<CancellationToken>()))
314+
.Throws(new Exception("error"));
315+
316+
int exitCode = await _paser.InvokeAsync(command);
317+
318+
Assert.Equal(ExitCodes.DestinationAe_ErrorCEcho, exitCode);
319+
320+
_informaticsGatewayClient.Verify(p => p.ConfigureServiceUris(It.IsAny<Uri>()), Times.Once());
321+
_informaticsGatewayClient.Verify(p => p.DicomDestinations.CEcho(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Once());
322+
323+
_logger.VerifyLoggingMessageBeginsWith("C-ECHO to MyName failed", LogLevel.Critical, Times.Once());
324+
}
325+
326+
[Fact(DisplayName = "dst cecho command configuration exception")]
327+
public async Task DstCEcho_Command_ConfigurationException()
328+
{
329+
var command = "dst cecho -n MyName";
330+
_configurationService.SetupGet(p => p.IsInitialized).Returns(false);
331+
332+
int exitCode = await _paser.InvokeAsync(command);
333+
334+
Assert.Equal(ExitCodes.Config_NotConfigured, exitCode);
335+
336+
_informaticsGatewayClient.Verify(p => p.ConfigureServiceUris(It.IsAny<Uri>()), Times.Never());
337+
_informaticsGatewayClient.Verify(p => p.DicomDestinations.CEcho(It.IsAny<string>(), It.IsAny<CancellationToken>()), Times.Never());
338+
339+
_logger.VerifyLoggingMessageBeginsWith("Please execute `testhost config init` to intialize Informatics Gateway.", LogLevel.Critical, Times.Once());
340+
}
288341
}
289342
}

src/Client/Services/AeTitle{T}Service.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ public interface IAeTitleService<T>
3535
Task<T> Create(T item, CancellationToken cancellationToken);
3636

3737
Task<T> Delete(string aeTitle, CancellationToken cancellationToken);
38+
39+
Task CEcho(string name, CancellationToken cancellationToken);
3840
}
3941

4042
internal class AeTitleService<T> : ServiceBase, IAeTitleService<T>
@@ -88,5 +90,14 @@ public async Task<IReadOnlyList<T>> List(CancellationToken cancellationToken)
8890
var list = await response.Content.ReadFromJsonAsync<IEnumerable<T>>(Configuration.JsonSerializationOptions, cancellationToken).ConfigureAwait(false);
8991
return list.ToList().AsReadOnly();
9092
}
93+
94+
public async Task CEcho(string name, CancellationToken cancellationToken)
95+
{
96+
name = Uri.EscapeDataString(name);
97+
Guard.Against.NullOrWhiteSpace(name, nameof(name));
98+
Logger.SendingRequestTo($"{Route}/{name}");
99+
var response = await HttpClient.GetAsync($"{Route}/cecho/{name}", cancellationToken).ConfigureAwait(false);
100+
await response.EnsureSuccessStatusCodeWithProblemDetails(Logger).ConfigureAwait(false);
101+
}
91102
}
92103
}

src/InformaticsGateway/Services/Http/DestinationAeTitleController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ public async Task<IActionResult> CEcho(string name)
114114
return NotFound();
115115
}
116116

117-
var request = new ScuRequest(traceId, RequestType.CEcho, destinationApplicationEntity.HostIp, destinationApplicationEntity.Port, destinationApplicationEntity.AeTitle);
117+
var request = new ScuWorkRequest(traceId, RequestType.CEcho, destinationApplicationEntity.HostIp, destinationApplicationEntity.Port, destinationApplicationEntity.AeTitle);
118118
var response = await _scuQueue.Queue(request, HttpContext.RequestAborted).ConfigureAwait(false);
119119

120120
if (response.Status != ResponseStatus.Success)

src/InformaticsGateway/Services/Scp/ScpService.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
using Microsoft.Extensions.Logging;
2727
using Microsoft.Extensions.Options;
2828
using Monai.Deploy.InformaticsGateway.Api.Rest;
29-
using Monai.Deploy.InformaticsGateway.Common;
3029
using Monai.Deploy.InformaticsGateway.Configuration;
3130
using Monai.Deploy.InformaticsGateway.Logging;
3231
using Monai.Deploy.InformaticsGateway.Services.Common;

src/InformaticsGateway/Services/Scu/IScuQueue.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ public interface IScuQueue
3131
/// </summary>
3232
/// <param name="request">SCU Request for the SCU Service.</param>
3333
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
34-
Task<ScuResponse> Queue(ScuRequest request, CancellationToken cancellationToken);
34+
Task<ScuWorkResponse> Queue(ScuWorkRequest request, CancellationToken cancellationToken);
3535

3636
/// <summary>
3737
/// Dequeue a ScuRequest from the queue for processing.
3838
/// The default implementation blocks the call until a file is available from the queue.
3939
/// </summary>
4040
/// <param name="cancellationToken">Propagates notification that operations should be canceled.</param>
41-
ScuRequest Dequeue(CancellationToken cancellationToken);
41+
ScuWorkRequest Dequeue(CancellationToken cancellationToken);
4242
}
4343
}

0 commit comments

Comments
 (0)