Introduction to the Inventory service: A replacement for SMD

  1. Inventory Service Overview
    1. IDs in the Inventory Service
  2. Running the Service
    1. Run in a Quadlet
    2. Run from local binary
    3. Run using docker image
      1. Example building locally
    4. Run in the quickstart
    5. Test With SMD
  3. REST Endpoints

Inventory Service Overview

Inventory service is a replacement for the older SMD service. It is an OpenCHAMI service developed using Fabrica.

Inventory service implements enough of the APIs on SMD to be a drop in replacement for SMD in OpenCHAMI. The goal is to support enough of the original SMD APIs, such that exiting clients of SMD do not need to change. For example, PCS should work with either SMD or Inventory service. Inventory service is not 100% backward compatible with SMD.

The primary way that Inventory service differs from SMD is that SMD supports discovering nodes. i.e. it makes the redfish calls to the Node’s Management processor and collects all the information about the node. Inventory service does not do this.

The rationales for creating Inventory service are as follows:

  1. To be more flexible in the data that is stored. For example, to allow IDs other than xnames.
  2. To support the OpenCHAMI style REST API
  3. To make it easier to add new APIs and capabilities.
  4. To have a service that is less complex then the current SMD.
    • It does this by not bringing forward the discovery capabilities.

IDs in the Inventory Service

Fabrica based services assign a UID to each resource when it is created. SMD allows the user to pick the ID for each resource, with the exception that the ID is limited to the xname format. Inventory assigns UIDs at creation time in the same way as any other fabrica based service. It also enforces uniqueness for the fields that were IDs in SMD; however, it does not require that these IDs be xnames.

ResourceUnique Spec field
ComponentSpec.ID
ComponentEndpointSpec.ID
EthernetInterfaceSpec.ID
GroupSpec.Label
HardwareSpec.ID
RedfishEndpointSpec.ID
ServiceEndpointSpec.RedfishType + “-” + Spec.RedfishEndpointID

The uniqueness of these fields are enforced in the Database schema.

Running the Service

The service can be run in multiple ways

Run in a quadlet

This shows to how to run as a quadlet as is done in the OpenCHAMI Tutorial

Create the file inventory-service.container in /etc/containers/systemd with the following contents

[Unit]
Description=The inventory service container
PartOf=openchami.target

# Don’t start until the network has started
Requires=openchami-internal-network.service openchami-jwt-internal-network.service
After=openchami-internal-network.service openchami-jwt-internal-network.service

# Don’t start until JWKS is ready:
Wants=hydra-gen-jwks.service
After=hydra-gen-jwks.service

[Container]
ContainerName=inventory-service
HostName=inventory
Image=ghcr.io/openchami/inventory-service:0.1.0

# Environment Variables
EnvironmentFile=/etc/openchami/configs/openchami.env

# Secrets
# Secret=

# Networks for the Container to use
Network=openchami-internal.network

# Unsupported by generator options
# Proxy settings
PodmanArgs=--http-proxy=false

[Service]
Restart=always

Run from local binary

make build
rm -rf data
mkdir -p data
./bin/inventory-service serve --database-url "file:data/inventory-service.db?_fk=1"

See the learn by example section in Fabrica

Run using docker image

rm -rf data
mkdir -p data
docker run --rm --name inventory-service -d -p 8080:8080 -v $(pwd)/data:/data ghcr.io/openchami/inventory-service:0.1.0

fabrica style

curl -s -X GET http://localhost:8080/components | jq

SMD style

curl -s -X GET http://localhost:8080/hsm/v2/State/Components | jq

Stop the inventory container

docker container stop inventory-service

Example building locally

make build image
rm -rf data
mkdir -p data
docker run --rm --name inventory-service -d -p 8080:8080 -v $(pwd)/data:/data inventory-service:latest

SMD style request for the component resource

curl -s -X GET http://localhost:8080/hsm/v2/State/Components | jq
{
  "Components": []
}

Fabrica (OpenCHAMI) style request for the same resource.

curl -s -X GET http://localhost:8080/components | jq
null

Post a Component using the SMD style API

curl -s -X POST http://localhost:8080/hsm/v2/State/Components \
  -H "Content-Type: application/json" \
  -d '{ "Components": [{
      "ID": "x3000c0s19b1",
      "Type": "NodeBMC",
      "State": "Ready",
      "Flag": "OK",
      "Enabled": true,
      "NetType": "Sling",
      "Arch": "X86",
      "Class": "River"
  }]}'
