Blog

  • Recipe: Streaming the FHIR Audit from the IBM FHIR Server with Go

    The IBM FHIR Server supports audit events for FHIR interactions (CREATE-READ-UPDATE-DELETE-SEARCH-EXTENDED_OPERATION) in Cloud Auditing Data Federation (CADF) and HL7 FHIR AuditEvent and pushing the events to an Apache Kafka backend. You can read more about it in another post I made.

    This recipe shows how to stream the data with Go-Kafka in a small lightweight library and decode the BASE64 content embedded in CADF.

    Let’s spin up an IBM FHIR Server with fhir-audit and how to stream and decode the important content.

    Recipe

    1. Log in to the IBM Cloud Console
    2. Click Create Resource
    Click Create Resource (on right)

    3. Search for Event Streams, and click on Event Streams

    Search and Click on Event Streams

    4. Choose your Location. I chose us-south. Pick the data center that is closest or co-located with your IBM FHIR Server.

    5. Select the Standard plan. A typical bundle which represents a patient history, such as Antonia30_Acosta403.json, include hundreds of resources which correspond to many messages sent over the topic when processed as a Bundle batch. For a Bundle transaction, you get only one notification message.

    6. Click I Accept

    7. Click Create. You are redirected to the EventStreams resource that is created.

    Your Service is Created

    8. Click Service Credentials

    9. Click New Credentials

    10. Click Add

    Create Credentials Dialog

    11. Copy the Service Credentials and paste it locally (on the right hand-side)

    Copy Credentials

    12. Click on Topics

    13. Click Create Topic

    14. Enter Topic Name – FHIR_AUDIT

    15. Click Next

    Enter Topic Name

    16. Select the default number of partitions

    Select Number of Partitions

    17. Click Next

    18. Select Message Retention – 1 Day

    19. Click Create Topic

    Create Topic

    With the IBM EventStreams Kafka Topic setup, it’s now time to connect the IBM FHIR Server and the FHIR Audit module to Kafka.

    20. Download the fhir-server-config.json

    curl -L https://raw.githubusercontent.com/IBM/FHIR/main/fhir-server-webapp/src/main/liberty/config/config/default/fhir-server-config-audit-environment.json -o fhir-server-config-audit-environment.json

    The file contains a setting for loading the properties from the Environment (e.g. Operation System variables) and some basic settings:

            "audit": {
                "serviceClassName" : "com.ibm.fhir.audit.impl.KafkaService",
                "serviceProperties" : {
                    "auditTopic": "FHIR_AUDIT",
                    "geoCity": "Dallas",
                    "geoState": "TX",
                    "geoCounty": "US"
                }
            }

    21. Download the docker image

    docker pull ibmcom/ibm-fhir-server:latest

    22. Convert the copied Service Credentials into a file – ibm-creds.json

    23. Make the JSON a single line

    cat ibm-creds.json | tr -d '\n'

    The output is:

    {  "api_key": "credentialapikey",  "apikey": "credentialapikey",  "iam_apikey_description": "Auto-generated for key 01ba3165-85d9-410b-ad1a-1111",  "iam_apikey_name": "Service credentials-1",  "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",  "iam_serviceid_crn": "crn:v1:bluemix:public:iam-identity::a/11111::serviceid:ServiceId-864b549f-ff90-4b24-84f2-1111",  "instance_id": "6b00cc8c-26ec-4c42-a1e8-f4da5b9f71e7",  "kafka_admin_url": "https://admin.eventstreams.cloud.ibm.com",  "kafka_brokers_sasl": [    "broker-3.eventstreams.cloud.ibm.com:9093",    "broker-5.eventstreams.cloud.ibm.com:9093",    "broker-1.eventstreams.cloud.ibm.com:9093",    "broker-2.eventstreams.cloud.ibm.com:9093",    "broker-4.eventstreams.cloud.ibm.com:9093",    "broker-0.eventstreams.cloud.ibm.com:9093"  ],  "kafka_http_url": "https://admin.eventstreams.cloud.ibm.com",  "password": "credentialapikey",  "user": "token"}
    

    24. Export ES_CONFIG

    export ES_CONFIG='{  "api_key": "credentialapikey",  "apikey": "credentialapikey",  "iam_apikey_description": "Auto-generated for key 01ba3165-85d9-410b-ad1a-1111",  "iam_apikey_name": "Service credentials-1",  "iam_role_crn": "crn:v1:bluemix:public:iam::::serviceRole:Manager",  "iam_serviceid_crn": "crn:v1:bluemix:public:iam-identity::a/11111::serviceid:ServiceId-864b549f-ff90-4b24-84f2-1111",  "instance_id": "6b00cc8c-26ec-4c42-a1e8-f4da5b9f71e7",  "kafka_admin_url": "https://admin.eventstreams.cloud.ibm.com",  "kafka_brokers_sasl": [    "broker-3.eventstreams.cloud.ibm.com:9093",    "broker-5.eventstreams.cloud.ibm.com:9093",    "broker-1.eventstreams.cloud.ibm.com:9093",    "broker-2.eventstreams.cloud.ibm.com:9093",    "broker-4.eventstreams.cloud.ibm.com:9093",    "broker-0.eventstreams.cloud.ibm.com:9093"  ],  "kafka_http_url": "https://admin.eventstreams.cloud.ibm.com",  "password": "credentialapikey",  "user": "token"}'
    

    Note the single quotes surround the above.

    25. Start up the IBM FHIR Server with the EventStreams credentials and fhir-server-config.json we just downloaded.

    docker run --rm -d -p 9443:9443 -e BOOTSTRAP_DB=true \
        -v $(pwd)/fhir-server-config-audit-environment.json:/config/config/default/fhir-server-config.json \
        -e EVENT_STREAMS_AUDIT_BINDING="${ES_CONFIG}" ibmcom/ibm-fhir-server
    

    You see the container id output 60a5f1cae6d677d80772f1736db1be74836a8a4845fcccc81286b7c557bc2d86.

    26. Check that the applications are started using the container id.

    $ docker logs 60a | grep -i started
    [4/21/21, 20:55:58:449 UTC] 00000001 FrameworkMana I   CWWKE0002I: The kernel started after 1.43 seconds
    [4/21/21, 20:55:58:464 UTC] 0000002a FeatureManage I   CWWKF0007I: Feature update started.
    [4/21/21, 20:56:01:328 UTC] 00000030 AppMessageHel A   CWWKZ0001I: Application fhir-openapi started in 1.588 seconds.
    [4/21/21, 20:56:03:141 UTC] 00000031 AppMessageHel A   CWWKZ0001I: Application fhir-bulkdata-webapp started in 3.402 seconds.
    [4/21/21, 20:56:07:824 UTC] 0000002d AppMessageHel A   CWWKZ0001I: Application fhir-server-webapp started in 7.871 seconds.
    [4/21/21, 20:56:07:868 UTC] 0000002a TCPPort       I   CWWKO0219I: TCP Channel defaultHttpEndpoint-ssl has been started and is now listening for requests on host *  (IPv4) port 9443.
    [4/21/21, 20:56:07:880 UTC] 0000002a FeatureManage A   CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 10.885 seconds.

    27. Download the Sample Data

    curl -L https://raw.githubusercontent.com/IBM/FHIR/main/fhir-server-test/src/test/resources/testdata/everything-operation/Antonia30_Acosta403.json -o Antonia30_Acosta403.json

    28. Load the Sample Data bundle to the IBM FHIR Server

    curl -k --location --request POST 'https://localhost:9443/fhir-server/api/v4' \
    --header 'Content-Type: application/fhir+json' \
    -u "fhiruser:change-password" \
    --data-binary  "@Antonia30_Acosta403.json" -o response.json

    29. Scan the response.json for any status that is not "status": "201". For example, the status is in the family of User Request Error or Server Side Error.

    30. Clone the repository fhir-kafka-go

    31. Clone the repository

    git clone https://github.com/prb112/fhir-kafka-go.git

    32. Build the Go guild

    go build

    33. Run the fhir-kakfka-go library

    ./fhir-kafka-go $(cat ibm-creds.json | jq -r '.kafka_brokers_sasl | join(",")') "token" $(cat ibm-creds.json | jq -r '.password') FHIR_AUDIT

    You’ll see the following output:

    2022/02/18 12:53:02 The audit details are:  t${
        "request_unique_id": "8b7ebb8f-3b48-4633-92d3-4e1d423a3aa2",
        "action": "C",
        "start_time": "2022-02-18 17:52:22.444",
        "end_time": "2022-02-18 17:52:22.525",
        "api_parameters": {
            "request": "/fhir-server/api/v4/Patient",
            "request_status": 201
        },
        "data": {
            "resource_type": "Patient",
            "id": "17f0df6a5b6-1d8db4d7-5042-4742-a951-6c3a3d08ee34",
            "version_id": "1"
        },
        "event_type": "fhir-create",
        "description": "FHIR Create request",
        "location": "172.17.0.1/_gateway"
    }

    This tool uses Go-Kafka and a few extra tools to process Kafka Messages and help tail the messages.

    I hope this works for you, it’s great to work with Go and Kafka.

  • A Reliable CVE Dependency Check: How-To

    dependency-check is a standalone maven plugin which checks for vulnerable dependencies. It’s hosted on GitHub. I switched to it from the victims-db, which no longer looks like it is updated. I had to carefully analyze the output, it was very helpful finding one issue where we had an unintended include.

    Build

    export TAG=4.10.2
    git checkout ${TAG}
    mvn clean install -f fhir-examples -DskipTests
    mvn clean install -f fhir-parent -DskipTests
    mvn org.owasp:dependency-check-maven:check -f fhir-parent/ -l output-${TAG}.log
    grep -e 'vulnerabilities' -e CVE- output-${TAG}.log  > summary-${TAG}.log
    

    Output Note, this is some what cleaned up.

    fhir-term-graph:
    ant-1.7.0.jar (pkg:maven/org.apache.ant/ant@1.7.0, cpe:2.3:a:apache:ant:1.7.0:*:*:*:*:*:*:*) : CVE-2020-1945
    cassandra-driver-core-3.11.0.jar (pkg:maven/com.datastax.cassandra/cassandra-driver-core@3.11.0, cpe:2.3:a:apache:cassandra:3.11.0:*:*:*:*:*:*:*) : CVE-2018-8016, CVE-2020-13946, CVE-2020-17516
    gremlin-core-3.5.1.jar (pkg:maven/org.apache.tinkerpop/gremlin-core@3.5.1, cpe:2.3:a:apache:tinkerpop:3.5.1:*:*:*:*:*:*:*) : CVE-2021-37136, CVE-2021-37137
    
  • Bulk Data: Using the SMART-on-FHIR Bulk Data Client to test $export

    I recently attended the HL7 FHIR Connectathon 29. For those that are not familiar with Connectathons, I think they are fairly unique events featuring standards enthusiasts, vendors and implementors doing hands-on standards development (FHIR) and testing. As an attendee I picked one of the tracksbulk data.

    This blog is part of a series on Bulk Data Setup and Testing based on my experience at HL7 FHIR Connectathon 29.

    The SMART-on-FHIR team has built a node application to call Bulk Data $export and facilitate the calls to get a bearer token.

    1. Clone the Bulk Data Client repository
    /git/wffh/2023$ git clone -b main-pb --single-branch \
        https://github.com/prb112/bulk-data-client.git && \
        cd $(basename $_ .git)
    Cloning into 'bulk-data-client'...
    remote: Enumerating objects: 343, done.
    remote: Counting objects: 100% (343/343), done.
    remote: Compressing objects: 100% (188/188), done.
    remote: Total 343 (delta 220), reused 261 (delta 146), pack-reused 0
    Receiving objects: 100% (343/343), 407.93 KiB | 3.32 MiB/s, done.
    Resolving deltas: 100% (220/220), done.
    /git/wffh/2023/bulk-data-client$
    

    Note I added a changes to support the IBM FHIR Server.

    1. Install nvm
    $ brew install nvm
    Running `brew update --preinstall`...
    ==> Auto-updated Homebrew!
    Updated 3 taps (homebrew/core, homebrew/cask and minio/stable).
    ==> New Formulae
    cwb3                                                      erofs-utils                                               netmask                                                   scalingo                                                  zk
    ==> Updated Formulae
    Updated 390 formulae.
    
    ...
    
    nvm 0.35.3 is already installed but outdated (so it will be upgraded).
    ==> Downloading https://ghcr.io/v2/homebrew/core/nvm/manifests/0.39.1_1
    ######################################################################## 100.0%
    ==> Downloading https://ghcr.io/v2/homebrew/core/nvm/blobs/sha256:6e14c8a2bf94212545c1ebac9a722df168c318d0e8af2fc75b729a07fea54efe
    ==> Downloading from https://pkg-containers.githubusercontent.com/ghcr1/blobs/sha256:6e14c8a2bf94212545c1ebac9a722df168c318d0e8af2fc75b729a07fea54efe?se=2022-01-13T21%3A30%3A00Z&sig=52CYOAyuxabqLNYih9%2BjMQtHLFrN%2FVyHXf%2FgPuBgi6w%3D&sp=r&spr=https&sr=b&sv=2019-12-12
    ######################################################################## 100.0%
    ==> Upgrading nvm
      0.35.3 -> 0.39.1_1
    
    ==> Pouring nvm--0.39.1_1.all.bottle.tar.gz
    ...
    
    Bash completion has been installed to:
      /usr/local/etc/bash_completion.d
    ==> Summary
    🍺  /usr/local/Cellar/nvm/0.39.1_1: 9 files, 184.1KB
    ==> Running `brew cleanup nvm`...
    Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
    Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
    Removing: /usr/local/Cellar/nvm/0.35.3... (7 files, 149.9KB)
    
    1. Switch to Node 16
    $ nvm use 16
    Now using node v16.13.0 (npm v8.1.2)
    
    1. Install Node 16
    $ nvm install
    Found '/git/wffh/2023/bulk-data-client/.nvmrc' with version <16>
    Downloading and installing node v16.13.2...
    Downloading https://nodejs.org/dist/v16.13.2/node-v16.13.2-darwin-x64.tar.xz...
    ############################################################## 100.0%
    Computing checksum with sha256sum
    Checksums matched!
    Now using node v16.13.2 (npm v8.1.2)
    
    1. Create the binaries
    $ npm run clean && npm run build
    
    > bulk-data-client@1.0.0 clean
    > rm -rf ./built
    
    
    > bulk-data-client@1.0.0 build
    > tsc
    
    1. Create the config/ibm-fhir-server.js – update the fhirUrl, tokenUrl and privateKey. I used the RS384 Key.
    1. Run the application
    $ AUTO_RETRY_TRANSIENT_ERRORS=1 SHOW_ERRORS=1 node . --config config/ibm-fhir-server.js  --_type=Patient
    Kick-off started
    Got new access token
    Kick-off completed
    Bulk Data export started
    Bulk Data export completed in 10 seconds
    Export manifest:  {
      transactionTime: '2022-01-14T20:56:31.553Z',
      request: 'https://bulk.cluster1-blue-x123456.containers.appdomain.cloud/fhir-server/api/v4/Patient/$export?_type=Patient',
      requiresAccessToken: false,
      output: [
        {
          type: 'Patient',
          url: 'https://x123456.cloud-object-storage.appdomain.cloud/x123456/f1wSMD84-3eRVNTUlyIwctWtNxA33Odz14KiQctry-U/Patient_1.ndjson?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=x123456%2F20220114%2Fus-east%2Fs3%2Faws4_request&X-Amz-Date=20220114T205641Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=abcdefgh',
          count: 2
        }
      ],
      error: []
    }
    
    Downloading exported files: ▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉▉ 100%
              Downloaded Files: 1 of 1
                FHIR Resources: 2
                   Attachments: 0
               Downloaded Size: 6.3 kB
    
    Download completed in 0 seconds
    Do you want to signal the server that this export can be removed? [Y/n]n
    

    If you need to debug, you’ll see all the outputs:

    NODE_DEBUG=* AUTO_RETRY_TRANSIENT_ERRORS=1 SHOW_ERRORS=1 node . \
       --config config/ibm-fhir-server.js
    
    1. The files are downloaded to a local directory, and you can check the data. cat downloads/1.Patient.ndjson
    {"resourceType":"Patient","id":"17e44643c84-7618d4fe-e1d3-48e5-8a66-923bd4780c17","meta":{"versionId":"1","lastUpdated":"2022-01-10T14:28:46.854836Z"},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Generated by <a href=\"https://github.com/synthetichealth/synthea\">Synthea</a>.Version identifier: master-branch-latest-2-g8e7e92c\n .   Person seed: 7105486291024734541  Population seed: 0</div>"},"extension":[{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Graciela518 Badillo851"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"Caguas","state":"Puerto Rico","country":"PR"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":1.6165934938317164},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":47.383406506168285},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"F"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"bd958c64-56b6-f206-b03d-8f4b8a417215"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"bd958c64-56b6-f206-b03d-8f4b8a417215"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-93-6090"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99913499"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X26583229X"}],"name":[{"use":"official","family":"Acosta403","given":["Antonia30"],"prefix":["Mrs."]},{"use":"maiden","family":"Armenta418","given":["Antonia30"],"prefix":["Mrs."]}],"telecom":[{"system":"phone","value":"555-457-3489","use":"home"}],"gender":"female","birthDate":"1970-09-06","address":[{"extension":[{"extension":[{"url":"latitude","valueDecimal":41.6497330022825},{"url":"longitude","valueDecimal":-71.17511064024423}],"url":"http://hl7.org/fhir/StructureDefinition/geolocation"}],"line":["635 Littel Esplanade Suite 65"],"city":"Fall River","state":"Massachusetts","postalCode":"02790","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]}
    {"resourceType":"Patient","id":"17e4f2f3dca-67724e2c-3991-4ac1-b6f9-51fc30209894","meta":{"versionId":"1","lastUpdated":"2022-01-12T16:46:43.149786Z"},"text":{"status":"generated","div":"<div xmlns=\"http://www.w3.org/1999/xhtml\">Generated by <a href=\"https://github.com/synthetichealth/synthea\">Synthea</a>.Version identifier: v2.4.0-404-ge7ce2295\n .   Person seed: -8292973307042192125  Population seed: 0</div>"},"extension":[{"extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2135-2","display":"Other"}},{"url":"text","valueString":"Other"}],"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-race"},{"extension":[{"url":"ombCategory","valueCoding":{"system":"urn:oid:2.16.840.1.113883.6.238","code":"2186-5","display":"Not Hispanic or Latino"}},{"url":"text","valueString":"Not Hispanic or Latino"}],"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-ethnicity"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-mothersMaidenName","valueString":"Isabel214 Reséndez908"},{"url":"http://hl7.org/fhir/us/core/StructureDefinition/us-core-birthsex","valueCode":"M"},{"url":"http://hl7.org/fhir/StructureDefinition/patient-birthPlace","valueAddress":{"city":"La Paz","state":"Baja California","country":"MX"}},{"url":"http://synthetichealth.github.io/synthea/disability-adjusted-life-years","valueDecimal":0.13213216767824654},{"url":"http://synthetichealth.github.io/synthea/quality-adjusted-life-years","valueDecimal":34.86786783232176}],"identifier":[{"system":"https://github.com/synthetichealth/synthea","value":"d171d808-1f31-4ad3-aba5-e03a2fa921c7"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"MR","display":"Medical Record Number"}],"text":"Medical Record Number"},"system":"http://hospital.smarthealthit.org","value":"d171d808-1f31-4ad3-aba5-e03a2fa921c7"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"SS","display":"Social Security Number"}],"text":"Social Security Number"},"system":"http://hl7.org/fhir/sid/us-ssn","value":"999-83-6585"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"DL","display":"Driver's License"}],"text":"Driver's License"},"system":"urn:oid:2.16.840.1.113883.4.3.25","value":"S99914532"},{"type":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v2-0203","code":"PPN","display":"Passport Number"}],"text":"Passport Number"},"system":"http://standardhealthrecord.org/fhir/StructureDefinition/passportNumber","value":"X18544570X"}],"name":[{"use":"official","family":"Hurtado459","given":["Marco Antonio298"],"prefix":["Mr."]}],"telecom":[{"system":"phone","value":"555-185-5373","use":"home"}],"gender":"male","birthDate":"1983-04-03","address":[{"extension":[{"extension":[{"url":"latitude","valueDecimal":42.49384661818001},{"url":"longitude","valueDecimal":-70.92858466579901}],"url":"http://hl7.org/fhir/StructureDefinition/geolocation"}],"line":["552 Rippin Port"],"city":"Revere","state":"Massachusetts","postalCode":"02151","country":"US"}],"maritalStatus":{"coding":[{"system":"http://terminology.hl7.org/CodeSystem/v3-MaritalStatus","code":"M","display":"M"}],"text":"M"},"multipleBirthBoolean":false,"communication":[{"language":{"coding":[{"system":"urn:ietf:bcp:47","code":"es","display":"Spanish"}],"text":"Spanish"}}]}
    

    As you can see the bulk-data-client is a handy tool to test Bulk Data with $export with SMART Backend Services Authorization.

  • IBM FHIR Server: Getting Started Links

    The IBM FHIR Server supports FHIR R4 and is at version 4.10.2.

    The conformance page outlines the features and capabilities of the main build link. The user’s guide outlines the features and configurations link. The module catalog outlines the features link.

    The IBM FHIR Server is delivered in three ways.

    1. A Modular Application
    • Jars published to Maven Central
    • Search for Jars at https://mvnrepository.com/artifact/com.ibm.fhir
    • Javadocs
    1. Docker
    1. A Helm Chart to Install a Working Environment

    If you have more specifics, we can work on diving into the features.

  • USING THE IBM FHIR SERVER WITH HELM

    This article walks folks through the process of using the IBM FHIR Server’s helm chart with Docker Desktop Kubernetes and getting it up and running.

  • Help… run nsenter

    Per Enqueue Zero, Nsenter is a utility enters the namespaces of one or more other processes and then executes the specified program. In other words, we jump to the inner side of the namespace.

    Search for the namespace, by searching for S+, and then using the PID to target the namespace, and run the local tools in the namespace. This is very helpful where the docker container does not contain the necessary tools by default.

    [root@localhost ~]# ps aux | grep 'S+'
    gdm         1203  0.0  0.0   6084  1064 tty1     S+    2021   0:00 dbus-run-session -- gnome-session --autostart /usr/share/gdm/greeter/autostart
    root       24439  0.0  0.0   4420   632 pts/0    S+    2021   0:00 tail -f /database/config/db2inst1/sqllib/db2dump/DIAG0000/db2diag.log
    root      922523  0.0  0.0 221568   776 pts/0    S+   14:07   0:00 grep --color=auto S+
    [root@localhost ~]# nsenter --target 24439 --mount --uts  --net --pid ps aux
    USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
    root           1  0.0  0.0  12100  2416 pts/0    Ss+   2021   0:01 /bin/bash /var/db2_setup/lib/setup_db2_instance.sh
    root       14738  0.0  0.0 111996  5688 ?        Ss    2021  33:46 /usr/bin/python /usr/bin/supervisord -c /etc/supervisord.conf
    root       14741  0.0  0.0   4420   632 pts/0    S+    2021   0:00 tail -f /database/config/db2inst1/sqllib/db2dump/DIAG0000/db2diag.log
    root       14742  0.0  0.0  95724 12332 ?        S     2021   5:59 /opt/ibm/db2/V11.5/bin/db2fmcd
    root       14743  0.0  0.0 112952  5696 ?        S     2021   0:00 /usr/sbin/sshd -D
    root       65263  0.0  0.0 1313568 8708 ?        Sl    2021   0:06 db2wdog 0 [db2inst1]
    db2inst1   65265  0.1  0.1 3716884 30216 ?       Sl    2021  88:11 db2sysc 0
    root       65271  0.0  0.0 1316348 3548 ?        S     2021   0:00 db2ckpwd 0
    root       65272  0.0  0.0 1316348 3548 ?        S     2021   0:00 db2ckpwd 0
    root       65273  0.0  0.0 1316348 3544 ?        S     2021   0:00 db2ckpwd 0
    db2inst1   65275  0.0  0.0 723620  7448 ?        S     2021   0:00 db2vend (PD Vendor Process - 1) 0
    db2inst1   65283  0.0  0.1 961132 23452 ?        Sl    2021  16:42 db2acd 0 ,0,0,0,1,0,0,00000000,0,0,0000000000000000,0000000000000000,00000000,00000
    db2fenc1   66416  0.0  0.1 650952 20416 ?        Sl    2021   0:01 db2fmp ( ,1,0,0,0,0,0,00000000,0,0,0000000000000000,0000000000000000,00000000,00000
    db2fenc1   70651  0.0  0.1 424932 19852 ?        Sl    2021   0:00 db2fmp ( ,0,0,0,0,0,0,00000000,0,0,0000000000000000,0000000000000000,00000000,00000
    db2fenc1 2274960  0.0  0.1 424932 19872 ?        Sl    2021   0:00 db2fmp ( ,0,0,0,0,0,0,00000000,0,0,0000000000000000,0000000000000000,00000000,00000
    root     4113980  0.0  0.0  11704  2660 ?        S    19:05   0:00 bash -c /var/db2_setup/lib/backup_cfg.sh >> /tmp/backup_cfg.out 2>&1
    root     4113981  0.0  0.0  11704  2816 ?        S    19:05   0:00 /bin/bash /var/db2_setup/lib/backup_cfg.sh
    root     4114110  0.0  0.0   4380   736 ?        S    19:05   0:00 sleep 2m
    root     4114269  0.1  0.0  12100  3072 ?        S    19:07   0:00 /bin/bash /var/db2_setup/lib/fix_etc_host.sh
    root     4114285  0.0  0.0   4380   716 ?        S    19:07   0:00 sleep 10
    root     4114290  0.0  0.0  53348  3856 pts/0    R+   19:07   0:00 ps aux
    [root@localhost ~]#
    
  • Never accept the defaults: Lessons Learned using OpenJ9 in a Container

    Never accept the defaults: Lessons Learned using OpenJ9 in a Container

    Eclipse OpenJ9 is an efficient virtual machine with a small-dynamic footprint that is used for many cloud applications.  Many applications use the OpenJ9 to run their applications, such as the Apache OpenWhisk, IBM FHIR Server and Open Liberty.

    I learned a few things about running Java applications with the OpenJ9 VM in Docker:

    1. Eclipse OpenJ9 knows about modern applications
    2. Tweak Your Settings
    3. Review your Settings

    1.   Eclipse OpenJ9 knows about modern applications

    The Eclipse OpenJ9 team smartly realized many Java applications are in a container or namespace or virtual machine which has a runtime determined memory allocation.  For instance, Kubernetes may scale the memory available to a Java Container from 4G to 8G. As such, the OpenJ9 VM automatically adjust the minimum and maximum heap sizes based on the available memory in the container.

    There are plenty of nobs to tweak. Further, the Eclipse team has some compelling research on performance and places to look to tweak my application.

    2.   Tweak your settings

    I scanned through the JVM -XX Options and found -XX:InitialRAMPercentage / -XX:MaxRAMPercentage.  These options combined with -XX:[+|-]UseContainerSupport

    , the default, enable the VM to allocate a fraction of the available memory to the JVM.  I tried this with a Docker container assigned 2GB Memory.

    -XX:InitialRAMPercentage=50.00

    -XX:MaxRAMPercentage=90.00

    3.   Review and Test Your Settings

    I turned on the -verbose:gc setting, and started my VM and ran some load.

    I waited for a steady state and checked my available memory, and saw the 469M free of 858M total.

    <gc-end id=”443″ type=”scavenge” contextid=”439″ durationms=”57.408″ usertimems=”223.188″ systemtimems=”0.172″ stalltimems=”4.439″ timestamp=”2021-12-20T18:00:43.360″ activeThreads=”4″>

    <mem-info id=”444″ free=”469525144″ total=”858914816″ percent=”54″>

    <mem type=”nursery” free=”269655128″ total=”468451328″ percent=”57″>

    <mem type=”allocate” free=”269655128″ total=”349700096″ percent=”77″ />

    <mem type=”survivor” free=”0″ total=”118751232″ percent=”0″ /> </mem>

     <mem type=”tenure” free=”199870016″ total=”390463488″ percent=”51″ macro-fragmented=”99381968″>

    <mem type=”soa” free=”180346432″ total=”370939904″ percent=”48″ />

     <mem type=”loa” free=”19523584″ total=”19523584″ percent=”100″ /> </mem>

    I tweaked the memory based on the Garbage Collection log and used the tools as mentioned in the OpenJ9 docs. You can also see some more of the enhancements for Containers that OpenJ9 has added.

    Go forth, tweak the settings for the container and best wishes. Please comment if I can help.

  • Upper Limit for PreparedStatement Parameters

    Thanks to Lup Peng PostgreSQL JDBC Driver – Upper Limit on Parameters in PreparedStatement I was able to diagnose an upper limit:

    Caused by: java.io.IOException: Tried to send an out-of-range integer as a 2-byte value: 54838
    	at org.postgresql.core.PGStream.sendInteger2(PGStream.java:349)
    	at org.postgresql.core.v3.QueryExecutorImpl.sendParse(QueryExecutorImpl.java:1546)
    	at org.postgresql.core.v3.QueryExecutorImpl.sendOneQuery(QueryExecutorImpl.java:1871)
    	at org.postgresql.core.v3.QueryExecutorImpl.sendQuery(QueryExecutorImpl.java:1432)
    	at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:314)
    	... 96 more
    

    I had 54K parameters on my query. It turns out due to public void sendInteger2(int val) throws IOException PGStream.java has a maximum number of Short.MAX_VALUE – 32767

    Net for others hitting the same issue in different RDBMS systems:

    1. Postgres – 32767 Parameters
    2. IBM Db2 Limit – Maximum number of host variable references in a dynamic SQL statement -> 32,767 32,767 Parameters and 2,097,152 length of the text in the generated sql. Limits
    3. Derby – Storage capacity is the limit https://db.apache.org/derby/docs/10.14/ref/refderby.pdf
  • Bulk Data Configurations for IBM FHIR Server’s Storage Providers

    As of IBM FHIR Server 4.10.2

    A colleague of mine is entering into the depths of the IBM FHIR Server’s Bulk Data feature. Each tenant in the IBM FHIR Server may specify multiple storageProviders. The default tenant is assumed, unless specified with the Http Headers X-FHIR-BULKDATA-PROVIDER and X-FHIR-BULKDATA-PROVIDER-OUTCOME. Each tenant’s configuration may mix the different providers, however each provider is only of a single type. For instance, minio is aws-s3 and default is file and az is azure-blob.

    Note, type http is only applicable to $import operations. Export is only supported with s3, azure-blob and file.

    File Storage Provider Configuration

    The file storage provider uses a directory local to the IBM FHIR Server. The fileBase is an absolute path that must exist. Each import inputUrl is a relative path from the fileBase. The File Provider is available for both import and export. Authentication is not supported.

    {
        "__comment": "IBM FHIR Server BulkData - File Storage configuration",
        "fhirServer": {
            "bulkdata": {
                "__comment" : "The other bulkdata configuration elements are skipped",
                "storageProviders": {
                    "default": {
                        "type": "file",
                        "fileBase": "/opt/wlp/usr/servers/fhir-server/output",
                        "disableOperationOutcomes": true,
                        "duplicationCheck": false,
                        "validateResources": false,
                        "create": false
                    }
                }
            }
        }
    }
    

    An example request is:

    {
        "resourceType": "Parameters",
        "id": "30321130-5032-49fb-be54-9b8b82b2445a",
        "parameter": [
            {
                "name": "inputSource",
                "valueUri": "https://my-server/source-fhir-server"
            },
            {
                "name": "inputFormat",
                "valueString": "application/fhir+ndjson"
            },
            {
                "name": "input",
                "part": [
                    {
                        "name": "type",
                        "valueString": "AllergyIntolerance"
                    },
                    {
                        "name": "url",
                        "valueUrl": "r4_AllergyIntolerance.ndjson"
                    }
                ]
            },
            {
                "name": "storageDetail",
                "valueString": "file"
            }
        ]
    }
    

    HTTPS Storage Provider Configuration

    The http storage provider uses a set of validBaseUrls to confirm $import inputUrl is acceptable. The Https Provider is available for import. Authentication is not supported.

    {
        "__comment": "IBM FHIR Server BulkData - Https Storage configuration (Import only)",
        "fhirServer": {
            "bulkdata": {
                "__comment" : "The other bulkdata configuration elements are skipped",
                "storageProviders": {
                    "default": {
                        "type": "https",
                        "__comment": "The whitelist of valid base urls, you can always disable",
                        "validBaseUrls": [],
                        "disableBaseUrlValidation": true,
                        "__comment": "You can always direct to another provider",
                        "disableOperationOutcomes": true,
                        "duplicationCheck": false,
                        "validateResources": false
                    }
                }
            }
        }
    }
    

    An example request is:

    {
        "resourceType": "Parameters",
        "id": "30321130-5032-49fb-be54-9b8b82b2445a",
        "parameter": [
            {
                "name": "inputSource",
                "valueUri": "https://my-server/source-fhir-server"
            },
            {
                "name": "inputFormat",
                "valueString": "application/fhir+ndjson"
            },
            {
                "name": "input",
                "part": [
                    {
                        "name": "type",
                        "valueString": "AllergyIntolerance"
                    },
                    {
                        "name": "url",
                        "valueUrl": "https://validbaseurl.com/r4_AllergyIntolerance.ndjson"
                    }
                ]
            },
            {
                "name": "storageDetail",
                "valueString": "https"
            }
        ]
    }
    

    Azure Storage Provider Configuration

    The azure-blob storage provider uses a connection string from the Azure Blob configuration. The bucketName is the blob storage name. The azure-blob provider supports import and export. Authentication and configuration are built into the Connection string.

        "__comment": "IBM FHIR Server BulkData - Azure Blob Storage configuration",
        "fhirServer": {
            "bulkdata": {
                "__comment" : "The other bulkdata configuration elements are skipped",
                "storageProviders": {
                    "default": {
                        "type": "azure-blob",
                        "bucketName": "fhirtest",
                        "auth": {
                            "type": "connection",
                            "connection": "DefaultEndpointsProtocol=https;AccountName=fhirdt;AccountKey=ABCDEF==;EndpointSuffix=core.windows.net"
                        },
                        "disableBaseUrlValidation": true,
                        "disableOperationOutcomes": true
                    }
                }
            }
        }
    }```

    An example request is:

    {
        "resourceType": "Parameters",
        "id": "30321130-5032-49fb-be54-9b8b82b2445a",
        "parameter": [
            {
                "name": "inputSource",
                "valueUri": "https://my-server/source-fhir-server"
            },
            {
                "name": "inputFormat",
                "valueString": "application/fhir+ndjson"
            },
            {
                "name": "input",
                "part": [
                    {
                        "name": "type",
                        "valueString": "AllergyIntolerance"
                    },
                    {
                        "name": "url",
                        "valueUrl": "r4_AllergyIntolerance.ndjson"
                    }
                ]
            },
            {
                "name": "storageDetail",
                "valueString": "azure-blob"
            }
        ]
    }
    

    S3 Storage Provider Configuration

    The aws-s3 storage provider supports import and export. The bucketName, location, auth style (hmac, iam), endpointInternal, endpointExternal are separate values in the configuration. Note, enableParquet is obsolete.

    {
        "__comment": "IBM FHIR Server BulkData - AWS/COS/Minio S3 Storage configuration",
        "fhirServer": {
            "bulkdata": {
                "__comment" : "The other bulkdata configuration elements are skipped",
                "storageProviders": {
                    "default": {
                        "type": "aws-s3",
                        "bucketName": "myfhirbucket",
                        "location": "us-east-2",
                        "endpointInternal": "https://s3.us-east-2.amazonaws.com",
                        "endpointExternal": "https://myfhirbucket.s3.us-east-2.amazonaws.com",
                        "auth": {
                            "type": "hmac",
                            "accessKeyId": "AKIAAAAF2TOAAATMAAAO",
                            "secretAccessKey": "mmmUVsqKzAAAAM0QDSxH9IiaGQAAA"
                        },
                        "enableParquet": false,
                        "disableBaseUrlValidation": true,
                        "exportPublic": false,
                        "disableOperationOutcomes": true,
                        "duplicationCheck": false,
                        "validateResources": false,
                        "create": false,
                        "presigned": true,
                        "accessType": "host"
                    }
                }
            }
        }
    }
    

    An example request is:

    {
        "resourceType": "Parameters",
        "id": "30321130-5032-49fb-be54-9b8b82b2445a",
        "parameter": [
            {
                "name": "inputSource",
                "valueUri": "https://my-server/source-fhir-server"
            },
            {
                "name": "inputFormat",
                "valueString": "application/fhir+ndjson"
            },
            {
                "name": "input",
                "part": [
                    {
                        "name": "type",
                        "valueString": "AllergyIntolerance"
                    },
                    {
                        "name": "url",
                        "valueUrl": "r4_AllergyIntolerance.ndjson"
                    }
                ]
            },
            {
                "name": "storageDetail",
                "valueString": "aws-s3"
            }
        ]
    }

    Note, you can exchange aws-s3 and ibm-cos as the parameter.where(name=’storageDetail’). These are treated interchangeably.

    There are lots of configurations that are possible, I hope this helps you.

  • Not Yet Another Docker to Rancher Desktop Alternative

    With the change to Docker, Docker is changing its license going forward with Docker Desktop as noted in their license and blog. Much like a former colleague of mine’s article YADPBP: Yet Another Docker to Podman Blog Post, I have entered into the Docker Desktop migration.

    I’ve tried minikube, microk8s, podman, Lima-vm and Rancher Desktop. Many of these solutions run a single container, such as multipass. In fact, I tried using Multipass with Podman installed inside of the multipass vm. I found the networking and forwarding needs while testing multiple containers to a local dev environment was a pain. I spent a few days working with minikube, microk9s, podman and ended up on Racher Desktop.

    Rancher Desktop has flavors for Mac and Linux (I don’t run Windows as a base OS anymore). I downloaded one of the tech preview releases from GitHub and installed. It’s fairly simple, and they have a straight-forward readme. One trick, be sure to install / setup nerdctl.

    select nerdctl

    nerdctl is a Docker-compatible commandline replacement and integrate seamlessly with Rancher Desktop.

    ~/$ nerdctl run -p 9443:9443 --name fhir -e BOOTSTRAP_DB=true ibmcom/ibm-fhir-server
    docker.io/ibmcom/ibm-fhir-server:latest:                                          resolved       |++++++++++++++++++++++++++++++++++++++| 
    manifest-sha256:41f6894fa546899e02e4a8d2370bb6910eb72ed77ec58ae06c3de5e12f3ebb1c: done           |++++++++++++++++++++++++++++++++++++++| 
    config-sha256:3c912cc1a5b7c69ae15c9b969ae0085839b926e825b6555a28518458f4bd4935:   done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:06038631a24a25348b51d1bfc7d0a0ee555552a8998f8328f9b657d02dd4c64c:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:661abc6f8cb3c6d78932032ce87eb294f43f6eca9daa7681816d83ee0f62fb3d:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:e74a68c65fb24cc6fabe5f925d450cae385b2605d8837d5d7500bdd5bad7f268:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:262268b65bd5f33784d6a61514964887bc18bc00c60c588bc62bfae7edca46f1:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:d5e08b0b786452d230adf5d9050ce06b4f4d73f89454a25116927242507b603b:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:50dc68e56d6bac757f0176b8b49cffc234879e221c64a8481805239073638fb4:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:1831e571c997bd295bd5ae59bfafd69ba942bfe9e63f334cfdc35a8c86886d47:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:d29b7147ca6a2263381a0e4f3076a034b223c041d2a8f82755c30a373bb6ade7:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:a2643035bb64ff12bb72e7b47b1d88e0cdbc3846b5577a9ee9c44baf7c707b20:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:3ba05464ea94778cacf3f55c7b11d7d41293c1fc169e9e290b48e2928eaad779:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:6fb3372b06eb12842f94f14039c1d84608cbed52f56d3862f2c545d65e784a00:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:4cf8515f0f05c79594b976e803ea54e62fcaee1f6e5cfadb354ab687b758ed55:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:4debf1aa73b3e81393dc46e2f3c9334f6400e5b0160beb00196d0e5803af1e63:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:ecaacecff5f80531308a1948790550b421ca642f57b78ea090b286f74f3a7ba1:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:1ccf6767107a3807289170cc0149b6f60b5ed2f52ba3ba9b00b8d320951c4317:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:8144e53119b8ac586492370a117aa83bc31cf439c70663a58894fc1dfe9a4e08:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:16bdcde4e18e3d74352c7e42090514c7f2e0213604c74e5a6bf938647c195546:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:e9726188008a01782dcb61103c7d892f605032386f5ba7ea2acbcb6cf9770a0e:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:c37730e2eaef6bbb446d2ebe5ec230eef4abdb36e6153778d1ae8416f5543e7d:    done           |++++++++++++++++++++++++++++++++++++++| 
    layer-sha256:35d3a4502906b8e3a4c962902925f8e1932c8fb012fa84e875494049d8a6b324:    done           |++++++++++++++++++++++++++++++++++++++| 
    elapsed: 94.3s                                                                    total:  696.1  (7.4 MiB/s)                                       
    bootstrap.sh - [INFO]: 2021-12-07_19:42:09 - Current directory: /opt/ibm-fhir-server
    bootstrap.sh - [INFO]: 2021-12-07_19:42:09 - Performing Derby database bootstrapping
    2021-12-07 19:42:11.348 00000001    INFO .common.JdbcConnectionProvider Opening connection to database: jdbc:derby:/output/derby/fhirDB;create=true
    2021-12-07 19:42:13.138 00000001 WARNING ls.pool.PoolConnectionProvider Get connection took 1.791 seconds
    2021-12-07 19:42:13.382 00000001    INFO m.fhir.schema.app.LeaseManager Requesting update lease for schema 'APP' [attempt 1]
    

    When you see ready to run a smarter planet, the server is started.

    [12/7/21, 19:45:00:437 UTC] 00000027 FeatureManage A   CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 20.229 seconds.
    

    When running the $healthcheck, you see:

    curl -u fhiruser:change-password https://localhost:9443/fhir-server/api/v4/\$healthcheck -k -H "Prefer: return=OperationOutcome"
    {"resourceType":"OperationOutcome","issue":[{"severity":"information","code":"informational","details":{"text":"All OK"}}]}
    

    Racher Desktop is up… time to run with it…