Transmitting HL7v2 messages over TCP/IP connections

This tutorial provides instructions for transmitting HL7v2 messages over TCP/IP connections using the minimal lower layer protocol (MLLP). To require the MLLP image to be signed by an attestor, follow the steps in Transmitting HL7v2 messages over TCP/IP connections using a signed MLLP image.

This tutorial provides instructions for running the open source MLLP adapter hosted in GitHub in the following environments:

Objectives

After completing this tutorial, you'll know how to:

Costs

This tutorial uses billable components of Google Cloud, including:

  • Cloud Healthcare API
  • Google Kubernetes Engine
  • Compute Engine
  • Cloud VPN
  • Pub/Sub

Use the Pricing Calculator to generate a cost estimate based on your projected usage. New Cloud Platform users might be eligible for a free trial.

Before you begin

Before beginning this tutorial, familiarize yourself with the conceptual documentation on minimal lower layer protocol (MLLP) by reviewing MLLP and the Google Cloud MLLP adapter. The conceptual documentation provides an overview of MLLP, how care systems can send and receive messages to and from the Cloud Healthcare API over an MLLP connection, and the basics of MLLP security.

Before you can set up the MLLP adapter, you must choose or create a Google Cloud project and enable the required APIs by completing the following steps:

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the Google Cloud Console, on the project selector page, select or create a Google Cloud project.

    Go to the project selector page

  3. Make sure that billing is enabled for your Cloud project. Learn how to confirm that billing is enabled for your project.

  4. Enable the Cloud Healthcare API, Google Kubernetes Engine, Container Registry, and Pub/Sub APIs.

    Enable the APIs

  5. Wait for the Kubernetes Engine API and related services to be enabled. This can take several minutes.

Choosing a shell

To complete this tutorial, you can use Cloud Shell or your local shell.

Cloud Shell is a shell environment for managing resources hosted on Google Cloud. Cloud Shell comes preinstalled with the gcloud tool and the kubectl tool. The gcloud tool provides the primary command-line interface for GCP. kubectl provides the command-line interface for running commands against GKE clusters.

If you prefer using your local shell, you must install the Cloud SDK, which includes the gcloud tool, and the kubectl tool.

To open Cloud Shell or configure your local shell, complete the following steps:

Cloud Shell

To launch Cloud Shell, complete the following steps:

  1. Go to Google Cloud Console.

    Google Cloud Console

  2. From the top-right corner of the console, click the Activate Google Cloud Shell button:

A Cloud Shell session opens inside a frame at the bottom of the console. You use this shell to run gcloud and kubectl commands.

Local Shell

To install the gcloud tool and the kubectl tool, complete the following steps:

  1. Install and initialize the Cloud SDK.
  2. If you're only testing the adapter locally, you don't need to complete any more steps and can continue to Creating a dataset. If you're deploying the adapter to GKE, install the kubectl command-line tool by running the following command:

    gcloud components install kubectl

Creating a dataset

If you haven't already created a Cloud Healthcare API dataset, create a dataset by completing the following steps:

Console

  1. In the Cloud Console, go to the Datasets page.

    Go to the Datasets page

  2. Click Create dataset.
  3. Choose a dataset identifier that's unique in your project and region. If the identifier is not unique, the dataset creation fails.
  4. Choose the region where the dataset permanently resides and then click Create.

The new dataset appears in the list.

gcloud

To create a dataset, run the gcloud healthcare datasets create command:

gcloud healthcare datasets create DATASET_ID \
    --location=LOCATION

If the request is successful, the command returns the following output:

Create request issued for: [DATASET_ID]
Waiting for operation [OPERATION_ID] to complete...done.
Created dataset [DATASET_ID].

Creating a Pub/Sub topic and subscription

To receive notifications when messages are created or ingested, you need to configure a Pub/Sub topic with your HL7v2 store. For more information, see Configuring Pub/Sub notifications.

To create a topic, complete the following steps:

Console

  1. Go to the Pub/Sub Topics page in the Cloud Console.

    Go to the Pub/Sub topics page

  2. Click Create Topic.

  3. Enter a topic name with the URI:

    projects/PROJECT_ID/topics/TOPIC_NAME

    where PROJECT_ID is your Google Cloud project ID.

  4. Click Create.

gcloud

To create a topic, run the gcloud pubsub topics create command:

gcloud pubsub topics create projects/PROJECT_ID/topics/TOPIC_NAME

If the request is successful, the command returns the following output:

Created topic [projects/PROJECT_ID/topics/TOPIC_NAME].

To create a subscription, complete the following steps:

Console

  1. Go to the Pub/Sub Topics page in the Cloud Console.

    Go to the Pub/Sub topics page

  2. Click your project's topic.

  3. Click Create Subscription.

  4. Enter a subscription name:

    projects/PROJECT_ID/subscriptions/SUBSCRIPTION_NAME

  5. Leave Delivery Type set to Pull and then click Create.

gcloud

To create a subscription, run the gcloud pubsub subscriptions create command:

gcloud pubsub subscriptions create SUBSCRIPTION_NAME \
    --topic=projects/PROJECT_ID/topics/TOPIC_NAME

If the request is successful, the command returns the following output:

Created subscription [projects/PROJECT_ID/subscriptions/SUBSCRIPTION_NAME].

Creating an HL7v2 store configured with a Pub/Sub topic

Create an HL7v2 store and configure it with a Pub/Sub topic. To create an HL7v2 store, you must already have created a dataset. For the purposes of this tutorial, use the same project for your HL7v2 store and for the Pub/Sub topic.

To create an HL7v2 store configured with a Pub/Sub topic, complete the following steps:

curl

curl -X POST \
    --data "{
      'notificationConfigs': [
        {
          'pubsubTopic': 'projects/PROJECT_ID/topics/PUBSUB_TOPIC',
          'filter': ''
        }
      ]
    }" \
    -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
    -H "Content-Type: application/json; charset=utf-8" \
    "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores?hl7V2StoreId=HL7V2_STORE_ID"

If the request is successful, the server returns the response in JSON format:

{
  "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID",
  "notificationConfigs": [
    {
      "pubsubTopic": "projects/PROJECT_ID/topics/PUBSUB_TOPIC"
    }
  ]
}

PowerShell

$cred = gcloud auth application-default print-access-token
$headers = @{ Authorization = "Bearer $cred" }

Invoke-WebRequest `
  -Method Post `
  -Headers $headers `
  -ContentType: "application/json; charset=utf-8" `
  -Body "{
      'notificationConfigs': [
        {
          'pubsubTopic': 'projects/PROJECT_ID/topics/PUBSUB_TOPIC',
          'filter': ''
        }
      ]
  }" `
  -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores?hl7V2StoreId=HL7V2_STORE_ID" | Select-Object -Expand Content

If the request is successful, the server returns the response in JSON format:

{
  "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID",
  "notificationConfigs": [
    {
      "pubsubTopic": "projects/PROJECT_ID/topics/PUBSUB_TOPIC"
    }
  ]
}

Configuring Pub/Sub permissions

To send notifications to Pub/Sub when an HL7v2 message is created or ingested, you need to configure Pub/Sub permissions on the Cloud Healthcare API. This step needs to be done once per project.

To add the required pubsub.publisher role to your project's service account, complete the following steps:

Console

  1. On the IAM page in the Cloud Console, verify that the role Healthcare Service Agent appears in the Role column for the relevant project service account. The account name is service-PROJECT_NUMBER@gcp-sa-healthcare.iam.gserviceaccount.com. For information about finding the PROJECT_NUMBER, see Identifying projects.

  2. In the Inheritance column that matches the role, click the pencil icon. The Edit permissions pane opens.

  3. Click Add another role and then search for the Pub/Sub Publisher role.

  4. Select the role and then click Save. The pubsub.publisher role is added to the service account.

gcloud

To add the service account permissions, run the gcloud projects add-iam-policy-binding command. For information about finding the PROJECT_ID and PROJECT_NUMBER, see Identifying projects.

gcloud projects add-iam-policy-binding PROJECT_ID \
    --member=serviceAccount:service-PROJECT_NUMBER@gcp-sa-healthcare.iam.gserviceaccount.com \
    --role=roles/pubsub.publisher

Pulling the pre-built Docker image

The MLLP adapter is a containerized application staged in a pre-built Docker image in Container Registry.

To pull the latest version of the image, run the following command:

docker pull gcr.io/cloud-healthcare-containers/mllp-adapter:latest

Testing the MLLP adapter locally

When testing the adapter locally, you can configure it to run as a receiver, a publisher, or both. The receiver and publisher configurations have the following key differences:

  • When the adapter runs as a receiver, it receives HL7v2 messages from an external source and calls messages.ingest to ingest the messages into an HL7v2 store, thereby creating a Pub/Sub notification. The notification is sent to applications that are subscribed to the HL7v2 store's Pub/Sub topic.
  • When the adapter runs as a publisher, it listens for HL7v2 messages that were created or ingested in an HL7v2 store using messages.create or messages.ingest. After a message is created, a Pub/Sub notification is sent to the adapter and the adapter publishes the messages to an external receiver.

The following sections show how to run the adapter so that it acts as either a receiver or a publisher.

After verifying that you can run the MLLP adapter on your local machine, you can continue to the next section on Deploying the MLLP adapter to Google Kubernetes Engine.

Testing the MLLP adapter locally as a receiver

When the adapter receives an HL7v2 message from an external source, such as a care center, the adapter calls messages.ingest and ingests the HL7v2 message into the configured HL7v2 store. You can observe this in the source code for the adapter.