curl -s -X GET http://localhost:8080/components | jq
[
  {
    "apiVersion": "v1",
    "kind": "Component",
    "metadata": {
      "name": "x3000c0s19b1",
      "uid": "component-673d2bde",
      "createdAt": "2026-04-23T18:20:11.798823051Z",
      "updatedAt": "2026-04-23T18:20:11.798823051Z"
    },
    "id": "x3000c0s19b1",
    "spec": {
      "ID": "x3000c0s19b1",
      "Type": "NodeBMC",
      "State": "Ready",
      "Flag": "OK",
      "Enabled": true,
      "NetType": "Sling",
      "Arch": "X86",
      "Class": "River"
    },
    "status": {
      "ready": false
    }
  }
]
curl -s -X GET http://localhost:8080/hsm/v2/State/Components | jq
{
  "Components": [
    {
      "ID": "x3000c0s19b1",
      "Type": "NodeBMC",
      "State": "Ready",
      "Flag": "OK",
      "Enabled": true,
      "NetType": "Sling",
      "Arch": "X86",
      "Class": "River"
    }
  ]
}

POSTing the same resource again

curl -s -X POST http://localhost:8080/hsm/v2/State/Components   -H "Content-Type: application/json"   -d '{ "Components": [{
      "ID": "x3000c0s19b1",
      "Type": "NodeBMC",
      "State": "Ready",
      "Flag": "OK",
      "Enabled": true,
      "NetType": "Sling",
      "Arch": "X86",
      "Class": "River"
  }]}' | jq
{
  "error": "failed to save Component: failed to create Component: ent: constraint failed: UNIQUE constraint failed: resources.resource_type, resources.resource_id",
  "code": 500
}

The error comes directly from the Database and refers to fields in the database not in the json. In the future we may improve this. resource_type is the Kind field in the fabrica style API’s output. resource_id is an internal field. For Components this contains the same value as the ID field.

Stop the inventory container

docker stop inventory-service

Run in the quickstart

This shows how to run in the quickstart guide

Replace the SMD Init and Server Containers with the following

services:
###
# Inventory Service Container
###
  inventory-service:
    image: ghcr.io/openchami/inventory-service:0.1.0
    container_name: inventory-service
    hostname: inventory
    networks:
      - internal

Test With SMD

Running make test-compare-to-smd starts a docker compose environment with both SMD and the Inventory Service. It also starts a few instances of the Redfish Interface Emulator (RIE). This does does the following

  1. Starts a compose environment with the Inventory Service, SMD, and RIE.
  2. Makes requests to SMD to discover the Redfish endpoints emulated by RIE.
  3. Makes REST calls to SMD to get the Resources and POSTs those to the Inventory Service.
  4. Compares the Resources in SMD to the Inventory Service.
  5. Stops the compose environment.

Here is an example of running the parts of this test

make all
make start-inventory-and-smd
docker ps --no-trunc --format "table {{.Names}}\t{{.Status}}\t{{.Command}}"
NAMES               STATUS                    COMMAND
smd                 Up 9 seconds (healthy)    "/sbin/tini -- /smd"
postgres            Up 22 seconds (healthy)   "docker-entrypoint.sh postgres"
rf-x0c0s2b0         Up 22 seconds             "python3 emulator.py"
rf-x0c0s1b0         Up 22 seconds             "python3 emulator.py"
rf-x0c0s3b0         Up 22 seconds             "python3 emulator.py"
rf-x0c0s4b0         Up 22 seconds             "python3 emulator.py"
inventory-service   Up 22 seconds             "/usr/local/bin/inventory-service serve --port 8080 --database-url file:/data/inventory.db?_fk=1"

Run the tests. SMD will discover the nodes, the resources will be POSTed to the Inventory Service, and then the Resources in SMD and Inventory Service will be compared

docker run --rm -it --network inventory_internal inventory-test:latest
=========================================================== test session starts ============================================================
platform linux -- Python 3.14.2, pytest-9.0.2, pluggy-1.6.0
rootdir: /app
collected 1 item

test_compare_to_smd.py .                                                                                                             [100%]

============================================================ 1 passed in 20.05s ============================================================

The inventory-test:latest image can be used to make curl calls to the Inventory Service and to SMD.

SMD style REST call to Inventory

