Skip to content

Commit

Permalink
Merge branch 'release/1.2.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
tznind committed Jan 6, 2020
2 parents 2a25664 + 7395d74 commit dc7dba8
Show file tree
Hide file tree
Showing 28 changed files with 1,465 additions and 3,265 deletions.
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## [Unreleased]

...
## [1.2.1] - 2020-01-06

### Added

- Added the `set-sleep-time-ms` control message to DicomReprocessor

### Changed

- Updated Rdmp.Dicom nuget package to 2.0.6

## [1.2.0] - 2019-12-12

Expand Down Expand Up @@ -84,7 +92,8 @@ First stable release after importing the repository from the private [SMIPlugin]
- Anonymous `MappingTableName` must now be fully specified to pass validation (e.g. `mydb.mytbl`). Previously skipping database portion was supported.


[Unreleased]: https://github.com/SMI/SmiServices/compare/1.2.0...develop
[Unreleased]: https://github.com/SMI/SmiServices/compare/v1.2.1...develop
[1.2.1]: https://github.com/SMI/SmiServices/compare/1.2.0...v1.2.1
[1.2.0]: https://github.com/SMI/SmiServices/compare/1.1.0-rc1...1.2.0
[1.2.0-rc1]: https://github.com/SMI/SmiServices/compare/1.1.0...1.2.0-rc1
[1.1.0]: https://github.com/SMI/SmiServices/compare/1.0.0...1.1.0
Expand Down
2 changes: 1 addition & 1 deletion PACKAGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
| fo-dicom.Json | [GitHub](https://github.com/fo-dicom/fo-dicom) | [4.0.1](https://www.nuget.org/packages/fo-dicom.Json/4.0.1) | [MS-PL](https://opensource.org/licenses/MS-PL)| Support library for serializing fo-dicom DICOM datasets to json | |
| HIC.DicomTypeTranslation | [GitHub](https://github.com/HicServices/DicomTypeTranslation) | [2.1.2](https://www.nuget.org/packages/HIC.DicomTypeTranslation/2.1.2) | [GPL 3.0](https://www.gnu.org/licenses/gpl-3.0.html) | Translate dicom types into C# / database types | |
| HIC.RDMP.Plugin | [GitHub](https://github.com/HicServices/RDMP) | [4.0.1](https://www.nuget.org/packages/HIC.RDMP.Plugin/4.0.1) | [GPL 3.0](https://www.gnu.org/licenses/gpl-3.0.html) | Interact with RDMP objects, base classes for plugin components etc | |
| HIC.RDMP.Dicom | [GitHub](https://github.com/HicServices/RdmpDicom) | [2.0.4](https://www.nuget.org/packages/HIC.RDMP.Dicom/2.0.4) | [GPL 3.0](https://www.gnu.org/licenses/gpl-3.0.html) | RDMP Plugin containing data load / pipeline components for imaging, reading dicom files etc | |
| HIC.RDMP.Dicom | [GitHub](https://github.com/HicServices/RdmpDicom) | [2.0.6](https://www.nuget.org/packages/HIC.RDMP.Dicom/2.0.6) | [GPL 3.0](https://www.gnu.org/licenses/gpl-3.0.html) | RDMP Plugin containing data load / pipeline components for imaging, reading dicom files etc | |
| [Newtonsoft.Json](https://www.newtonsoft.com/json) | [GitHub](https://github.com/JamesNK/Newtonsoft.Json) | [12.0.3](https://www.nuget.org/packages/Newtonsoft.Json/12.0.3) | [MIT](https://opensource.org/licenses/MIT) | Serialization of objects for sharing/transmission |
| [NLog](https://nlog-project.org/) | [GitHub](https://github.com/NLog/NLog) | [4.6.4](https://www.nuget.org/packages/NLog/4.6.4) | [BSD 3-Clause](https://github.com/NLog/NLog/blob/dev/LICENSE.txt) | Flexible user configurable logging | |
| [RabbitMQ.Client](https://www.rabbitmq.com/) | [GitHub](https://github.com/rabbitmq/rabbitmq-dotnet-client) | [5.1.2](https://www.nuget.org/packages/RabbitMQ.Client/5.1.2) | [Apache License v2 / MPL 1.1](https://github.com/rabbitmq/rabbitmq-dotnet-client/blob/master/LICENSE) | Handles messaging between microservices | |
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
![GitHub](https://img.shields.io/github/license/SMI/SmiServices)
[![Total alerts](https://img.shields.io/lgtm/alerts/g/SMI/SmiServices.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/SMI/SmiServices/alerts/)

Version: `1.2.0`
Version: `1.2.1`

# SMI Services

![loaddiagram](./SmiFlow.svg)
![loaddiagram](./docs/Images/SmiFlow.svg)

A suite of microservices for [loading*](./Glossary.md#loading), anonymising, linking and extracting [large volumnes](#scaleability) of [dicom] medical images to support medical research.

Expand Down Expand Up @@ -190,4 +190,5 @@ Scaleability is handled through parallel process execution (using [RabbitMQ]).
[RabbitMQ]: https://www.rabbitmq.com/
[DBMS]: https://github.com/HicServices/RDMP/blob/develop/Documentation/CodeTutorials/Glossary.md#DBMS
[Dicom]: ./Glossary.md#dicom
[Dicom tags]: ./Glossary.md#dicom-tags
[Dicom tags]: ./Glossary.md#dicom-tags

1,046 changes: 1,046 additions & 0 deletions data/ctp/ctp-whitelist.script

Large diffs are not rendered by default.

3,103 changes: 0 additions & 3,103 deletions data/logging/NLog.xsd

This file was deleted.

2 changes: 1 addition & 1 deletion data/logging/Smi.NLog.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd"
autoReload="true"
autoReload="false"
throwExceptions="true"
internalLogLevel="Debug"
internalLogFile="./logs/nlog-internal.log">
Expand Down
142 changes: 142 additions & 0 deletions docs/Extraction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Extraction

Describes the build-install-test procedure, not the deployment into production.

Main docs:
https://github.com/SMI/SmiServices/tree/master/src/microservices/com.smi.microservices.ctpanonymiser

https://github.com/SMI/SmiServices/tree/release/1.2.0#image-extraction-microservices

See also: the extraction-refactoring branch
https://github.com/SMI/SmiServices/tree/feature/extraction-refactoring/docs/extraction

Other docs:
https://uoe.sharepoint.com/sites/SMI/Shared%20Documents/Forms/AllItems.aspx
https://git.ecdf.ed.ac.uk/SMI/SmiServiceOps/blob/master/Planning/ExtractionFlags
https://github.com/HicServices/SMIPlugin/blob/master/Documentation/Images/ExtractionMicroservices.png

# Building

See elsewhere the documents for building the Java programs.

# Prerequisites for testing

A RabbitMQ instance is required - you can run a test version inside a Docker container:

```
sudo docker run -d --hostname my-rabbit --name some-rabbit-mgt -p 5671:5671 -p 5672:5672 -p 5673:5673 -p 15671:15671 -p 15672:15672 -p 25672:25672 rabbitmq:3-management
```

# ExtractorCLI

```
cd ~/src/SmiServices/src/applications/com.smi.applications.extractorcli/target
cat > extractme.csv << _EOF
SeriesInstanceUID,foo
1.2.826.0.1.3680043.2.1125.1.78969117856457473538394301521877227,1
_EOF
Edit default.yaml (RabbitOptions and FileSystemOptions)
You could do this programmatically with
`yq_linux_amd64 write --inplace d FileSystemOptions.FileSystemRoot /tmp`
although the current version of yq loses comments and unnecessary quotes.
Login to rabbit (localhost:15672) and create exchanges:
TEST.RequestExchange
TEST.RequestInfoExchange
Add bindings from those exchanges to any queue (TEST.xxx)
ProjectNum=001
rmdir /tmp/${ProjectNum}/tmp # program gives error if dir already exists
java -jar ExtractorCL-portable-1.0.0.jar -y default.yaml -c 0 -e tmp -p ${ProjectNum} extractme.csv
(interactive - answer y to create messages)
```

Two messages are created:

```
{"KeyTag":"SeriesInstanceUID","ExtractionIdentifiers":["1.2.826.0.1.3680043.2.1125.1.78969117856457473538394301521877227"],"ExtractionJobIdentifier":"bb1cbed5-a666-4307-a781-5b83926eaa81","ProjectNumber":"001","ExtractionDirectory":"001/tmp","JobSubmittedAt":"2019-12-19T10:49Z"}
```
and
```
{"KeyTag":"SeriesInstanceUID","KeyValueCount":1,"ExtractionJobIdentifier":"bb1cbed5-a666-4307-a781-5b83926eaa81","ProjectNumber":"001","ExtractionDirectory":"001/tmp","JobSubmittedAt":"2019-12-19T10:49Z"}
```

# CohortExtractor

Requires MySQL instance? so not described (yet).

Creates messages containing fields:
DicomFilePath: Path to the original file
ExtractionDirectory: Extraction directory relative to the extract root
OutputPath: Output path for the anonymised file, relative to the extraction directory
See: ~/src/SmiServices/src/common/Smi.Common/Messages/Extraction/ExtractFileMessage.cs
Inherits ExtractMessage so:
Guid ExtractionJobIdentifier
string ProjectNumber
string ExtractionDirectory
DateTime JobSubmittedAt

# CTPanonymiser

`cd ~/src/SmiServices/src/microservices/com.smi.microservices.ctpanonymiser/target`

A whitelist is required, available from the old repo as:
https://raw.githubusercontent.com/HicServices/SMIPlugin/develop/Documentation/Anon/dicom-whitelist.script
https://raw.githubusercontent.com/HicServices/SMIPlugin/develop/Documentation/Anon/dicom-whitelist.script.new
(possibly identical content, apart from whitespace/newlines??)
or from the new repo in the directories:
```
SmiServices/src/applications/com.smi.applications.extractorcli/anonScript.txt
SmiServices/src/microservices/com.smi.microservices.ctpanonymiser/src/test/resources/dicom-anonymizer.script
```
Haven't yet determined which one is correct.

Edit `default.yaml` (RabbitOptions and FileSystemOptions)

Login to rabbit (http://localhost:15672/) and create exchanges:
TEST.ControlExchange
TEST.FatalLoggingExchange
TEST.FileStatusExchange
and queue: TEST.ExtractFileQueue
Check: do we need to add bindings from those exchanges to the queue?

Copy an input file into the directory relative to the root in default.yaml:
```
cp src/SmiServices/src/microservices/com.smi.microservices.ctpanonymiser/src/test/resources/image-000001.dcm /tmp
```

Create a fake message and send to TEST.ControlExchange:
```
python3 -m pip install pika
#!/usr/bin/env python3
msg_json = '{ "DicomFilePath": "image-000001.dcm", "ExtractionDirectory": "001/tmp/extractiondir/", "OutputPath": "output.dcm", "ExtractionJobIdentifier":"bb1cbed5-a666-4307-a781-5b83926eaa81",
"ProjectNumber":"001", "ExtractionDirectory":"001/tmp", "JobSubmittedAt":"2019-12-19T10:49Z" }'
hdr={'MessageGuid':'', 'OriginalPublishTimestamp':'', 'ProducerExacutableName':'test.py', 'ProducerProcessID': '0'}
import pika
connection = pika.BlockingConnection(pika.ConnectionParameters('localhost'))
channel = connection.channel()
# exchange='TEST.ControlExchange', '' to make binding straight to routing_key queue
channel.basic_publish(exchange='', routing_key='TEST.ExtractFileQueue', body=msg_json, properties=pika.BasicProperties(content_type='application/json', headers=hdr) )
```

Run:
```
java -jar CTPAnonymiser-portable-1.0.0.jar -a dicom-whitelist.script.new -y default.yaml
```

The output is written to `/tmp/001/tmp/output.dcm` in this example and the log file is in `logs/YYYY-MM-DD-hhmmss.log`

A 'success' message is published to TEST.FileStatusExchange containing:
```
{"DicomFilePath":"image-000001.dcm","AnonymisedFileName":"output.dcm","Status":0,"ExtractionJobIdentifier":"bb1cbed5-a666-4307-a781-5b83926eaa81","ProjectNumber":"001","ExtractionDirectory":"001/tmp","JobSubmittedAt":"2019-12-19T10:49Z"}
```

# IsIdentifiable

See the netcoreapp2.2 branch of IsIdentifiable here:
https://github.com/HicServices/IsIdentifiable/tree/netcoreapp2.2
with the changes required to build and run on dotnet core 2.2 Linux
(until such time as it's merged into master).
File renamed without changes
22 changes: 21 additions & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@

# TODO Figure out what documentation can be (safely) imported here from the private repo
# SMI Services Documentation

This is the documentation for the SMI Servies platform. It should (hopefully) contain enough information to run your own instance of the service.

The platform is currently deployed in the National Safe Haven, so some documentation may specifically refer to that environment. The software should be deployable in any environment though, so please open an [issue](https://github.com/SMI/SmiServices/issues) if anything isn't clear.


### Contents

- [Controlling the services](#controlling-the-services)
- [TODO](#todo)


## Controlling the services

The services can be controlled by sending messages to the RabbitMQ control exchange with specific routing keys. See the [main doc](control-queues.md)


## TODO

- Figure out what documentation can be (safely) imported from the old private repo
91 changes: 91 additions & 0 deletions docs/control-queues.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@

# Microservice Control Queues

This describes how the services can be controlled via RabbitMQ messages.

### Contents

- [Commands](#commands)
- [Sending a message](#sending-a-message)
- [Implementing a new control command handler](#implementing-a-new-control-command-handler)
- [Control Queues and Cleanup](#control-queues-and-cleanup)

## Commands

Commands are sent by publishing a message to the ControlExchange (specified in your config by `RabbitOptions.RabbitMqControlExchangeName`) with a specific routing key. This allows you to easily send them from the RabbitMQ web management page, or via a CLI.

RabbitMQ message routing keys are used to control which services recieve the message. The current format for routing keys is `smi.control.<who>.<what>`. Where `<who>` is the name of the service, and `<what>` is some defined action. The currently defined actions are:

### General - any service

- `stop` - Stops the service
- `ping` - Logs a `pong` message. Useful for debugging

### DicomReprocessor

- `set-sleep-time-ms` - Sets the sleep time between batches. This also requires the new value to be set in the message body

### IdentifierMapper

- `refresh` - Refreshes any caches in use


## Sending a message

Messages can be sent either via the Web UI or via a CLI (see below for details). In either case, the following applies:

- The `<who>` field must exactly match the name of the microservice process (e.g. `identifiermapper`)
- All routing keys should be lowercase
- `all` can be used as the `<who>` keyword to control all services
- A specific service can be messaged by including its `PID` at the end of the routing key. This is currently the only way to control a specific service instance rather than all services of a certain type

Examples of some routing keys:

```text
smi.control.all.stop # Stop all services
smi.control.dicomtagreader.stop # Stop all DicomTagReader services
smi.control.identifiermapper.refresh1234 # Refresh the IdentifierMapper service with PID `1234`
```

Note that some services may take some time to finish their current operation and exit after receiveing a `shutdown` command.


### Via the Web UI

On your RabbitMQ Management interface (`http://<rabbit host>:15672`), click `Exchanges` then `Control Exchange`. Expand the `Publish message` box then enter the message info. Any required content should be entered into the `Payload` box in plain text. Example:

![test](Images/control-queue-publish.PNG)

### Via the CLI

`TODO`

## Implementing A New Control Command Handler

Implement a class which contains a method with the following signature:

```c#
void MyControlHandler(string action, string message)
```

Then, instantiate your class and register its event in your host (must be a subclass of `MicroserviceHost`):

```c#
var controlClass = new MyControlClass(...);
AddControlHandler(controlClass.MyControlHandler);
```

That's it! Now you will be passed the full routing key for any control message addressed to your specific microservice type (i.e. where the `<who>` part of the routing key matches your microservice name), and any message content.

## Control Queues and Cleanup

The actual implementation of the control queues works as follows:

- When each service starts up, it creates a new queue named with its service name and process ID
- It then binds this queue to the global `ControlExchange`. Two bindings are created:
- `smi.control.all.*`: Matches any "send to all" routing keys
- `smi.control.<process_name>.*`: Matches "all services of my type" routing keys
- On shutdown (when the RMQ connection is closed), the control queue should be automatically delted by the server

The creation of the control queue is performed during a single ad-hoc connection, and is not part of the standard Consumer process (for _reasons_). One consequence of this is that if a microservice crashes _after_ the control queue is created, but _before_ the actual subscription to the queue is started (i.e. at some point during startup before RabbitMQAdapter.StartConsumer is called), then the control queue may not be automatically deleted. This isn't really an issue other than causing visual clutter on the RabbitMQ management interface. These dangling queues can be manually deleted with the [TidyQueues](../utils/RabbitMqTidyQueues) utility tool.

6 changes: 3 additions & 3 deletions src/SharedAssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
[assembly: AssemblyCulture("")]

// These should be overwritten by release builds
[assembly: AssemblyVersion("1.2.0")]
[assembly: AssemblyFileVersion("1.2.0")]
[assembly: AssemblyInformationalVersion("1.2.0")] // This one can have the extra build info after it
[assembly: AssemblyVersion("1.2.1")]
[assembly: AssemblyFileVersion("1.2.1")]
[assembly: AssemblyInformationalVersion("1.2.1")] // This one can have the extra build info after it
4 changes: 2 additions & 2 deletions src/common/Smi.Common/Execution/MicroserviceHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,10 @@ protected MicroserviceHost(GlobalOptions globals, bool loadSmiLogConfig = true)
/// Add an event handler to the control message consumer
/// </summary>
/// <param name="handler">Method to call when invoked. Parameters are the action to perform, and the message body</param>
protected void AddControlHandler(Action<string, string> handler)
protected void AddControlHandler(IControlMessageHandler handler)
{
//(a, m) => action, message content
_controlMessageConsumer.ControlEvent += (a, m) => handler(a, m);
_controlMessageConsumer.ControlEvent += handler.ControlMessageHandler;
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public int Main()
catch (Exception e)
{
string nl = Environment.NewLine;
Console.Error.WriteLine($"{e}{nl}{nl}Failed to construct host:{nl}{e.Message}");
Console.Error.WriteLine($"{e}{nl}{nl}Host constructor threw an exception:{nl}{e.Message}");
return -1;
}

Expand All @@ -51,16 +51,14 @@ public int Main()
Console.WriteLine("Bootstrapper -> Host aux connections started, calling Start()");

host.Start();
Console.WriteLine("Bootstrapper -> Host started");
Console.WriteLine("Bootstrapper -> Host created and started...");
}
catch (Exception e)
{
host.Fatal("Failed to start host", e);
return -2;
}

Console.WriteLine("Bootstrapper -> Exiting main");


// Only thing keeping process from exiting after this point are any
// running tasks (i.e. RabbitMQ consumer tasks)
return 0;
Expand Down
Loading

0 comments on commit dc7dba8

Please sign in to comment.