To test the adapter locally as a receiver, complete the following steps:

  1. On the machine where you pulled the pre-built Docker image, run the following command:

    docker run \
        --network=host \
        -v ~/.config:/root/.config \
        gcr.io/cloud-healthcare-containers/mllp-adapter \
        /usr/mllp_adapter/mllp_adapter \
        --hl7_v2_project_id=PROJECT_ID \
        --hl7_v2_location_id=LOCATION \
        --hl7_v2_dataset_id=DATASET_ID \
        --hl7_v2_store_id=HL7V2_STORE_ID \
        --export_stats=false \
        --receiver_ip=0.0.0.0 \
        --port=2575 \
        --api_addr_prefix=https://healthcare.googleapis.com:443/v1 \
        --logtostderr
    

    where:

    • PROJECT_ID is the ID for the Google Cloud project containing your HL7v2 store.
    • LOCATION is the region where your HL7v2 store is located.
    • DATASET_ID is the ID for the parent dataset of your HL7v2 store.
    • HL7V2_STORE_ID is the ID for the HL7v2 store to which you're sending HL7v2 messages.

    After running the previous command, the adapter prints a message similar to the following and starts to run on your local machine at the 127.0.0.1 IP address on port 2575:

    I0000 00:00:00.000000      1 healthapiclient.go:171] Dialing connection to https://healthcare.googleapis.com:443/v1
    I0000 00:00:00.000000      1 mllp_adapter.go:89] Either --pubsub_project_id or --pubsub_subscription is not provided, notifications of the new messages are not read and no outgoing messages will be sent to the target MLLP address.
    
  2. To continue with testing while the adapter runs as a foreground process, open a different terminal on your local machine.

  3. In the new terminal, to install Netcat, run the following command:

    sudo apt install netcat
    
  4. Download the hl7v2-mllp-sample.txt file and save it to your local machine.

  5. To send the HL7v2 message to the adapter, in the directory where you downloaded the file, run the following command. The MLLP adapter is listening on your local host on port 2575. The command sends the message through the MLLP adapter into your HL7v2 store:

    echo -n -e "\x0b$(cat hl7v2-mllp-sample.txt)\x1c\x0d" | nc -q1 localhost 2575 | less
    

    If the message was successfully ingested into the HL7v2 store, the command returns the following output:

    ^KMSH|^~\&|TO_APP|TO_FACILITY|FROM_APP|FROM_FACILITY|19700101010000||ACK|c507a97e-438d-44b0-b236-ea95e5ecbbfb|P|2.5^MMSA|AA|20150503223000^\
    

    This output indicates that the HL7v2 store responded with an AA (Application Accept) response type, meaning that the message was validated and successfully ingested.

  6. You can also verify that the message was successfully sent by opening the terminal where you ran the adapter. The output should look like the following sample:

     I0000 00:00:00.000000       1 healthapiclient.go:171] Dialing connection to https://healthcare.googleapis.com:443/v1
     I0000 00:00:00.000000       1 mllp_adapter.go:89] Either --pubsub_project_id or --pubsub_subscription is not provided, notifications of the new messages are not read and no outgoing messages will be sent to the target MLLP address.
     I0213 00:00:00.000000       1 healthapiclient.go:190] Sending message of size 319.
     I0213 00:00:00.000000       1 healthapiclient.go:223] Message was successfully sent.
    
  7. The message is stored in your HL7v2 store, so you can call messages.list to view the message:

    curl

    curl -X GET \
         -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
         -H "Content-Type: application/json; charset=utf-8" \
         "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages"
    

    If the request is successful, the server returns the message's ID in a resource path:

    {
      "hl7V2Messages": [
        {
          "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID"
        }
      ]
    }
    

    PowerShell

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
      -Method Get `
      -Headers $headers `
      -ContentType: "application/json; charset=utf-8" `
      -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages" | Select-Object -Expand Content
    

    If the request is successful, the server returns the message's ID in a resource path:

    {
      "hl7V2Messages": [
        {
          "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID"
        }
      ]
    }
    

Testing the MLLP adapter locally as a publisher

When testing the adapter as a publisher, you create messages by calling messages.create or messages.ingest and supplying a message file as binary data.

The adapter automatically acknowledges Pub/Sub messages sent through messages.create and messages.ingest.

The adapter notifies you when it successfully fetches and sends Pub/Sub messages. The adapter is a Pub/Sub subscriber, so it automatically acknowledges these messages. As a result, they're removed from the message queue in the Pub/Sub subscription that you configured with the adapter.

To pull from the Pub/Sub subscription and separately verify that the messages were published, you need to create a second Pub/Sub subscription assigned to the topic you created previously. The messages sent to the second subscription aren't automatically acknowledged by the adapter and persist so that you can pull them.

To create a second Pub/Sub subscription assigned to the topic you created previously, complete the following steps:

Console

  1. Go to the Pub/Sub Topics page in the Cloud Console.

    Go to the Pub/Sub topics page

  2. Click your project's topic. This is the topic that you used to create the initial subscription.

  3. Click Create Subscription.

  4. Enter a subscription name:

    projects/PROJECT_ID/subscriptions/SECOND_SUBSCRIPTION_NAME

    Leave Delivery Type set to Pull.

  5. Click Create.

gcloud

To create a second Pub/Sub subscription assigned to the topic you created previously, run the gcloud pubsub subscriptions create command:

gcloud pubsub subscriptions create SECOND_SUBSCRIPTION_NAME --topic=projects/PROJECT_ID/topics/TOPIC_NAME

If the request is successful, the command returns the following output:

Created subscription [projects/PROJECT_ID/subscriptions/SECOND_SUBSCRIPTION_NAME].

To test the adapter locally as a publisher, complete the following steps on the machine where you pulled the pre-built Docker image:

  1. Install Netcat:

    sudo apt install netcat
    
  2. Download the hl7v2-mllp-ack-sample.txt file and save it to your local machine. The file contains an ACK message that the adapter requires as a response when it attempts to publish a message.

  3. To cause Netcat to listen for incoming connections on port 2525, in the directory where you downloaded the file, run the following command:

    echo -n -e "\x0b$(cat hl7v2-mllp-ack-sample.txt)\x1c\x0d" | nc -q1 -lv -p 2525 | less
    

    After starting Netcat, an output message similar to the following sample displays:

    listening on [any] 2525 ...
    
  4. Netcat runs as a foreground process, so to continue with testing, open a different terminal on your local machine.

  5. To start the adapter, in the new terminal, run the following command:

    docker run \
        --network=host \
        gcr.io/cloud-healthcare-containers/mllp-adapter \
        /usr/mllp_adapter/mllp_adapter \
        --hl7_v2_project_id=PROJECT_ID \
        --hl7_v2_location_id=LOCATION \
        --hl7_v2_dataset_id=DATASET_ID \
        --hl7_v2_store_id=HL7V2_STORE_ID \
        --export_stats=false \
        --receiver_ip=127.0.0.1 --port 2575 \
        --mllp_addr=127.0.0.1:2525 \
        --pubsub_project_id=PROJECT_ID \
        --pubsub_subscription=PUBSUB_SUBSCRIPTION \
        --api_addr_prefix=https://healthcare.googleapis.com:443/v1 \
        --logtostderr
    

    where:

    • PROJECT_ID is the ID for the Google Cloud project containing your HL7v2 store.
    • LOCATION is the region where your HL7v2 store is located.
    • DATASET_ID is the ID for the parent dataset of your HL7v2 store.
    • HL7V2_STORE_ID is the ID for the HL7v2 store to which you're sending HL7v2 messages.
    • PROJECT_ID is the ID for the Google Cloud project containing the Pub/Sub topic.
    • PUBSUB_SUBSCRIPTION is the name of the first subscription you created that was associated with your Pub/Sub topic. The adapter consumes messages from this subscription and automatically acknowledges them, so to view the messages published to the topic you must pull messages from the second subscription you created previously.

    After running the previous command, the adapter starts to run on your local machine at the 127.0.0.1 IP address on port 2575.

    The adapter runs as a foreground process, so to continue with testing, open a different terminal on your local machine.

  6. Download the hl7v2-sample.json file and save it to your local machine. In the directory where you downloaded the file, call the messages.create method to create the message in an HL7v2 store:

    curl

    To create an HL7v2 message, make a POST request and specify the following information:

    • The name of the parent dataset
    • The name of the HL7v2 store
    • A message
    • An access token

    The following sample shows a POST request using curl and a sample JSON file called hl7v2-sample.json.

    curl -X POST \
         -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
         -H "Content-Type: application/json; charset=utf-8" \
         --data-binary @hl7v2-sample.json \
         "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages"
    

    If the request is successful, the server returns the response in JSON format:

    {
      "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID",
      "data": "TVNIfF5+XCZ8QXxTRU5EX0ZBQ0lMSVRZfEF8QXwyMDE4MDEwMTAwMDAwMHx8VFlQRV5BfDIwMTgwMTAxMDAwMDAwfFR8MC4wfHx8QUF8fDAwfEFTQ0lJDUVWTnxBMDB8MjAxODAxMDEwNDAwMDANUElEfHwxNAExMTFeXl5eTVJOfDExMTExMTExXl5eXk1STn4xMTExMTExMTExXl5eXk9SR05NQlI=",
      "sendFacility": "SEND_FACILITY",
      "sendTime": "2018-01-01T00:00:00Z",
      "messageType": "TYPE",
      "createTime": "1970-01-01T00:00:00Z",
      "patientIds": [
        {
          "value": "14\u0001111",
          "type": "MRN"
        },
        {
          "value": "11111111",
          "type": "MRN"
        },
        {
          "value": "1111111111",
          "type": "ORGNMBR"
        }
      ]
    }
    

    PowerShell

    To create an HL7v2 message, make a POST request and specify the following information:

    • The name of the parent dataset
    • The name of the HL7v2 store
    • A message
    • An access token

    The following sample shows a POST request using Windows PowerShell and a sample JSON file called hl7v2-sample.json.

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
      -Method Post `
      -Headers $headers `
      -ContentType: "application/json; charset=utf-8" `
      -InFile hl7v2-sample.json `
      -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages" | Select-Object -Expand Content
    

    If the request is successful, the server returns the response in JSON format:

    {
      "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID",
      "data": "TVNIfF5+XCZ8QXxTRU5EX0ZBQ0lMSVRZfEF8QXwyMDE4MDEwMTAwMDAwMHx8VFlQRV5BfDIwMTgwMTAxMDAwMDAwfFR8MC4wfHx8QUF8fDAwfEFTQ0lJDUVWTnxBMDB8MjAxODAxMDEwNDAwMDANUElEfHwxNAExMTFeXl5eTVJOfDExMTExMTExXl5eXk1STn4xMTExMTExMTExXl5eXk9SR05NQlI=",
      "sendFacility": "SEND_FACILITY",
      "sendTime": "2018-01-01T00:00:00Z",
      "messageType": "TYPE",
      "createTime": "1970-01-01T00:00:00Z",
      "patientIds": [
        {
          "value": "14\u0001111",
          "type": "MRN"
        },
        {
          "value": "11111111",
          "type": "MRN"
        },
        {
          "value": "1111111111",
          "type": "ORGNMBR"
        }
      ]
    }
    

    After creating the message, the MLLP adapter returns the following response:

    I0214 00:00:00.000000       1 healthapiclient.go:266] Started to fetch message.
    I0214 00:00:00.000000       1 healthapiclient.go:283] Message was successfully fetched.
    
  7. In the terminal where you ran Netcat, an output similar to the following sample displays. This output indicates that the message was published:

    connect to [127.0.0.1] from localhost [127.0.0.1] 39522
    ^KMSH|^~\&|A|SEND_FACILITY|A|A|20180101000000||TYPE^A|20180101000000|T|0.0|||AA||00|ASCII^MEVN|A00|20180101040000^MPID||14^A111^^^^MRN|11111111^^^^MRN~1111111111^^^^ORGNMBR^\
    

    This corresponds to the value in the data field of the response that you received when you created the message. It's the same as the data value in the hl7v2-sample.json file.

  8. To view the message that the adapter published to the Pub/Sub topic, run the gcloud pubsub subscriptions pull command on the second Pub/Sub subscription you created:

    gcloud pubsub subscriptions pull --auto-ack SECOND_SUBSCRIPTION
    

    The command returns the following output about the created HL7v2 message. Note the publish=true value in the ATTRIBUTES column, which indicates that the message was published to Pub/Sub:

    ┌-----------------------------------------------------------------------------------------------------------------|-----------------|---------------┐
    |                                                               DATA                                              |    MESSAGE_ID   |   ATTRIBUTES  |
    ├-----------------------------------------------------------------------------------------------------------------|-----------------|---------------|
    | projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/HL7V2_MESSAGE_ID | 123456789012345 | msgType=ADT   |
    |                                                                                                                 |                 | publish=true  |
    └-----------------------------------------------------------------------------------------------------------------|-----------------|---------------┘
    

Publishing messages to different external receivers

You can configure the HL7v2 store with multiple Pub/Sub topics and use filters to send notifications to different Pub/Sub topics. You can then run an MLLP adapter for each Pub/Sub topic to publish the messages to a different external receiver.

To configure the HL7v2 store with multiple Pub/Sub topics and a filter for each topic, complete the following steps:

  1. Create two Pub/Sub topics and a subscription for each topic. For more information, see Creating a Pub/Sub topic and subscription.

  2. Run the following command:

    curl

    curl -X PATCH \
        -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
        -H "Content-Type: application/json; charset=utf-8" \
        --data "{
          'notificationConfigs': [
              {
                  'pubsubTopic': 'projects/PROJECT_ID/topics/PUBSUB_TOPIC',
                  'filter' : 'sendFacility=\"SEND_FACILITY_1\"'
              },
              {
                  'pubsubTopic': 'projects/PROJECT_ID/topics/SECOND_PUBSUB_TOPIC',
                  'filter': 'sendFacility=\"SEND_FACILITY_2\"'
              }
          ]
        }" "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID?updateMask=notificationConfigs"
    

    If the request is successful, the server returns the response in JSON format:

    {
      "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID",
      "notificationConfigs": [
        {
          "pubsubTopic": "projects/PROJECT_ID/topics/PUBSUB_TOPIC",
          "filter": "sendFacility=\"SEND_FACILITY_1\""
        },
        {
          "pubsubTopic": "projects/PROJECT_ID/topics/SECOND_PUBSUB_TOPIC",
          "filter": "sendFacility=\"SEND_FACILITY_2\""
        }
      ]
    }
    

    PowerShell

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
      -Method Patch `
      -Headers $headers `
      -ContentType: "application/json; charset=utf-8" `
      -Body "{
          'notificationConfigs': [
            {
              'pubsubTopic' : 'projects/PROJECT_ID/topics/PUBSUB_TOPIC',
              'filter': 'sendFacility=\"SEND_FACILITY_1\"'
            },
            {
              'pubsubTopic' : 'projects/PROJECT_ID/topics/SECOND_PUBSUB_TOPIC',
              'filter' : 'sendFacility=\"SEND_FACILITY_2\"'
            }
          ]
      }" `
      -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores?hl7V2StoreId=HL7V2_STORE_ID?updateMask=notificationConfigs" | Select-Object -Expand Content
    

    If the request is successful, the server returns the response in JSON format:

    {
      "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID",
      "notificationConfigs": [
        {
          "pubsubTopic": "projects/PROJECT_ID/topics/PUBSUB_TOPIC",
          "filter": "sendFacility=\"SEND_FACILITY_1\""
        },
        {
          "pubsubTopic": "projects/PROJECT_ID/topics/SECOND_PUBSUB_TOPIC",
          "filter": "sendFacility=\"SEND_FACILITY_2\""
        }
      ]
    }
    

