Skip to content

APIs to update DICOM source and destination #197

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 113 additions & 23 deletions docs/api/rest/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,16 @@ Response Content Type: JSON - [MonaiApplicationEntity](xref:Monai.Deploy.Informa

```bash
curl --location --request POST 'http://localhost:5000/config/ae/' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "breast-tumor",
"aeTitle": "BREASTV1",
"timeout": 5,
"workflows": [
"3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"
]
}
}'
--header 'Content-Type: application/json' \
--data-raw '{
"name": "breast-tumor",
"aeTitle": "BREASTV1",
"timeout": 5,
"workflows": [
"3f6a08a1-0dea-44e9-ab82-1ff1adf43a8e"
]
}
}'
```

### Example Response
Expand Down Expand Up @@ -310,12 +310,56 @@ Response Content Type: JSON - [SourceApplicationEntity](xref:Monai.Deploy.Inform

```bash
curl --location --request POST 'http://localhost:5000/config/source' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "USEAST",
"hostIp": "10.20.3.4",
"aeTitle": "PACSUSEAST"
}'
--header 'Content-Type: application/json' \
--data-raw '{
"name": "USEAST",
"hostIp": "10.20.3.4",
"aeTitle": "PACSUSEAST"
}'
```

### Example Response

```json
{
"name": "USEAST",
"aeTitle": "PACSUSEAST",
"hostIp": "10.20.3.4"
}
```

---

## PUT /config/source

Updates an existing calling (source) AE Title.

### Parameters

See the [SourceApplicationEntity](xref:Monai.Deploy.InformaticsGateway.Api.SourceApplicationEntity)
class definition for details.

### Responses

Response Content Type: JSON - [SourceApplicationEntity](xref:Monai.Deploy.InformaticsGateway.Api.SourceApplicationEntity).

| Code | Description |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200 | AE Title updated successfully. |
| 400 | Validation error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with details of the validation errors . |
| 404 | DICOM source cannot be found. |
| 500 | Server error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with server error details. |

### Example Request

```bash
curl --location --request PUT 'http://localhost:5000/config/source' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "USEAST",
"hostIp": "10.20.3.4",
"aeTitle": "PACSUSEAST"
}'
```

### Example Response
Expand Down Expand Up @@ -479,6 +523,52 @@ curl --location --request DELETE 'http://localhost:5000/config/destination/cecho

---

## PUT /config/destination

Updates an existing DICOM destination.

### Parameters

See the [DestinationApplicationEntity](xref:Monai.Deploy.InformaticsGateway.Api.DestinationApplicationEntity)
class definition for details.

### Responses

Response Content Type: JSON - [DestinationApplicationEntity](xref:Monai.Deploy.InformaticsGateway.Api.DestinationApplicationEntity).

| Code | Description |
| ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 200 | DICOM destination updated successfully. |
| 400 | Validation error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with details of the validation errors . |
| 404 | DICOM destination cannot be found. |
| 500 | Server error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with server error details. |

### Example Request

```bash
curl --location --request PUT 'http://localhost:5000/config/destination' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "USEAST",
"hostIp": "10.20.3.4",
"port": 104,
"aeTitle": "PACSUSEAST"
}'
```

### Example Response

```json
{
"port": 104,
"name": "USEAST",
"aeTitle": "PACSUSEAST",
"hostIp": "10.20.3.4"
}
```

---

## POST /config/destination

Adds a new DICOM destination AET for exporting results to.
Expand All @@ -502,13 +592,13 @@ Response Content Type: JSON - [DestinationApplicationEntity](xref:Monai.Deploy.I

```bash
curl --location --request POST 'http://localhost:5000/config/destination' \
--header 'Content-Type: application/json' \
--data-raw '{
"name": "USEAST",
"hostIp": "10.20.3.4",
"port": 104,
"aeTitle": "PACSUSEAST"
}'
--header 'Content-Type: application/json' \
--data-raw '{
"name": "USEAST",
"hostIp": "10.20.3.4",
"port": 104,
"aeTitle": "PACSUSEAST"
}'
```

### Example Response
Expand Down
56 changes: 56 additions & 0 deletions src/CLI/Commands/DestinationCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public DestinationCommand() : base("dst", "Configure DICOM destinations")
AddAlias("destination");

SetupAddDestinationCommand();
SetupEditDestinationCommand();
SetupRemoveDestinationCommand();
SetupListDestinationCommand();
SetupCEchoCommand();
Expand All @@ -67,6 +68,23 @@ private void SetupListDestinationCommand()
listCommand.Handler = CommandHandler.Create<DestinationApplicationEntity, IHost, bool, CancellationToken>(ListDestinationHandlerAsync);
}

private void SetupEditDestinationCommand()
{
var addCommand = new Command("update", "Update a new DICOM destination");
AddCommand(addCommand);

var nameOption = new Option<string>(new string[] { "--name", "-n" }, "Name of the DICOM destination") { IsRequired = false };
addCommand.AddOption(nameOption);
var aeTitleOption = new Option<string>(new string[] { "--aetitle", "-a" }, "AE Title of the DICOM destination") { IsRequired = true };
addCommand.AddOption(aeTitleOption);
var hostOption = new Option<string>(new string[] { "--host-ip", "-h" }, "Host or IP address of the DICOM destination") { IsRequired = true };
addCommand.AddOption(hostOption);
var portOption = new Option<int>(new string[] { "--port", "-p" }, "Listening port of the DICOM destination") { IsRequired = true };
addCommand.AddOption(portOption);

addCommand.Handler = CommandHandler.Create<DestinationApplicationEntity, IHost, bool, CancellationToken>(EditDestinationHandlerAsync);
}

private void SetupRemoveDestinationCommand()
{
var removeCommand = new Command("rm", "Remove a DICOM destination");
Expand Down Expand Up @@ -234,6 +252,44 @@ private async Task<int> RemoveDestinationHandlerAsync(string name, IHost host, b
return ExitCodes.Success;
}

private async Task<int> EditDestinationHandlerAsync(DestinationApplicationEntity entity, IHost host, bool verbose, CancellationToken cancellationToken)
{
Guard.Against.Null(entity, nameof(entity));
Guard.Against.Null(host, nameof(host));

LogVerbose(verbose, host, "Configuring services...");
var configService = host.Services.GetRequiredService<IConfigurationService>();
var client = host.Services.GetRequiredService<IInformaticsGatewayClient>();
var logger = CreateLogger<DestinationCommand>(host);

Guard.Against.Null(logger, nameof(logger), "Logger is unavailable.");
Guard.Against.Null(configService, nameof(configService), "Configuration service is unavailable.");
Guard.Against.Null(client, nameof(client), $"{Strings.ApplicationName} client is unavailable.");

try
{
CheckConfiguration(configService);
client.ConfigureServiceUris(configService.Configurations.InformaticsGatewayServerUri);

LogVerbose(verbose, host, $"Connecting to {Strings.ApplicationName} at {configService.Configurations.InformaticsGatewayServerEndpoint}...");
LogVerbose(verbose, host, $"Updating DICOM destination {entity.AeTitle}...");
var result = await client.DicomDestinations.Update(entity, cancellationToken).ConfigureAwait(false);

logger.DicomDestinationCreated(result.Name, result.AeTitle, result.HostIp, result.Port);
}
catch (ConfigurationException ex)
{
logger.ConfigurationException(ex.Message);
return ExitCodes.Config_NotConfigured;
}
catch (Exception ex)
{
logger.ErrorUpdatingDicomDestination(entity.AeTitle, ex.Message);
return ExitCodes.DestinationAe_ErrorUpdate;
}
return ExitCodes.Success;
}

private async Task<int> AddDestinationHandlerAsync(DestinationApplicationEntity entity, IHost host, bool verbose, CancellationToken cancellationToken)
{
Guard.Against.Null(entity, nameof(entity));
Expand Down
52 changes: 52 additions & 0 deletions src/CLI/Commands/SourceCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,12 @@ public SourceCommand() : base("src", "Configure DICOM sources")
AddAlias("source");

SetupAddSourceCommand();
SetupUpdateSourceCommand();
SetupRemoveSourceCommand();
SetupListSourceCommand();
}


private void SetupListSourceCommand()
{
var listCommand = new Command("ls", "List all DICOM sources");
Expand Down Expand Up @@ -79,6 +81,20 @@ private void SetupAddSourceCommand()

addCommand.Handler = CommandHandler.Create<SourceApplicationEntity, IHost, bool, CancellationToken>(AddSourceHandlerAsync);
}
private void SetupUpdateSourceCommand()
{
var addCommand = new Command("update", "Update a new DICOM source");
AddCommand(addCommand);

var nameOption = new Option<string>(new string[] { "--name", "-n" }, "Name of the DICOM source") { IsRequired = false };
addCommand.AddOption(nameOption);
var aeTitleOption = new Option<string>(new string[] { "--aetitle", "-a" }, "AE Title of the DICOM source") { IsRequired = true };
addCommand.AddOption(aeTitleOption);
var hostOption = new Option<string>(new string[] { "--host-ip", "-h" }, "Host or IP address of the DICOM source") { IsRequired = true };
addCommand.AddOption(hostOption);

addCommand.Handler = CommandHandler.Create<SourceApplicationEntity, IHost, bool, CancellationToken>(UpdateSourceHandlerAsync);
}

private async Task<int> ListSourceHandlerAsync(SourceApplicationEntity entity, IHost host, bool verbose, CancellationToken cancellationTokena)
{
Expand Down Expand Up @@ -217,5 +233,41 @@ private async Task<int> AddSourceHandlerAsync(SourceApplicationEntity entity, IH
}
return ExitCodes.Success;
}
private async Task<int> UpdateSourceHandlerAsync(SourceApplicationEntity entity, IHost host, bool verbose, CancellationToken cancellationTokena)
{
Guard.Against.Null(entity, nameof(entity));
Guard.Against.Null(host, nameof(host));

LogVerbose(verbose, host, "Configuring services...");
var configService = host.Services.GetRequiredService<IConfigurationService>();
var client = host.Services.GetRequiredService<IInformaticsGatewayClient>();
var logger = CreateLogger<SourceCommand>(host);

Guard.Against.Null(logger, nameof(logger), "Logger is unavailable.");
Guard.Against.Null(configService, nameof(configService), "Configuration service is unavailable.");
Guard.Against.Null(client, nameof(client), $"{Strings.ApplicationName} client is unavailable.");

try
{
CheckConfiguration(configService);
client.ConfigureServiceUris(configService.Configurations.InformaticsGatewayServerUri);
LogVerbose(verbose, host, $"Connecting to {Strings.ApplicationName} at {configService.Configurations.InformaticsGatewayServerEndpoint}...");
LogVerbose(verbose, host, $"Updating DICOM source {entity.AeTitle}...");
var result = await client.DicomSources.Update(entity, cancellationTokena).ConfigureAwait(false);

logger.DicomSourceUpdated(result.Name, result.AeTitle, result.HostIp);
}
catch (ConfigurationException ex)
{
logger.ConfigurationException(ex.Message);
return ExitCodes.Config_NotConfigured;
}
catch (Exception ex)
{
logger.ErrorUpdatingDicomSource(entity.AeTitle, ex.Message);
return ExitCodes.SourceAe_ErrorUpdate;
}
return ExitCodes.Success;
}
}
}
2 changes: 2 additions & 0 deletions src/CLI/ExitCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@ public static class ExitCodes
public const int DestinationAe_ErrorDelete = 301;
public const int DestinationAe_ErrorCreate = 302;
public const int DestinationAe_ErrorCEcho = 303;
public const int DestinationAe_ErrorUpdate = 304;

public const int SourceAe_ErrorList = 400;
public const int SourceAe_ErrorDelete = 401;
public const int SourceAe_ErrorCreate = 402;
public const int SourceAe_ErrorUpdate = 403;

public const int Restart_Cancelled = 500;
public const int Restart_Error = 501;
Expand Down
12 changes: 12 additions & 0 deletions src/CLI/Logging/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,18 @@ public static partial class Log
[LoggerMessage(EventId = 30055, Level = LogLevel.Critical, Message = "C-ECHO to {name} failed: {error}.")]
public static partial void ErrorCEchogDicomDestination(this ILogger logger, string name, string error);

[LoggerMessage(EventId = 30056, Level = LogLevel.Information, Message = "DICOM destination updated:\r\n\tName: {name}\r\n\tAE Title: {aeTitle}\r\n\tHost/IP Address: {hostIp}\r\n\tPort: {port}")]
public static partial void DicomDestinationUpdated(this ILogger logger, string name, string aeTitle, string hostIp, int port);

[LoggerMessage(EventId = 30057, Level = LogLevel.Critical, Message = "Error updating DICOM destination {aeTitle}: {message}")]
public static partial void ErrorUpdatingDicomDestination(this ILogger logger, string aeTitle, string message);

[LoggerMessage(EventId = 30058, Level = LogLevel.Information, Message = "DICOM source updated:\r\n\tName: {name}\r\n\tAE Title: {aeTitle}\r\n\tHost/IP Address: {hostIp}")]
public static partial void DicomSourceUpdated(this ILogger logger, string name, string aeTitle, string hostIp);

[LoggerMessage(EventId = 30059, Level = LogLevel.Critical, Message = "Error updating DICOM source {aeTitle}: {message}")]
public static partial void ErrorUpdatingDicomSource(this ILogger logger, string aeTitle, string message);

// Docker Runner
[LoggerMessage(EventId = 31000, Level = LogLevel.Debug, Message = "Checking for existing {applicationName} ({version}) containers...")]
public static partial void CheckingExistingAppContainer(this ILogger logger, string applicationName, string version);
Expand Down
Loading