Skip to content

Commit

Permalink
gh-180 Config AE/src/dst APIs to return 409 if entity already exists
Browse files Browse the repository at this point in the history
Signed-off-by: Victor Chang <vicchang@nvidia.com>
  • Loading branch information
mocsharp committed Sep 27, 2022
1 parent fe56aca commit cd93043
Show file tree
Hide file tree
Showing 18 changed files with 166 additions and 48 deletions.
3 changes: 3 additions & 0 deletions docs/api/rest/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Response Content Type: JSON - Array of [MonaiApplicationEntity](xref:Monai.Deplo
| ---- | --------------------------------------------------------------------------------------------------------------------------------------- |
| 200 | AE Titles retrieved successfully. |
| 404 | AE Title not found. |
| 409 | Entity already exists with the same name or AE Title. |
| 500 | Server error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with server error details. |

### Example Request
Expand Down Expand Up @@ -136,6 +137,7 @@ Response Content Type: JSON - [MonaiApplicationEntity](xref:Monai.Deploy.Informa
| ---- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| 201 | AE Title created successfully. |
| 400 | Validation error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with server error details. |
| 409 | Entity already exists with the same name or entity already exists with the same AE Title and port combination. |
| 500 | Server error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with server error details. |

### Example Request
Expand Down Expand Up @@ -301,6 +303,7 @@ Response Content Type: JSON - [SourceApplicationEntity](xref:Monai.Deploy.Inform
| ---- | --------------------------------------------------------------------------------------------------------------------------------------- |
| 201 | AE Title created successfully. |
| 400 | Validation error. |
| 409 | Entity already exists with the same name or entity already exists with the same AE Title, host/IP address and port combination. |
| 500 | Server error. The response will be a [Problem details](https://datatracker.ietf.org/doc/html/rfc7807) object with server error details. |

### Example Request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageReference Include="xRetry" Version="1.8.0" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="System.CommandLine.Hosting" Version="0.4.0-alpha.22272.1" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xRetry" Version="1.8.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageReference Include="xunit" Version="2.4.2" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="xRetry" Version="1.8.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
Expand Down
42 changes: 42 additions & 0 deletions src/InformaticsGateway/Common/ObjectExistsException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2021-2022 MONAI Consortium
* Copyright 2019-2020 NVIDIA Corporation
*
* 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.
*/

using System;
using System.Runtime.Serialization;

namespace Monai.Deploy.InformaticsGateway.Common
{
[Serializable]
internal class ObjectExistsException : Exception
{
public ObjectExistsException()
{
}

public ObjectExistsException(string message) : base(message)
{
}

public ObjectExistsException(string message, Exception innerException) : base(message, innerException)
{
}

protected ObjectExistsException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Monai.Deploy.InformaticsGateway.Api;
using Monai.Deploy.InformaticsGateway.Common;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Logging;
using Monai.Deploy.InformaticsGateway.Repositories;
Expand Down Expand Up @@ -90,6 +91,7 @@ public async Task<ActionResult<DestinationApplicationEntity>> GetAeTitle(string
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[Produces("application/json")]
public async Task<ActionResult<string>> Create(DestinationApplicationEntity item)
Expand All @@ -105,14 +107,18 @@ public async Task<ActionResult<string>> Create(DestinationApplicationEntity item
_logger.DestinationApplicationEntityAdded(item.AeTitle, item.HostIp);
return CreatedAtAction(nameof(GetAeTitle), new { name = item.Name }, item);
}
catch (ObjectExistsException ex)
{
return Problem(title: "DICOM destination already exists", statusCode: (int)System.Net.HttpStatusCode.Conflict, detail: ex.Message);
}
catch (ConfigurationException ex)
{
return Problem(title: "Validation error.", statusCode: (int)System.Net.HttpStatusCode.BadRequest, detail: ex.Message);
return Problem(title: "Validation error", statusCode: (int)System.Net.HttpStatusCode.BadRequest, detail: ex.Message);
}
catch (Exception ex)
{
_logger.ErrorAddingDestinationApplicationEntity(ex);
return Problem(title: "Error adding new DICOM destination.", statusCode: (int)System.Net.HttpStatusCode.InternalServerError, detail: ex.Message);
return Problem(title: "Error adding new DICOM destination", statusCode: (int)System.Net.HttpStatusCode.InternalServerError, detail: ex.Message);
}
}

Expand Down Expand Up @@ -148,11 +154,11 @@ private void Validate(DestinationApplicationEntity item)
{
if (_repository.Any(p => p.Name.Equals(item.Name)))
{
throw new ConfigurationException($"A DICOM destination with the same name '{item.Name}' already exists.");
throw new ObjectExistsException($"A DICOM destination with the same name '{item.Name}' already exists.");
}
if (_repository.Any(p => p.AeTitle.Equals(item.AeTitle) && p.HostIp.Equals(item.HostIp) && p.Port.Equals(item.Port)))
{
throw new ConfigurationException($"A DICOM destination with the same AE Title '{item.AeTitle}', host/IP Address '{item.HostIp}' and port '{item.Port}' already exists.");
throw new ObjectExistsException($"A DICOM destination with the same AE Title '{item.AeTitle}', host/IP Address '{item.HostIp}' and port '{item.Port}' already exists.");
}
if (!item.IsValid(out var validationErrors))
{
Expand Down
10 changes: 8 additions & 2 deletions src/InformaticsGateway/Services/Http/MonaiAeTitleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Monai.Deploy.InformaticsGateway.Api;
using Monai.Deploy.InformaticsGateway.Common;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Logging;
using Monai.Deploy.InformaticsGateway.Repositories;
Expand Down Expand Up @@ -96,6 +97,7 @@ public async Task<ActionResult<MonaiApplicationEntity>> GetAeTitle(string name)
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[Produces("application/json")]
public async Task<ActionResult<MonaiApplicationEntity>> Create(MonaiApplicationEntity item)
Expand All @@ -112,6 +114,10 @@ public async Task<ActionResult<MonaiApplicationEntity>> Create(MonaiApplicationE
_logger.MonaiApplicationEntityAdded(item.AeTitle);
return CreatedAtAction(nameof(GetAeTitle), new { name = item.Name }, item);
}
catch (ObjectExistsException ex)
{
return Problem(title: "AE Title already exists", statusCode: (int)System.Net.HttpStatusCode.Conflict, detail: ex.Message);
}
catch (ConfigurationException ex)
{
return Problem(title: "Validation error.", statusCode: (int)System.Net.HttpStatusCode.BadRequest, detail: ex.Message);
Expand Down Expand Up @@ -158,11 +164,11 @@ private void Validate(MonaiApplicationEntity item)

if (_repository.Any(p => p.Name.Equals(item.Name)))
{
throw new ConfigurationException($"A MONAI Application Entity with the same name '{item.Name}' already exists.");
throw new ObjectExistsException($"A MONAI Application Entity with the same name '{item.Name}' already exists.");
}
if (_repository.Any(p => p.AeTitle.Equals(item.AeTitle)))
{
throw new ConfigurationException($"A MONAI Application Entity with the same AE Title '{item.AeTitle}' already exists.");
throw new ObjectExistsException($"A MONAI Application Entity with the same AE Title '{item.AeTitle}' already exists.");
}
if (item.IgnoredSopClasses.Any() && item.AllowedSopClasses.Any())
{
Expand Down
10 changes: 8 additions & 2 deletions src/InformaticsGateway/Services/Http/SourceAeTitleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Monai.Deploy.InformaticsGateway.Api;
using Monai.Deploy.InformaticsGateway.Common;
using Monai.Deploy.InformaticsGateway.Configuration;
using Monai.Deploy.InformaticsGateway.Logging;
using Monai.Deploy.InformaticsGateway.Repositories;
Expand Down Expand Up @@ -91,6 +92,7 @@ public async Task<ActionResult<SourceApplicationEntity>> GetAeTitle(string name)
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
[ProducesResponseType(StatusCodes.Status500InternalServerError)]
[Produces("application/json")]
public async Task<ActionResult<string>> Create(SourceApplicationEntity item)
Expand All @@ -105,6 +107,10 @@ public async Task<ActionResult<string>> Create(SourceApplicationEntity item)
_logger.SourceApplicationEntityAdded(item.AeTitle, item.HostIp);
return CreatedAtAction(nameof(GetAeTitle), new { name = item.Name }, item);
}
catch (ObjectExistsException ex)
{
return Problem(title: "DICOM source already exists", statusCode: (int)System.Net.HttpStatusCode.Conflict, detail: ex.Message);
}
catch (ConfigurationException ex)
{
return Problem(title: "Validation error.", statusCode: (int)System.Net.HttpStatusCode.BadRequest, detail: ex.Message);
Expand Down Expand Up @@ -148,11 +154,11 @@ private void Validate(SourceApplicationEntity item)
{
if (_repository.Any(p => p.Name.Equals(item.Name)))
{
throw new ConfigurationException($"A DICOM source with the same name '{item.Name}' already exists.");
throw new ObjectExistsException($"A DICOM source with the same name '{item.Name}' already exists.");
}
if (_repository.Any(p => item.AeTitle.Equals(p.AeTitle) && item.HostIp.Equals(p.HostIp)))
{
throw new ConfigurationException($"A DICOM source with the same AE Title '{item.AeTitle}' and host/IP address '{item.HostIp}' already exists.");
throw new ObjectExistsException($"A DICOM source with the same AE Title '{item.AeTitle}' and host/IP address '{item.HostIp}' already exists.");
}
if (!item.IsValid(out var validationErrors))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Mvc.WebApiCompatShim" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="6.0.9" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Moq" Version="4.18.2" />
<PackageReference Include="NPOI" Version="2.5.6" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,38 @@ public async Task Create_ShallReturnBadRequestWithBadJobProcessType()
Assert.NotNull(objectResult);
var problem = objectResult.Value as ProblemDetails;
Assert.NotNull(problem);
Assert.Equal("Validation error.", problem.Title);
Assert.Equal("Validation error", problem.Title);
Assert.Equal($"'{aeTitle}' is not a valid AE Title (source: DestinationApplicationEntity).", problem.Detail);
Assert.Equal((int)HttpStatusCode.BadRequest, problem.Status);
}

[RetryFact(5, 250, DisplayName = "Create - Shall return Conflict if entity already exists")]
public async Task Create_ShallReturnConflictIfEntityAlreadyExists()
{
var aeTitle = "AET";
var aeTitles = new DestinationApplicationEntity
{
Name = aeTitle,
AeTitle = aeTitle,
HostIp = "host",
Port = 1
};

_repository.Setup(p => p.Any(It.IsAny<Func<DestinationApplicationEntity, bool>>())).Returns(true);

var result = await _controller.Create(aeTitles);

var objectResult = result.Result as ObjectResult;
Assert.NotNull(objectResult);
var problem = objectResult.Value as ProblemDetails;
Assert.NotNull(problem);
Assert.Equal("DICOM destination already exists", problem.Title);
Assert.Equal("A DICOM destination with the same name 'AET' already exists.", problem.Detail);
Assert.Equal((int)HttpStatusCode.Conflict, problem.Status);

_repository.Verify(p => p.AddAsync(It.IsAny<DestinationApplicationEntity>(), It.IsAny<CancellationToken>()), Times.Never());
}

[RetryFact(5, 250, DisplayName = "Create - Shall return problem if failed to add")]
public async Task Create_ShallReturnBadRequestOnAddFailure()
{
Expand All @@ -217,7 +244,7 @@ public async Task Create_ShallReturnBadRequestOnAddFailure()
Assert.NotNull(objectResult);
var problem = objectResult.Value as ProblemDetails;
Assert.NotNull(problem);
Assert.Equal("Error adding new DICOM destination.", problem.Title);
Assert.Equal("Error adding new DICOM destination", problem.Title);
Assert.Equal($"error", problem.Detail);
Assert.Equal((int)HttpStatusCode.InternalServerError, problem.Status);

Expand Down
Loading

0 comments on commit cd93043

Please sign in to comment.