Testing the message routing

To test the message routing, complete the steps in the following sections.

Configuring and starting the first receiver and adapter

To configure and start the first receiver and adapter, complete the following steps:

  1. On the machine where you pulled the pre-built Docker image, run the following command to install Netcat:

    sudo apt install netcat
    
  2. Download hl7v2-mllp-ack-sample.txt, if you haven't done so. The file contains an ACK message used as a response by the adapter when it attempts to publish a message.

  3. To set port 2525 for the first receiver, run the following command:

    echo -n -e "\x0b$(cat hl7v2-mllp-ack-sample.txt)\x1c\x0d" | nc -q1 -lv -p 2525 | less
    

    When the Netcat process starts, the following output displays:

    listening on [any] 2525 ...
    
  4. To start the first adapter, in a new terminal, run the following command:

    docker run \
        --network=host \
        gcr.io/cloud-healthcare-containers/mllp-adapter \
        /usr/mllp_adapter/mllp_adapter \
        --hl7_v2_project_id=PROJECT_ID \
        --hl7_v2_location_id=LOCATION \
        --hl7_v2_dataset_id=DATASET_ID \
        --hl7_v2_store_id=HL7V2_STORE_ID \
        --export_stats=false \
        --receiver_ip=127.0.0.1 --port 2575 \
        --mllp_addr=127.0.0.1:2525 \
        --pubsub_project_id=PROJECT_ID \
        --pubsub_subscription=PUBSUB_SUBSCRIPTION \
        --api_addr_prefix=https://healthcare.googleapis.com:443/v1 \
        --logtostderr
    

    where:

    • PROJECT_ID is the ID for the Google Cloud project containing your HL7v2 store.
    • LOCATION is the region where your HL7v2 store is located.
    • DATASET_ID is the ID for the parent dataset of your HL7v2 store.
    • HL7V2_STORE_ID is the ID for the HL7v2 store to which you're sending HL7v2 messages.
    • PROJECT_ID is the ID for the Google Cloud project containing the Pub/Sub topic.
    • PUBSUB_SUBSCRIPTION is the name of the first subscription you created that is associated with your first Pub/Sub topic. The adapter consumes messages from this subscription and automatically acknowledges them.

    After running this command, the adapter starts to run on your local machine on 127.0.0.1:2575. It publishes new messages to the first external receiver on port 2525.

Configuring and starting the second receiver and adapter

To configure and start the second receiver and adapter, complete the following steps:

  1. On the machine where you pulled the pre-built Docker image, run the following command to install Netcat:

    sudo apt install netcat
    
  2. Download hl7v2-mllp-ack-sample.txt, if you haven't done so. The file contains an ACK message used as a response by the adapter when it attempts to publish a message.

  3. To set port 2526 for the second receiver, run the following command:

    echo -n -e "\x0b$(cat hl7v2-mllp-ack-sample.txt)\x1c\x0d" | nc -q1 -lv -p 2526 | less
    

    When the Netcat process starts, the following output displays:

    listening on [any] 2526 ...
    
  4. To start the second adapter, in a new terminal, run the following command:

    docker run \
        --network=host \
        gcr.io/cloud-healthcare-containers/mllp-adapter \
        /usr/mllp_adapter/mllp_adapter \
        --hl7_v2_project_id=PROJECT_ID \
        --hl7_v2_location_id=LOCATION \
        --hl7_v2_dataset_id=DATASET_ID \
        --hl7_v2_store_id=HL7V2_STORE_ID \
        --export_stats=false \
        --receiver_ip=127.0.0.1 --port 2576 \
        --mllp_addr=127.0.0.1:2526 \
        --pubsub_project_id=PROJECT_ID \
        --pubsub_subscription=SECOND_PUBSUB_SUBSCRIPTION \
        --api_addr_prefix=https://healthcare.googleapis.com:443/v1 \
        --logtostderr
    

    where:

    • PROJECT_ID is the ID for the Google Cloud project containing your HL7v2 store.
    • LOCATION is the region where your HL7v2 store is located.
    • DATASET_ID is the ID for the parent dataset of your HL7v2 store.
    • HL7V2_STORE_ID is the ID for the HL7v2 store to which you're sending HL7v2 messages.
    • PROJECT_ID is the ID for the Google Cloud project containing the Pub/Sub topic.
    • SECOND_PUBSUB_SUBSCRIPTION is the name of the second subscription you created that is associated with your second Pub/Sub topic. The adapter consumes messages from this subscription and automatically acknowledges them.

    After running this command, the adapter starts to run on your local machine on port 127.0.0.1:2576 IP address. It publishes new messages to the second external receiver on port 2526.