docker run --rm -t --network inventory_internal inventory-test:latest sh -c 'curl -s -X GET http://inventory:8080/hsm/v2/State/Components' | jq -c '.Components[] | { "ID": .ID, "Type": .Type }'
{"ID":"x0c0s1e0","Type":"NodeEnclosure"}
{"ID":"x0c0s1b0n0","Type":"Node"}
{"ID":"x0c0s1b0","Type":"NodeBMC"}
{"ID":"x0c0s3e0","Type":"NodeEnclosure"}
{"ID":"x0c0s3b0n0","Type":"Node"}
{"ID":"x0c0s3b0n1","Type":"Node"}
{"ID":"x0c0s3b0","Type":"NodeBMC"}
{"ID":"x0c0s4e0","Type":"NodeEnclosure"}
{"ID":"x0c0s4b0n0","Type":"Node"}
{"ID":"x0c0s4b0n1","Type":"Node"}
{"ID":"x0c0s4b0","Type":"NodeBMC"}
{"ID":"x0c0s2e0","Type":"NodeEnclosure"}
{"ID":"x0c0s2b0n0","Type":"Node"}
{"ID":"x0c0s2b0n1","Type":"Node"}
{"ID":"x0c0s2b0","Type":"NodeBMC"}

OpenCHAMI (Fabrica) style REST call to Inventory

docker run --rm -t --network inventory_internal inventory-test:latest sh -c 'curl -s -X GET http://inventory:8080/components' | jq -c '.[] | { "kind": .kind, "metadata": { "name": .metadata.name }, "spec" : {"ID": .spec.ID, "Type": .spec.Type} }'
{"kind":"Component","metadata":{"name":"x0c0s1e0"},"spec":{"ID":"x0c0s1e0","Type":"NodeEnclosure"}}
{"kind":"Component","metadata":{"name":"x0c0s1b0n0"},"spec":{"ID":"x0c0s1b0n0","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s1b0"},"spec":{"ID":"x0c0s1b0","Type":"NodeBMC"}}
{"kind":"Component","metadata":{"name":"x0c0s3e0"},"spec":{"ID":"x0c0s3e0","Type":"NodeEnclosure"}}
{"kind":"Component","metadata":{"name":"x0c0s3b0n0"},"spec":{"ID":"x0c0s3b0n0","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s3b0n1"},"spec":{"ID":"x0c0s3b0n1","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s3b0"},"spec":{"ID":"x0c0s3b0","Type":"NodeBMC"}}
{"kind":"Component","metadata":{"name":"x0c0s4e0"},"spec":{"ID":"x0c0s4e0","Type":"NodeEnclosure"}}
{"kind":"Component","metadata":{"name":"x0c0s4b0n0"},"spec":{"ID":"x0c0s4b0n0","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s4b0n1"},"spec":{"ID":"x0c0s4b0n1","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s4b0"},"spec":{"ID":"x0c0s4b0","Type":"NodeBMC"}}
{"kind":"Component","metadata":{"name":"x0c0s2e0"},"spec":{"ID":"x0c0s2e0","Type":"NodeEnclosure"}}
{"kind":"Component","metadata":{"name":"x0c0s2b0n0"},"spec":{"ID":"x0c0s2b0n0","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s2b0n1"},"spec":{"ID":"x0c0s2b0n1","Type":"Node"}}
{"kind":"Component","metadata":{"name":"x0c0s2b0"},"spec":{"ID":"x0c0s2b0","Type":"NodeBMC"}}

REST call to SMD

docker run --rm -t --network inventory_internal inventory-test:latest sh -c 'curl -s -X GET http://smd:27779/hsm/v2/State/Components' | jq -c '.Components[] | { "ID": .ID, "Type": .Type }'
make stop-inventory-and-smd

REST Endpoints

ResourceInventory (Fabrica Style)Inventory (SMD Style)
Component/components/hsm/v2/Status/Components
ComponentEndpoint/componentendpoints/hsm/v2/Inventory/ComponentEndpoints
EthernetInterface/ethernetinterfaces/hsm/v2/Inventory/EthernetInterfaces
Group/groups/hsm/v2/groups
Hardware/hardwares/hsm/v2/Inventory/Hardware
RedfishEndpoint/redfishendpoints/hsm/v2/Inventory/RedfishEndpoints
ServiceEndpoint/serviceendpoints/hsm/v2/Inventory/ServiceEndpoints
health/health/hsm/v2/service/ready
liveness/hsm/v2/service/liveness