Publishing a message to the first receiver

To create a message that will only be published to the first external receiver, complete the following steps:

  1. Download hl7v2-sample1.json.

  2. In the directory where you downloaded hl7v2-sample1.json, call the messages.create method to create the message in an HL7v2 store:

    curl

    To create an HL7v2 message, make a POST request and specify the following information:

    • The name of the parent dataset
    • The name of the HL7v2 store
    • A message
    • An access token

    The following sample shows a POST request using curl and a sample JSON file, hl7v2-sample1.json.

    curl -X POST \
        -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
        -H "Content-Type: application/json; charset=utf-8" \
        --data-binary @hl7v2-sample1.json \
        "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages"
    

    If the request is successful, the server returns the response in JSON format:

    {
     "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID",
     "data": "TVNIfF5+XCZ8QXxTRU5EX0ZBQ0lMSVRZXzF8QXxBfDIwMTgwMTAxMDAwMDAwfHxUWVBFXkF8MjAxODAxMDEwMDAwMDB8VHwwLjB8fHxBQXx8MDB8QVNDSUkNRVZOfEEwMHwyMDE4MDEwMTA0MDAwMA1QSUR8fDE0ATExMV5eXl5NUk58MTExMTExMTFeXl5eTVJOfjExMTExMTExMTFeXl5eT1JHTk1CUg==",
     "sendFacility": "SEND_FACILITY_1",
     "sendTime": "2018-01-01T00:00:00Z",
     "messageType": "TYPE",
     "createTime": "1970-01-01T00:00:00Z",
     "patientIds": [
       {
         "value": "14\u0001111",
         "type": "MRN"
       },
       {
         "value": "11111111",
         "type": "MRN"
       },
       {
         "value": "1111111111",
         "type": "ORGNMBR"
       }
     ]
    }
    

    PowerShell

    To create an HL7v2 message, make a POST request and specify the following information:

    • The name of the parent dataset
    • The name of the HL7v2 store
    • A message
    • An access token

    The following sample shows a POST request using Windows PowerShell and a sample JSON file called hl7v2-sample1.json.

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
     -Method Post `
     -Headers $headers `
     -ContentType: "application/json; charset=utf-8" `
     -InFile hl7v2-sample1.json `
     -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages" | Select-Object -Expand Content
    

    If the request is successful, the server returns the response in JSON format:

    {
     "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID",
     "data": "TVNIfF5+XCZ8QXxTRU5EX0ZBQ0lMSVRZXzF8QXxBfDIwMTgwMTAxMDAwMDAwfHxUWVBFXkF8MjAxODAxMDEwMDAwMDB8VHwwLjB8fHxBQXx8MDB8QVNDSUkNRVZOfEEwMHwyMDE4MDEwMTA0MDAwMA1QSUR8fDE0ATExMV5eXl5NUk58MTExMTExMTFeXl5eTVJOfjExMTExMTExMTFeXl5eT1JHTk1CUg==",
     "sendFacility": "SEND_FACILITY_1",
     "sendTime": "2018-01-01T00:00:00Z",
     "messageType": "TYPE",
     "createTime": "1970-01-01T00:00:00Z",
     "patientIds": [
       {
         "value": "14\u0001111",
         "type": "MRN"
       },
       {
         "value": "11111111",
         "type": "MRN"
       },
       {
         "value": "1111111111",
         "type": "ORGNMBR"
       }
     ]
    }
    

    In this response, sendFacility is set to SEND_FACILITY_1, therefore the Pub/Sub notification is only sent to the first Pub/Sub topic. After creating the message, the first MLLP adapter returns the following response:

    I0214 00:00:00.000000       1 healthapiclient.go:266] Started to fetch message.
    I0214 00:00:00.000000       1 healthapiclient.go:283] Message was successfully fetched.
    

    The second MLLP adapter does not return any response because no notification is sent to the second Pub/Sub topic.

    In the terminal where you ran the first Netcat process, the following output displays. This output indicates that the message was published.

    connect to [127.0.0.1] from localhost [127.0.0.1] 39522
    ^KMSH|^~\&|A|SEND_FACILITY_1|A|A|20180101000000||TYPE^A|20180101000000|T|0.0|||AA||00|ASCII^MEVN|A00|20180101040000^MPID||14^A111^^^^MRN|11111111^^^^MRN~1111111111^^^^ORGNMBR^\
    

    This output corresponds to the value in the data field of the response that you received when you created the message. It's the same as the data value in the hl7v2-sample1.json file.

Publishing a message to the second receiver

To create a message that will only be published to the second external receiver, complete the following steps:

  1. Open a new terminal on your local machine.

  2. To create a message that will only be published to the second external receiver, download hl7v2-sample2.json.

  3. In the directory where you downloaded hl7v2-sample2.json, call the messages.create method to create the message in an HL7v2 store:

    curl

    To create an HL7v2 message, make a POST request and specify the following information:

    • The name of the parent dataset
    • The name of the HL7v2 store
    • A message
    • An access token

    The following sample shows a POST request using curl and a sample JSON file, hl7v2-sample2.json.

    curl -X POST \
        -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
        -H "Content-Type: application/json; charset=utf-8" \
        --data-binary @hl7v2-sample2.json \
        "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages"
    

    If the request is successful, the server returns the response in JSON format:

    {
     "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID",
     "data": "TVNIfF5+XCZ8QXxTRU5EX0ZBQ0lMSVRZXzJ8QXxBfDIwMTgwMTAxMDAwMDAwfHxUWVBFXkF8MjAxODAxMDEwMDAwMDB8VHwwLjB8fHxBQXx8MDB8QVNDSUkNRVZOfEEwMHwyMDE4MDEwMTA0MDAwMA1QSUR8fDE0ATExMV5eXl5NUk58MTExMTExMTFeXl5eTVJOfjExMTExMTExMTFeXl5eT1JHTk1CUg==",
     "sendFacility": "SEND_FACILITY_2",
     "sendTime": "2018-01-01T00:00:00Z",
     "messageType": "TYPE",
     "createTime": "1970-01-01T00:00:00Z",
     "patientIds": [
       {
         "value": "14\u0001111",
         "type": "MRN"
       },
       {
         "value": "11111111",
         "type": "MRN"
       },
       {
         "value": "1111111111",
         "type": "ORGNMBR"
       }
     ]
    }
    

    PowerShell

    To create an HL7v2 message, make a POST request and specify the following information:

    • The name of the parent dataset
    • The name of the HL7v2 store
    • A message
    • An access token

    The following sample shows a POST request using Windows PowerShell and a sample JSON file, hl7v2-sample2.json.

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
     -Method Post `
     -Headers $headers `
     -ContentType: "application/json; charset=utf-8" `
     -InFile hl7v2-sample2.json `
     -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages" | Select-Object -Expand Content
    

    If the request is successful, the server returns the response in JSON format:

    {
     "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID",
     "data": "TVNIfF5+XCZ8QXxTRU5EX0ZBQ0lMSVRZXzJ8QXxBfDIwMTgwMTAxMDAwMDAwfHxUWVBFXkF8MjAxODAxMDEwMDAwMDB8VHwwLjB8fHxBQXx8MDB8QVNDSUkNRVZOfEEwMHwyMDE4MDEwMTA0MDAwMA1QSUR8fDE0ATExMV5eXl5NUk58MTExMTExMTFeXl5eTVJOfjExMTExMTExMTFeXl5eT1JHTk1CUg==",
     "sendFacility": "SEND_FACILITY_2",
     "sendTime": "2018-01-01T00:00:00Z",
     "messageType": "TYPE",
     "createTime": "1970-01-01T00:00:00Z",
     "patientIds": [
       {
         "value": "14\u0001111",
         "type": "MRN"
       },
       {
         "value": "11111111",
         "type": "MRN"
       },
       {
         "value": "1111111111",
         "type": "ORGNMBR"
       }
     ]
    }
    

    Note that the sendFacility is SEND_FACILITY_2, therefore the Pub/Sub notification is only sent to the second Pub/Sub topic. After creating the message, the first MLLP adapter does not return any response, while the second MLLP adapter returns the following response:

    I0214 00:00:00.000000       1 healthapiclient.go:266] Started to fetch message.
    I0214 00:00:00.000000       1 healthapiclient.go:283] Message was successfully fetched.
    

    In the terminal where you ran the second Netcat process, the following output displays. This output indicates that the message was published.

    connect to [127.0.0.1] from localhost [127.0.0.1] 39522
    ^KMSH|^~\&|A|SEND_FACILITY_2|A|A|20180101000000||TYPE^A|20180101000000|T|0.0|||AA||00|ASCII^MEVN|A00|20180101040000^MPID||14^A111^^^^MRN|11111111^^^^MRN~1111111111^^^^ORGNMBR^\
    

    This output corresponds to the value in the data field of the response that you received when you created the message. It's the same as the data value in the hl7v2-sample2.json file.

Deploying the MLLP adapter to Google Kubernetes Engine

When transmitting HL7v2 messages over MLLP from your care center, one possible configuration is to send the messages to an adapter that is deployed in Google Cloud and can forward them to the Cloud Healthcare API.

The MLLP adapter runs as a stateless application on a GKE cluster. A GKE cluster is a managed group of VM instances for running containerized applications. Stateless applications are applications that don't store data or application state to the cluster or to persistent storage. Instead, data and application state stay with the client, which makes stateless applications more scalable.

GKE uses the Deployment controller to deploy stateless applications as uniform, non-unique Pods. Deployments manage the desired state of your application: how many Pods should run your application, what version of the container image should run, what the Pods should be labelled, and so on. The desired state can be changed dynamically through updates to the deployment's Pod specification.

At the same time that you deploy the adapter, you create a Service controller that allows you to connect the adapter to the Cloud Healthcare API using internal load balancing.

If you're new to GKE, you should complete the GKE quickstart to learn how the product works.

Adding Pub/Sub API permissions to the GKE service account

As stated in the GKE documentation on Authenticating to Cloud Platform with service accounts, each node in a container cluster is a Compute Engine instance. Therefore, when the MLLP adapter runs on a container cluster, it automatically inherits the scopes of the Compute Engine instances to which it is deployed.

Google Cloud automatically creates a service account named "Compute Engine default service account" and GKE associates this service account with the nodes that GKE creates. Depending on how your project is configured, the default service account might or might not have permissions to use other Cloud Platform APIs. GKE also assigns some limited access scopes to Compute Engine instances.

For best results, don't authenticate to other Google Cloud services (such as Pub/Sub) from Pods running on GKE by updating the default service account's permissions or assigning more access scopes to Compute Engine instances. Instead, create your own service accounts.

You have to grant the necessary Pub/Sub permissions to the container cluster, but you also have the option of granting permissions to write metrics to Cloud Monitoring.

To create a new service account that contains only the scopes that the container cluster requires, complete the following steps:

Console

  1. In the Cloud Console, go to the Create service account key page.

    Go to the Create Service Account Key page

  2. From the Service account list, select New service account.

  3. In the Service account name field, enter a name.

  4. From the Role list, select the following roles:

    • Pub/Sub > Pub/Sub Subscriber
    • Cloud Healthcare > Healthcare HL7v2 Message Ingest
  5. If you want to enable monitoring, select the following additional role:

    • Monitoring > Monitoring Metric Writer
  6. Click Create.

gcloud

  1. To create the service account, run the gcloud iam service-accounts create command.

    gcloud iam service-accounts create SERVICE_ACCOUNT_NAME
    

    The output is the service account:

    Created service account SERVICE_ACCOUNT_NAME.
    
  2. To grant each role to the service account, run the gcloud projects add-iam-policy-binding command.

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
      --role=roles/pubsub.publisher
    
    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
      --role=roles/healthcare.hl7V2Ingest
    
    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
      --role=roles/monitoring.metricWriter
    

    The output includes the updated policy:

    bindings:
        - members:
            - user:SERVICE_ACCOUNT_NAME
            role: roles/pubsub.publisher
        - members:
            - user:SERVICE_ACCOUNT_NAME
            roles/healthcare.hl7V2Ingest
        - members:
            - user:SERVICE_ACCOUNT_NAME
            roles/monitoring.metricWriter
        etag: ETAG
        version: 1
    
  3. To create a service account key, run the gcloud iam service-accounts keys create command.

    gcloud iam service-accounts keys create ~/FILENAME.json \
       --iam-account=SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
    

    After the service account is created, a JSON key file containing the credentials of the service account is downloaded to your computer. You'll use this key file to configure the MLLP adapter to authenticate to the Cloud Healthcare API, Pub/Sub API, and Cloud Monitoring API using the --service-account flag during cluster creation.

    created key [e44da1202f82f8f4bdd9d92bc412d1d8a837fa83] of type [json] as
        [/usr/home/USERNAME/FILENAME.json] for
        [SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com]
    

Creating the cluster

To create the cluster in GKE, run the gcloud container clusters create command:

gcloud container clusters create mllp-adapter \
    --zone=COMPUTE_ZONE \
    --service-account CLIENT_EMAIL

where:

  • COMPUTE_ZONE is the zone in which your cluster is deployed. A zone is an approximate regional location in which your clusters and their resources live. For example, us-west1-a is a zone in the us-west region. If you've set a default zone using gcloud config set compute/zone, the value of this flag overrides the default.
  • CLIENT_EMAIL is the identifier for the service account. You can find this email address in the service account key file in the "client_email": field. It has the format SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com.

The command returns output similar to the following sample:

Creating cluster mllp-adapter in COMPUTE_ZONE...
Cluster is being configured...
Cluster is being deployed...
Cluster is being health-checked...
Cluster is being health-checked (master is healthy)...done.
Created [https://container.googleapis.com/v1/projects/PROJECT_ID/zones/COMPUTE_ZONE/clusters/mllp-adapter].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/COMPUTE_ZONE/mllp-adapter?project=PROJECT_ID
kubeconfig entry generated for mllp-adapter.
NAME          LOCATION       MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
mllp-adapter  COMPUTE_ZONE   1.11.7-gke.4    203.0.113.1    n1-standard-1  1.11.7-gke.4  3          RUNNING

After creating the cluster, GKE creates three Compute Engine VM instances. You can verify this by listing the instances with the following command:

gcloud compute instances list

Configuring the deployment

When deploying an application to GKE, you define properties of the deployment using a deployment manifest file, which is typically a YAML file. For a sample, see Creating a deployment.

  1. Open a separate terminal.

  2. Using a text editor, create a deployment manifest file called mllp_adapter.yaml with the following content:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mllp-adapter-deployment
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mllp-adapter
  template:
    metadata:
      labels:
        app: mllp-adapter
    spec:
      containers:
        - name: mllp-adapter
          imagePullPolicy: Always
          image: gcr.io/cloud-healthcare-containers/mllp-adapter
          ports:
            - containerPort: 2575
              protocol: TCP
              name: "port"
          command:
            - "/usr/mllp_adapter/mllp_adapter"
            - "--port=2575"
            - "--hl7_v2_project_id=PROJECT_ID"
            - "--hl7_v2_location_id=LOCATION"
            - "--hl7_v2_dataset_id=DATASET_ID"
            - "--hl7_v2_store_id=HL7V2_STORE_ID"
            - "--api_addr_prefix=https://healthcare.googleapis.com:443/v1"
            - "--logtostderr"
            - "--receiver_ip=RECEIVER_IP"

where:

  • PROJECT_ID is the ID for the Google Cloud project containing your HL7v2 store.
  • LOCATION is the region where your HL7v2 store is located.
  • DATASET_ID is the ID for the parent dataset of your HL7v2 store.
  • HL7V2_STORE_ID is the ID for the HL7v2 store to which you're sending HL7v2 messages.
  • RECEIVER_IP is the IP address for the originating care center. If you're testing from a local machine that is acting as the care center, set RECEIVER_IP to 0.0.0.0.

The deployment has the following properties:

  • spec: replicas: is the number of replicated pods that the deployment manages.
  • spec: template: metadata: labels: is the label given to each Pod, which the deployment uses to manage the pods.
  • spec: template: spec: is the Pod specification, which defines how each Pod should run.
  • spec: containers includes the name of the container to run in each Pod and the container image that should run.

For more information about the deployment specification, see the Deployment API reference.

Configuring the Service

To make the MLLP adapter accessible to applications outside of the cluster (such as a care center), you must configure an internal load balancer.

If you haven't configured a VPN, applications can access the MLLP adapter through the internal load balancer as long as the applications use the same VPC network and are located in the same Google Cloud region. For example, to make the adapter accessible to a Compute Engine VM instance in the same region and on the same VPC network, you could add an internal load balancer to the cluster's Service resource.

In the directory where you created the deployment manifest file, use the text editor to create a Service manifest file called mllp_adapter_service.yaml with the following content. This file is responsible for configuring internal load balancing:

apiVersion: v1
kind: Service
metadata:
  name: mllp-adapter-service
  annotations:
    cloud.google.com/load-balancer-type: "Internal"
spec:
  type: LoadBalancer
  ports:
  - name: port
    port: 2575
    targetPort: 2575
    protocol: TCP
  selector:
    app: mllp-adapter

The Service has the following properties:

  • metadata: name: is the name you choose for the Service. In this case, it's mllp-adapter-service.
  • metadata: annotations: is an annotation that specifies that an internal load balancer is to be configured.
  • spec: type: is the type of load balancer.
  • ports: port: is used to specify the port on which the service can receive traffic from other services in the same cluster. The default MLLP port of 2575 is used.
  • ports: targetPort: is used to specify the port on each Pod where the service is running.
  • spec: selector: app: specifies the Pods that the Service targets.

Although it's possible to specify an IP address for the load balancer (using the clusterIP field), the load balancer can generate its own IP address to which you can send messages. For now, allow the cluster to generate the IP address, which you'll use later on in this tutorial.

For more information on internal load balancing, see the GKE documentation.

For more information about the Service specification, see the Service API reference.

Deploying the deployment

To deploy the adapter to a GKE cluster, in the directory containing the mllp_adapter.yaml deployment manifest file, run the following command:

kubectl apply -f mllp_adapter.yaml

The command returns the following output:

deployment.extensions "mllp-adapter-deployment" created

Inspecting the deployment

After you create the deployment, you can use the kubectl tool to inspect it.

To get detailed information about the deployment, run the following command:

kubectl describe deployment mllp-adapter

To list the Pod created by the deployment, run the following command:

kubectl get pods -l app=mllp-adapter

To get information about the created Pod:

kubectl describe pod POD_NAME

If the deployment was successful, the last part of the output from the previous command should contain the following information:

Events:
  Type    Reason     Age   From                                                  Message
  ----    ------     ----  ----                                                  -------
  Normal  Scheduled  1m    default-scheduler                                     Successfully assigned default/mllp-adapter-deployment-85b46f8-zxw68 to gke-mllp-adapter-default-pool-9c42852d-95sn
  Normal  Pulling    1m    kubelet, gke-mllp-adapter-default-pool-9c42852d-95sn  pulling image "gcr.io/cloud-healthcare-containers/mllp-adapter"
  Normal  Pulled     1m    kubelet, gke-mllp-adapter-default-pool-9c42852d-95sn  Successfully pulled image "gcr.io/cloud-healthcare-containers/mllp-adapter"
  Normal  Created    1m    kubelet, gke-mllp-adapter-default-pool-9c42852d-95sn  Created container
  Normal  Started    1m    kubelet, gke-mllp-adapter-default-pool-9c42852d-95sn  Started container

Deploying the Service and creating the internal load balancer

To create the internal load balancer, in the directory containing the mllp_adapter_service.yaml Service manifest file, run the following command:

kubectl apply -f mllp_adapter_service.yaml

The command returns the following output:

service "mllp-adapter-service" created

Inspecting the Service

After creating the Service, inspect it to verify that it has been configured successfully.

To inspect the internal load balancer, run the following command:

kubectl describe service mllp-adapter-service

The command's output is similar to the following sample:

Name:                     mllp-adapter-service
Namespace:                default
Labels:                   <none>
Annotations:              cloud.google.com/load-balancer-type=Internal
                          kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"v1","kind":"Service","metadata":{"annotations":{"cloud.google.com/load-balancer-type":"Internal"},"name":"mllp-adapter-service","namespa...
Selector:                 app=mllp-adapter
Type:                     LoadBalancer
IP:                       203.0.113.1
LoadBalancer Ingress:     203.0.113.1
Port:                     port  2575/TCP
TargetPort:               2575/TCP
NodePort:                 port  30660/TCP
Endpoints:                <none>
Session Affinity:         None
External Traffic Policy:  Cluster
Events:
  Type    Reason                Age   From                Message
  ----    ------                ----  ----                -------
  Normal  EnsuringLoadBalancer  1m    service-controller  Ensuring load balancer
  Normal  EnsuredLoadBalancer   1m    service-controller  Ensured load balancer

The LoadBalancer Ingress IP address might take up to a minute to populate. You'll use this IP address and the 2575 port to access the Service from outside the cluster in the next step.

Creating a Compute Engine VM and sending messages

Whereas earlier in this tutorial you tested the MLLP adapter locally and sent HL7v2 messages to your HL7v2 store, you'll now send messages from a Compute Engine VM to the MLLP adapter running on GKE. The messages are then forwarded to an HL7v2 store.

To send requests from the new instance to the GKE cluster, the instance and the existing instances must be in the same region and use the same VPC network.

At the end of this section, you'll list the notifications published to your Pub/Sub topic and the HL7v2 messages in your HL7v2 store. The Compute Engine VM instance must be granted permissions to perform these tasks. Before creating the instance, create a new service account with the required permissions by completing the following steps:

Console

  1. In the Cloud Console, go to the Create service account key page.

    Go to the Create Service Account Key page

  2. From the Service account list, select New service account.

  3. In the Service account name field, enter a name.

  4. From the Role list, select the following two roles:

    • Pub/Sub > Pub/Sub Subscriber
    • Cloud Healthcare > Healthcare HL7v2 Message Consumer
  5. Click Create.

After the service account is created, a JSON key file containing the credentials of the service account is downloaded to your computer.

gcloud

  1. To create the service account, run the gcloud iam service-accounts create command.

    gcloud iam service-accounts create SERVICE_ACCOUNT_NAME
    

    The output is the service account:

    Created service account SERVICE_ACCOUNT_NAME.
    
  2. To grant each role to the service account, run the gcloud projects add-iam-policy-binding command.

    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
      --role=roles/pubsub.publisher
    
    gcloud projects add-iam-policy-binding PROJECT_ID \
      --member=serviceAccount:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com \
      --role=roles/healthcare.hl7V2Consumer
    

    The output includes the updated policy:

    bindings:
        - members:
            - user:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
            role: roles/pubsub.publisher
        - members:
            - user:SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
            roles/healthcare.hl7V2Consumer
        etag: ETAG
        version: 1
    
  3. To create a service account key, run the gcloud iam service-accounts keys create command.

    gcloud iam service-accounts keys create ~/FILENAME.json \
       --iam-account=SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com
    

    After the service account is created, a JSON key file containing the credentials of the service account is downloaded to your computer.

The following steps show how to create a Linux virtual machine instance in Compute Engine:

Console

  1. In the Cloud Console, go to the VM Instances page.

    Go to the VM Instances page

  2. Click Create instance.

  3. Choose a Region and Zone for the instance that matches the zone you selected when you created the cluster. For example, if you used us-central1-a for the COMPUTE_ZONE when you created the cluster, then in the instance creation screen, select us-central1 (Iowa) for the Region and us-central1-a for the Zone.

  4. In the Boot disk section, click Change to begin configuring your boot disk.

  5. On the Public images tab, choose version 9 of the Debian operating system.

  6. Click Select.

  7. In the Identity and API access section, select the service account you created.

  8. In the Firewall section, select Allow HTTP traffic.

  9. Click Create to create the instance.

gcloud

To create a compute instance, run the gcloud compute instances create method with the following options:

  • The ZONE that you selected when you created the cluster
  • The http-server tag to allow HTTP traffic
  • The SERVICE_ACCOUNT that you created
gcloud compute instances create COMPUTE_NAME \
   --project=PROJECT_ID \
   --zone=ZONE \
   --image-family=debian-9 \
   --image-project=debian-cloud \
   --tags=http-server \
   --service-account=SERVICE_ACCOUNT

The output is similar to the following sample:

Created [https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/COMPUTE_NAME].
NAME          ZONE           MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS
COMPUTE_NAME  ZONE           n1-standard-1               INTERNAL_IP  EXTERNAL_IP    RUNNING

Allow a short time for the instance to start up. After the instance is started, it's listed on the VM Instances page with a green status icon.

By default, the instance uses the same default VPC network that the cluster uses, which means that traffic can be sent from the instance to the cluster.

To connect to the instance, complete the following steps:

Console

  1. In the Cloud Console, go to the VM Instances page.

    Go to the VM Instances page

  2. In the list of virtual machine instances, click SSH in the row of the instance that you created.

gcloud

To connect to the instance, run the gcloud compute ssh command:

gcloud compute ssh INSTANCE_NAME \
    --project PROJECT_ID \
    --zone ZONE

You now have a terminal window for interacting with your Linux instance.

  1. In the terminal window, install Netcat:

    sudo apt install netcat
    
  2. Download the hl7v2-mllp-sample.txt file and save it to the instance. For information on the encoding and segment terminators used in the file, see HL7v2 message segment separators and encoding.

  3. To start sending HL7v2 messages through the MLLP adapter to your HL7v2 store, in the directory where you downloaded the file, run the following command. Use the value of LoadBalancer Ingress that was displayed when you inspected the Service.

    echo -n -e "\x0b$(cat hl7v2-mllp-sample.txt)\x1c\x0d" | nc LOAD_BALANCER_INGRESS_IP_ADDRESS 2575
    

    After running the command, the message is sent through the MLLP adapter to your HL7v2 store. If the message was successfully ingested into the HL7v2 store, the command returns the following output:

    MSA|AA|20150503223000|ILITY|FROM_APP|FROM_FACILITY|20190312162410||ACK|f4c59243-19c2-4373-bea0-39c1b2ba616b|P|2.5
    

    This output indicates that the HL7v2 store responded with an AA (Application Accept) response type, meaning that the message was validated and successfully ingested.

  4. To view the message published to the Pub/Sub topic, run the gcloud pubsub subscriptions pull command:

    gcloud pubsub subscriptions pull --auto-ack PUBSUB_SUBSCRIPTION
    

    The command returns the following output about the ingested HL7v2 message:

    ┌-----------------------------------------------------------------------------------------------------------------|-----------------|---------------┐
    |                                                               DATA                                              |    MESSAGE_ID   |   ATTRIBUTES  |
    ├-----------------------------------------------------------------------------------------------------------------|-----------------|---------------|
    | projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/HL7V2_MESSAGE_ID | 123456789012345 | msgType=ADT   |
    └-----------------------------------------------------------------------------------------------------------------|-----------------|---------------┘
    
  5. You can also list the messages in your HL7v2 store to see if the message was added:

    curl

    curl -X GET \
         -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
         -H "Content-Type: application/json; charset=utf-8" \
         "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages"
    

    If the request is successful, the server returns the message's ID in a resource path:

    {
      "hl7V2Messages": [
        {
          "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID"
        }
      ]
    }
    

    PowerShell

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
      -Method Get `
      -Headers $headers `
      -ContentType: "application/json; charset=utf-8" `
      -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages" | Select-Object -Expand Content
    

    If the request is successful, the server returns the message's ID in a resource path:

    {
      "hl7V2Messages": [
        {
          "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID"
        }
      ]
    }
    

After completing this section, you have successfully deployed the MLLP adapter to GKE and sent an HL7v2 message from a remote instance through the adapter and to the Cloud Healthcare API.

In the rest of this tutorial, you learn how to securely encrypt the transmitted HL7v2 messages by configuring a VPN between a Compute Engine instance, which acts as an "on-premises" instance, and the adapter.

Configuring a VPN

Using a VPN allows you to extend the private network on which you send HL7v2 messages across a public network, such as the internet. By using a VPN, you can send messages from your care center through the MLLP adapter and to Google Cloud. The systems in this flow act as if they were on a single private network.

There are two methods of securing your MLLP connection using VPN:

Configuring Cloud VPN

Cloud VPN securely connects your on-premises network to your Google Cloud Virtual Private Cloud (VPC) network through an IPsec VPN connection. Traffic traveling between the two networks is encrypted by one VPN gateway, then decrypted by the other VPN gateway. This protects your data as it travels over the internet or over a care center network.

In this tutorial, each VPN gateway you configure is located on a different custom network and subnet in a different Google Cloud region.

The VPN gateway configured in us-central1 acts as the Cloud VPN gateway on the Google Cloud side, while the Cloud VPN gateway in europe-west1 simulates your "on-premises" gateway.

Naming and addressing reference

For reference, this tutorial uses the following naming and IP addressing:

Google Cloud side

  • Network name: cloud-vpn-network
  • Subnet name: subnet-us-central-10-0-1
  • Region: us-central1
  • Subnet range: 10.0.1.0/24
  • External IP address name: cloud-vpn-ip
  • VPN gateway name: vpn-us-central
  • VPN tunnel name: vpn-us-central-tunnel-1

"On-premises" side

  • Network name: on-prem-vpn-network
  • Subnet name: subnet-europe-west-10-0-2
  • Region: europe-west1
  • Subnet range: 10.0.2.0/24
  • External IP address name: on-prem-vpn-ip
  • VPN gateway name: vpn-europe-west
  • VPN tunnel name: vpn-europe-west-tunnel-1

Creating custom VPC networks and subnets

The first step in configuring Cloud VPN is to create two VPC networks. One network, called on-prem-vpn-network, is configured in the "on-premises" environment and runs on a Compute Engine VM instance called on-prem-instance. The other network, called cloud-vpn-network, is what the GKE cluster running the MLLP adapter uses. You'll connect to the on-prem-instance VM and send HL7v2 messages to the MLLP adapter running under the cloud-vpn-network network through the MLLP adapter's internal load balancer.

Create two custom VPC networks and their subnets by completing the following steps:

  1. To create the first VPC network, cloud-vpn-network, run the following command:

    gcloud compute networks create cloud-vpn-network \
       --project=PROJECT_ID \
       --subnet-mode=custom
    
  2. To create the subnet-us-central-10-0-1 subnet for the cloud-vpn-network network, run the following command:

    gcloud compute networks subnets create subnet-us-central-10-0-1 \
       --project=PROJECT_ID \
       --region=us-central1 \
       --network=cloud-vpn-network \
       --range=10.0.1.0/24
    
  3. To create the on-prem-vpn-network VPC network, run the following command:

    gcloud compute networks create on-prem-vpn-network \
       --project=PROJECT_ID \
       --subnet-mode=custom
    
  4. To create the subnet-europe-west-10-0-2 subnet for the on-prem-vpn-network VPC network, run the following command:

    gcloud compute networks subnets create subnet-europe-west-10-0-2 \
       --project=PROJECT_ID \
       --region=europe-west1 \
       --network=on-prem-vpn-network \
       --range=10.0.2.0/24
    

Creating an external IP address

Before creating the VPN gateways, reserve an external IP address for each gateway by completing the following steps:

  1. To reserve a regional external (static) IP address for the cloud-vpn-ip address, run the following command:

    gcloud compute addresses create cloud-vpn-ip \
       --project=PROJECT_ID \
       --region=us-central1
    
  2. To reserve a regional external (static) IP address for the on-prem-vpn-ip address, run the following command:

    gcloud compute addresses create on-prem-vpn-ip \
       --project=PROJECT_ID \
       --region=europe-west1
    
  3. Make note of the external IP addresses so that you can use them to configure the VPN gateways in the next section. To retrieve the external IP addresses, run the following command:

    Cloud VPN IP address:

    gcloud compute addresses describe cloud-vpn-ip  \
       --project PROJECT_ID \
       --region us-central1 \
       --format='flattened(address)'
    

    "On-premises" VPN IP address:

    gcloud compute addresses describe on-prem-vpn-ip \
       --project PROJECT_ID \
       --region europe-west1 \
       --format='flattened(address)'
    

    The commands return output similar to the following:

    address: 203.0.113.1
    

Creating the VPN gateways, tunnels, and routes

Complete the following steps to create the VPN gateway, tunnel, and route for the Cloud VPN:

  1. Create a cryptographically strong pre-shared key (shared secret) by following the instructions in Generating a strong pre-shared key. This key is referenced as SHARED_SECRET in this section.

  2. To create the target VPN gateway object, run the following command:

    gcloud compute target-vpn-gateways create vpn-us-central \
       --project PROJECT_ID \
       --region us-central1 \
       --network cloud-vpn-network
    
  3. To create three forwarding rules, run the following commands, replacing the CLOUD_VPN_EXTERNAL_ADDRESS variable with the value from the Cloud VPN IP address in the previous section:

    Send ESP (IPsec) traffic to the gateway:

    gcloud compute forwarding-rules create vpn-us-central-rule-esp \
        --project PROJECT_ID \
        --region us-central1 \
        --address CLOUD_VPN_EXTERNAL_ADDRESS \
        --ip-protocol ESP \
        --target-vpn-gateway vpn-us-central
    

    Send UDP 500 traffic to the gateway:

    gcloud compute forwarding-rules create vpn-us-central-rule-udp500 \
        --project PROJECT_ID \
        --region us-central1 \
        --address CLOUD_VPN_EXTERNAL_ADDRESS \
        --ip-protocol UDP \
        --ports 500 \
        --target-vpn-gateway vpn-us-central
    

    Send UDP 4500 traffic to the gateway:

    gcloud compute forwarding-rules create vpn-us-central-rule-udp4500 \
        --project PROJECT_ID \
        --region us-central1 \
        --address CLOUD_VPN_EXTERNAL_ADDRESS \
        --ip-protocol UDP \
        --ports 4500 \
        --target-vpn-gateway vpn-us-central
    
  4. To create a tunnel into the Cloud VPN gateway, run the following command. Replace ON_PREM_VPN_IP with the value from the "On-premises" VPN IP address in the previous section.

    gcloud compute vpn-tunnels create vpn-us-central-tunnel-1 \
        --project PROJECT_ID \
        --region us-central1 \
        --peer-address ON_PREM_VPN_IP \
        --shared-secret SHARED_SECRET \
        --ike-version 2 \
        --local-traffic-selector 0.0.0.0/0 \
        --target-vpn-gateway vpn-us-central
    
  5. To create a static route to 10.0.2.0/24, run the following command:

    gcloud compute routes create "vpn-us-central-tunnel-1-route-1" \
       --project PROJECT_ID \
       --network "cloud-vpn-network" \
       --next-hop-vpn-tunnel "vpn-us-central-tunnel-1" \
       --next-hop-vpn-tunnel-region "us-central1" \
       --destination-range "10.0.2.0/24"
    

Complete the following steps to create the VPN gateway, tunnel, and route for the "on-premises" VPN:

  1. To create the target VPN gateway object, run the following command:

    gcloud compute target-vpn-gateways create "vpn-europe-west" \
       --project PROJECT_ID \
       --region "europe-west1" \
       --network "on-prem-vpn-network"
    
  2. To create three forwarding rules, run the following commands, replacing the ON_PREMISES_VPN_EXTERNAL_ADDRESS variable with the value from the "On-premises" VPN IP address in the previous section:

    Send ESP (IPsec) traffic to the gateway:

    gcloud compute forwarding-rules create vpn-europe-west-rule-esp \
        --project PROJECT_ID \
        --region europe-west1 \
        --address ON_PREMISES_VPN_EXTERNAL_ADDRESS \
        --ip-protocol ESP \
        --target-vpn-gateway vpn-europe-west
    

    Send UDP 500 traffic to the gateway:

    gcloud compute forwarding-rules create vpn-europe-west-rule-udp500 \
        --project PROJECT_ID \
        --region europe-west1 \
        --address ON_PREMISES_VPN_EXTERNAL_ADDRESS \
        --ip-protocol UDP \
        --ports 500 \
        --target-vpn-gateway vpn-europe-west
    

    Send UDP 4500 traffic to the gateway:

    gcloud compute forwarding-rules create vpn-europe-west-rule-udp4500 \
         --project PROJECT_ID \
         --region europe-west1 \
         --address ON_PREMISES_VPN_EXTERNAL_ADDRESS \
         --ip-protocol UDP \
         --ports 4500 \
         --target-vpn-gateway vpn-europe-west
    
  3. To create a tunnel into the "on-premises" gateway, run the following command:

    gcloud compute vpn-tunnels create vpn-europe-west-tunnel-1 \
       --project PROJECT_ID \
       --region europe-west1 \
       --peer-address CLOUD_VPN_IP \
       --shared-secret SHARED_SECRET \
       --ike-version 2 \
       --local-traffic-selector 0.0.0.0/0 \
       --target-vpn-gateway vpn-europe-west
    
  4. To create a static route to 10.0.1.0/24, run the following command:

    gcloud compute routes create "vpn-europe-west-tunnel-1-route-1" \
       --project PROJECT_ID \
       --network "on-prem-vpn-network" \
       --next-hop-vpn-tunnel "vpn-europe-west-tunnel-1" \
       --next-hop-vpn-tunnel-region "europe-west1" \
       --destination-range "10.0.1.0/24"
    

You've created the Cloud VPN and "on-premises" gateways and initiated their tunnels. The VPN gateways won't connect until you've created firewall rules to allow traffic through the tunnel between them.

Creating firewall rules

You must create firewall rules for both sides of the VPN tunnel. These rules allow all TCP, UDP, and ICMP traffic to ingress from the subnet on one side of the VPN tunnel to the other.

  1. To create the firewall rules for the Cloud VPN subnet, run the following command:

    gcloud compute firewall-rules create allow-tcp-udp-icmp-cloud-vpn \
       --project=PROJECT_ID \
       --direction=INGRESS \
       --priority=1000 \
       --network=cloud-vpn-network \
       --action=ALLOW \
       --rules=tcp,udp,icmp \
       --source-ranges=10.0.2.0/24
    
  2. To create the firewall rules for the "on-premises" subnet, run the following command:

    gcloud compute firewall-rules create allow-tcp-udp-icmp-on-prem-vpn \
       --project=PROJECT_ID \
       --direction=INGRESS \
       --priority=1000 \
       --network=on-prem-vpn-network \
       --action=ALLOW \
       --rules=tcp,udp,icmp \
       --source-ranges=10.0.1.0/24
    
  3. Create a firewall rule that lets you SSH into the VM instance on port 22 by running the following command:

    gcloud compute firewall-rules create on-prem-vpn-allow-ssh \
       --project=PROJECT_ID \
       --direction=INGRESS \
       --priority=1000 \
       --network=on-prem-vpn-network \
       --action=ALLOW \
       --rules=tcp:22 \
       --source-ranges=0.0.0.0/0
    

Checking the status of the VPN tunnel

To verify that your tunnel is up, complete the following steps:

  1. Go to the VPN page in the Cloud Console.

    Go to the VPN page

  2. Click the Google VPN Tunnels tab.

  3. In the Status field for each tunnel, look for a green check mark and the word "Established." If these items are there, your gateways have negotiated a tunnel. If no mark appears after a few minutes, see Troubleshooting.

    For additional logging information related to your VPN tunnels, see Checking VPN Logs on the Troubleshooting page. For example, you can view metrics about dropped packets, tunnel status, received bytes, and sent bytes.

Now that you've successfully configured Cloud VPN with the necessary gateways, tunnels, and firewall rules, you can create a secure connection between the "on-premises" VM instance and the MLLP adapter running on GKE.

Combining deployment to GKE and Cloud VPN

Whereas earlier in this tutorial you tested the MLLP adapter locally and sent HL7v2 messages over a non-VPN connection to the MLLP adapter, you'll now send messages from a Compute Engine VM over a secure connection using Cloud VPN to the MLLP adapter running on GKE. The messages are then forwarded to an HL7v2 store.

Re-creating the deployment

First, re-create the deployment on GKE so that the cluster uses the settings you configured in Configuring Cloud VPN:

  1. To delete the mllp-adapter cluster you created, run the gcloud container clusters delete command. Enter the COMPUTE_ZONE value that you used when you created the cluster.

    gcloud container clusters delete mllp-adapter --zone=COMPUTE_ZONE
    
  2. Follow the steps in Deploying the MLLP adapter to Kubernetes Engine, but when you create the cluster in GKE, add the cloud-vpn-network network and the subnet-us-central-10-0-1 subnet that you created in Creating custom VPN networks and subnets.

    Make sure that the cluster creation command looks like the following:

    gcloud container clusters create mllp-adapter \
       --zone=COMPUTE_ZONE \
       --service-account=CLIENT_EMAIL \
       --network=cloud-vpn-network \
       --subnetwork=subnet-us-central-10-0-1
    

    where:

    • COMPUTE_ZONE is the zone in which your cluster is deployed. When you configured Cloud VPN in the previous section, you set the "GCP side" network to use us-central1. This "GCP side" network is what the GKE cluster runs on. Use any of the following zones in us-central1: us-central1-c, us-central1-a, us-central1-f, us-central1-b.

    • CLIENT_EMAIL is the identifier for the service account. You can find this in the service account key file in the "client_email": field. It takes the format SERVICE_ACCOUNT_NAME@PROJECT_ID.iam.gserviceaccount.com.

Creating a new Compute Engine VM with network settings

The following steps show how to create a Linux virtual machine instance in Compute Engine using the Cloud Console. Unlike the Compute Engine VM you created, this VM uses the "'on-premises' side" network settings to communicate with the GKE cluster over a VPN.

Console

  1. In the Cloud Console, go to the VM Instances page.

    Go to the VM Instances page

  2. Click Create instance.

  3. Choose a Region and Zone for the instance that matches the "'on-premises' side" network settings: europe-west1 (Belgium) for the Region and europe-west1-b for the Zone.

  4. In the Boot disk section, click Change to begin configuring your boot disk.

  5. On the Public images tab, choose version 9 of the Debian operating system.

  6. Click Select.

  7. In the Identity and API access section, select the service account you created.

  8. In the Firewall section, select Allow HTTP traffic.

  9. Expand the Management, security, disks, networking, sole tenancy section.

  10. Under Network interfaces in the Networking tab, specify the network details for the "'on-premises' side" network settings:

    1. In the Network field, select on-prem-vpn-network.
    2. In the Subnetwork field, select subnet-europe-west-10-0-2 (10.0.2.0/24).
  11. Click Create to create the instance.

Allow a short time for the instance to start up. When it's ready, it's listed on the VM Instances page with a green status icon.

gcloud

To create a compute instance, run the gcloud compute instances create method with the following options:

  • The ZONE that matches the "'on-premises' side" network settings: europe-west1-b for the Zone.
  • Allow HTTP traffic by specifying the http-server tag
  • The SERVICE_ACCOUNT that you created
gcloud compute instances create COMPUTE_NAME \
   --project=PROJECT_ID
   --zone=ZONE
   --image-family=debian-9 \
   --tags=http-server,https-server
   --service-account=SERVICE_ACCOUNT

The output is similar to the following sample:

Created [https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/instances/COMPUTE_NAME].
NAME          ZONE           MACHINE_TYPE   PREEMPTIBLE  INTERNAL_IP  EXTERNAL_IP    STATUS
COMPUTE_NAME  ZONE           n1-standard-1               INTERNAL_IP  EXTERNAL_IP    RUNNING

To connect to the instance, complete the following steps:

Console

  1. In the Cloud Console, go to the VM Instances page.

    Go to the VM Instances page

  2. In the list of virtual machine instances, click SSH in the row of the instance that you created.

gcloud

To connect to the instance, run the gcloud compute ssh command:

gcloud compute ssh INSTANCE_NAME \
    --project PROJECT_ID \
    --zone ZONE

You now have a terminal window for interacting with your Linux instance.

  1. In the terminal window, install Netcat:

    sudo apt install netcat
    
  2. Download the hl7v2-mllp-sample.txt file and save it to the instance.

  3. To start sending HL7v2 messages through the MLLP adapter to your HL7v2 store, in the directory where you downloaded the file, run the following command. Use the value of LoadBalancer Ingress that was displayed when you inspected the Service.

    echo -n -e "\x0b$(cat hl7v2-mllp-sample.txt)\x1c\x0d" | nc LOAD_BALANCER_INGRESS_IP_ADDRESS 2575
    

    After running the command, the message is sent through the MLLP adapter to your HL7v2 store. If the message was successfully ingested into the HL7v2 store, the command returns the following output:

    MSA|AA|20150503223000|ILITY|FROM_APP|FROM_FACILITY|20190312162410||ACK|f4c59243-19c2-4373-bea0-39c1b2ba616b|P|2.5
    

    This output indicates that the HL7v2 store responded with an AA (Application Accept) response type, meaning that the message was validated and successfully ingested.

  4. To view the message published to the Pub/Sub topic, run the gcloud pubsub subscriptions pull command:

    gcloud pubsub subscriptions pull --auto-ack PUBSUB_SUBSCRIPTION
    

    The command returns the following output about the ingested HL7v2 message:

    ┌-----------------------------------------------------------------------------------------------------------------|-----------------|---------------┐
    |                                                               DATA                                              |    MESSAGE_ID   |   ATTRIBUTES  |
    ├-----------------------------------------------------------------------------------------------------------------|-----------------|---------------|
    | projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/HL7V2_MESSAGE_ID | 123456789012345 | msgType=ADT   |
    └-----------------------------------------------------------------------------------------------------------------|-----------------|---------------┘
    
  5. You can also list the messages in your HL7v2 store to see if the message was added:

    curl

    curl -X GET \
         -H "Authorization: Bearer $(gcloud auth application-default print-access-token)" \
         -H "Content-Type: application/json; charset=utf-8" \
         "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages"
    

    If the request is successful, the server returns the message's ID in a resource path:

    {
      "hl7V2Messages": [
        {
          "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID"
        }
      ]
    }
    

    PowerShell

    $cred = gcloud auth application-default print-access-token
    $headers = @{ Authorization = "Bearer $cred" }
    
    Invoke-WebRequest `
      -Method Get `
      -Headers $headers `
      -ContentType: "application/json; charset=utf-8" `
      -Uri "https://healthcare.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages" | Select-Object -Expand Content
    

    If the request is successful, the server returns the message's ID in a resource path:

    {
      "hl7V2Messages": [
        {
          "name": "projects/PROJECT_ID/locations/LOCATION/datasets/DATASET_ID/hl7V2Stores/HL7V2_STORE_ID/messages/MESSAGE_ID"
        }
      ]
    }
    

After completing this section, you have successfully deployed the MLLP adapter to GKE and, over a VPN, securely sent an HL7v2 message from an "on-premises" instance through the adapter and to the Cloud Healthcare API.

Clean up

To avoid incurring charges to your Google Cloud account for the resources used in this tutorial, you can clean up the resources you created on Google Cloud.

Delete the project

Follow the steps below to delete the project you created in this tutorial:

  1. In the Cloud Console, go to the Manage resources page.

    Go to the Manage resources page

  2. In the project list, select the project that you want to delete and then click Delete .
  3. In the dialog, type the project ID and then click Shut down to delete the project.

Troubleshooting

Adapter failures

After deploying the MLLP adapter to GKE, the adapter encounters a failure.

Connection refused error when running locally

When testing the MLLP adapter locally, you encounter the error Connection refused.

  • This error occurs with some Mac OS users. Instead of using the --network=host flag, use -p 2575:2575. Also, instead of setting --receiver_ip=127.0.0.0, set --receiver_ip=0.0.0.0. The command should look like this:

    docker run \
      -p 2575:2575 \
      gcr.io/cloud-healthcare-containers/mllp-adapter \
      /usr/mllp_adapter/mllp_adapter \
      --hl7_v2_project_id=PROJECT_ID \
      --hl7_v2_location_id=LOCATION \
      --hl7_v2_dataset_id=DATASET_ID \
      --hl7_v2_store_id=HL7V2_STORE_ID \
      --export_stats=false \
      --receiver_ip=0.0.0.0 \
      --pubsub_project_id=PROJECT_ID \
      --pubsub_subscription=PUBSUB_SUBSCRIPTION \
      --api_addr_prefix=https://healthcare.googleapis.com:443/v1 \
      --logtostderr
    

could not find default credentials error when running locally

When testing the MLLP adapter locally, you encounter the error healthapiclient.NewHL7V2Client: oauth2google.DefaultTokenSource: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information..

This error occurs when the adapter can't find your Google Cloud credentials. To fix the error, try one of the following methods before running the command again: