DSX Agent Deployment Guide#

Deploy the DSX Agent to connect Dynamic Power Software (DPS) NvGrid power management to DSX Exchange. This guide is for Kubernetes administrators configuring the bidirectional connection.

The configuration and topics in this guide match DPS 0.10.0-rc1. Select the corresponding DSX Agent chart and container image from your release artifacts. Do not assume the agent chart version, image tag, and DPS version are identical.

Connection Overview#

The DSX Agent runs as a separate Kubernetes Deployment. It connects to DPS over gRPC, DSX Exchange over Message Queuing Telemetry Transport (MQTT), and PostgreSQL for persistent message queues.

The agent implements the following message paths:

  • Load targets: Subscribe to grid/v1/isv/+/loadtarget/set, validate incoming CloudEvents and JSON Web Signatures (JWS), queue the messages, and submit load schedules to the DPS NvGrid API.

  • Power state: Read NvGrid metadata, schedules, and status, then publish signed grid.powerstate.status.v1 CloudEvents on grid/v1/dsx-flex-agent/<agent-id>/powerstate/status. The default periodic interval is 30 seconds.

  • Power events: Register the agent webhook with DPS. DPS calls the agent when ramp-up or ramp-down starts or ends, and the agent publishes the resulting power state to DSX Exchange.

The agent provides these load-target and power-state operations. It does not implement every DSX OS integration. For NvGrid concepts and feed configuration, refer to NvGrid and Managing NvGrid.

Prepare the Deployment#

Before you install the agent, prepare the following resources:

  • A configured DPS server, reachable from the agent over verified Transport Layer Security (TLS). Configure feed metadata and the power-management topology before testing load targets. Refer to Deployment and Configuration Guide.

  • A DPS automation identity authorized to call the NvGrid API. The example uses a DPS password login. Use the authentication mode configured for your server.

  • A PostgreSQL database and role for agent queues. The agent applies its database migrations at startup; the role must be able to create and use the required tables, type, function, and trigger.

  • DSX Exchange broker addresses, credentials, and topic permissions from your Exchange administrator. Allow the agent to subscribe to grid/v1/isv/+/loadtarget/set and publish to its power-state topic. This release also subscribes to the legacy grid.setloadtarget.v1 topic for migration compatibility; include that subscription in the broker permissions.

  • A JWS signing private key for outbound messages and trusted public keys for inbound messages. Agree on the algorithm and key IDs with the Exchange participants. Register the agent public key with the participants that verify its outbound messages.

  • Network access to DPS, PostgreSQL, and the MQTT broker, plus your identity provider if you use OAuth 2.0. Allow DPS to reach the agent webhook on TCP port 9100.

  • Helm, kubectl, release-matched dpsctl, and permission to create the chart resources and Secrets in the target namespace.

This procedure uses namespace dps, DPS release dps, and agent release dps-dsx-agent. The broker certificate must chain to a certificate authority (CA) trusted by the agent container. PostgreSQL and DPS use the CA files you supply. Replace example hostnames, identifiers, and file paths with values for your environment.

Select the Chart and Image#

Obtain the versioned DSX Agent chart package and corresponding image tag from your release distribution. Inspect the package before installation:

export DSX_AGENT_CHART="/path/to/dps-dsx-agent-<chart-version>.tgz"
export DSX_AGENT_IMAGE_TAG="<release-image-tag>"
helm show chart "${DSX_AGENT_CHART}"
helm show values "${DSX_AGENT_CHART}" > dsx-agent-reference.yaml

The agent chart is installed separately from the DPS chart. Retain the reference values for your selected package. If you use a private image mirror, configure image.repository and imagePullSecrets for that mirror.

Create the Secrets#

Create the dps namespace if it does not exist:

kubectl create namespace dps --dry-run=client -o yaml | kubectl apply -f -

Store credentials in local files protected by your organization’s secret handling process. Create Kubernetes Secrets from those files. The following commands use an Exchange access token and a DPS login password:

Create Credential and Key Secrets
kubectl create secret generic dsx-agent-db --namespace dps \
  --from-file=password=/secure/path/postgres-password
kubectl create secret generic dsx-agent-mqtt --namespace dps \
  --from-file=token=/secure/path/exchange-token
kubectl create secret generic dsx-agent-dps-auth --namespace dps \
  --from-file=password=/secure/path/dps-password
kubectl create secret generic dsx-agent-dps-ca --namespace dps \
  --from-file=ca.crt=/secure/path/dps-ca.crt
kubectl create secret generic dsx-agent-db-ca --namespace dps \
  --from-file=ca.crt=/secure/path/postgres-ca.crt
kubectl create secret generic dsx-agent-jws --namespace dps \
  --from-file=signing-key.pem=/secure/path/agent-signing-key.pem \
  --from-file=exchange-public-key.pem=/secure/path/exchange-public-key.pem

The signing key stays with the agent. Share its public key, not the private key. The verification key in this example belongs to the participant that signs incoming load-target messages. If the message includes a JWS kid header, it must match the configured verification key ID.

Keep credentials out of Helm values and command-line literals. If your platform provisions Secrets, use the same Secret names and keys or adapt the values to your platform’s resources.

Configure the Agent#

Save the following overrides as dsx-agent-values.yaml. This example uses RSA signing with RS256, verified TLS, PostgreSQL server verification, and mounted credentials:

DSX Agent Values
fullnameOverride: dps-dsx-agent
agentConfig:
  agentName: dps
  logLevel: info
  database:
    hostname: postgres.example.com
    port: 5432
    name: dps
    user: dps
    existingSecret: dsx-agent-db
    existingSecretKey: password
    ssl_mode: verify-full
    ssl_root_cert: /secrets/db-ca/ca.crt
  mqtt:
    servers: ["tls://exchange.example.com:8883"]
    clientId: dps-dsx-agent
    username: oauthtoken
    tokenFile: /secrets/mqtt/token
    secretRef:
      name: dsx-agent-mqtt
      key: token
    tls:
      insecure_skip_verify: false
  jws:
    signatureAlgorithm: RS256
    signatureKeyFile: /secrets/jws/signing-key.pem
    signatureKeyId: dps-agent-signing-key
    verificationPublicKeyFiles:
      exchange-signing-key: /secrets/jws/exchange-public-key.pem
  dps:
    serverAddr: dps-server-grpc:8080
    tls:
      transportSecurity: tls
      secretRef:
        name: dsx-agent-dps-ca
    auth:
      dps:
        username: dps-agent
      secretRef:
        name: dsx-agent-dps-auth
        key: password
  dpsWebhookBindAddress: ":9100"
  dpsWebhookAnnounceAddress: http://dps-dsx-agent-webhook:9100
  handlers:
    PowerState:
      enabled: true
      agentId: dps
      publishPeriod: 30s
networkPolicy:
  dps:
    releaseName: dps
    namespace: dps
secrets:
  - name: dsx-agent-db-ca
    secretKey: ca.crt
    mountPath: /secrets/db-ca/ca.crt
  - name: dsx-agent-jws
    secretKey: signing-key.pem
    mountPath: /secrets/jws/signing-key.pem
  - name: dsx-agent-jws
    secretKey: exchange-public-key.pem
    mountPath: /secrets/jws/exchange-public-key.pem

Set handlers.PowerState.agentId to the identifier assigned for your deployment; it determines the outbound MQTT topic. Use a unique MQTT clientId for each simultaneously connected agent instance. The example uses the default single replica.

The dps.serverAddr value uses the standard DPS Helm gRPC Service. Adapt its host and port if your release overrides them. The server certificate must cover the hostname you configure.

The chart supports the following authentication alternatives:

  • MQTT: Replace tokenFile with oauth2 settings if Exchange provides client credentials. Configure tokenUrl, scopes, and credentialsFile, and point mqtt.secretRef to the credentials Secret. The credential file defaults to JSON fields client_id and client_secret. Select only one MQTT credential source.

  • DPS: Use a bearer-token Secret by omitting auth.dps and configuring auth.secretRef. For OAuth 2.0, replace auth.dps with auth.oauth2 settings and use a credentials Secret. Select only one DPS credential source.

  • Mutual TLS: Set dps.tls.transportSecurity: mtls and reference a Secret containing ca.crt, tls.crt, and tls.key.

For incoming-command-only deployments, set handlers.PowerState.enabled: false. Verification keys and the signature algorithm are still required; the agent does not initialize the outbound signer or register the power-state webhook in this mode.

Configure Webhook Delivery#

The standard webhook URL is http://dps-dsx-agent-webhook:9100. The DPS chart in 0.10.0-rc1 includes this URL in dps.nvgrid.webhook.whitelist. Confirm that your server’s deployed values permit it before starting the agent.

If you use another service name or namespace, update the announce URL and the DPS whitelist to match. For a separate agent namespace, use a service DNS name that resolves from the DPS namespace. Set networkPolicy.dps.releaseName and networkPolicy.dps.namespace to the server’s release and namespace. Retain any other whitelist entries your deployment requires.

The webhook listener uses HTTP. Restrict its ingress to DPS. The chart enables an ingress NetworkPolicy for this purpose; your cluster network plugin must enforce NetworkPolicy. If your security policy requires encryption on this path, provide it through your platform’s service mesh or gateway and configure the announced URL and allowed peers accordingly.

Install and Check Startup#

Install the selected chart with your overrides and explicit image tag:

helm upgrade --install dps-dsx-agent "${DSX_AGENT_CHART}" \
  --namespace dps \
  --values dsx-agent-values.yaml \
  --set-string image.tag="${DSX_AGENT_IMAGE_TAG}" \
  --wait --timeout 5m

Check the Deployment and its logs:

kubectl rollout status deployment/dps-dsx-agent --namespace dps
kubectl logs deployment/dps-dsx-agent --namespace dps --tail=200

Confirm that the logs report connected to mqtt and, with power-state publication enabled, starting DPS webhook server. Resolve any database, JWS, subscription, or DPS authentication errors. Webhook registration happens asynchronously; a registration failure can leave the agent running without webhook delivery.

The health endpoints return HTTP 200 while their server is running. A ready pod establishes process health but does not establish successful message delivery, webhook registration, or power control.

Verify the Signed Message Flow#

Use your configured dpsctl connection to confirm that NvGrid has feed metadata and readable status:

dpsctl nvgrid get-metadata
dpsctl nvgrid get-status
dpsctl nvgrid get-current

For client connection and authentication setup, refer to Installing dpsctl. For schedule queries, refer to dpsctl nvgrid.

Use the DPS 0.10.0-rc1 DSX Flex AsyncAPI schema archive when preparing the test command. Extract dsx-flex.yaml from the archive. The schema defines the envelope at components.schemas.LoadTargetSetEvent and the payload at components.schemas.SetLoadTargetData.

Set type to grid.loadtarget.set.v1 and specversion to "1.0.2". Sign the JSON payload and place its compact JWS serialization in the envelope’s data string. The JWS protects only that payload; the envelope fields, including the correlation ID, are unsigned.

The shipped DSX schema requires the exact specversion value "1.0.2". If your CloudEvents SDK emits "1.0", configure it to emit "1.0.2" for this integration; "1.0" does not pass the release schema validation.

With handlers.PowerState.enabled: true, complete the following verification with your Exchange administrator in an approved test environment:

  1. Receive a periodic message on grid/v1/dsx-flex-agent/<agent-id>/powerstate/status. Verify its JWS signature with the agent’s public key and confirm the feed metadata, schedules, and status against the NvGrid API results.

  2. Publish a schema-valid, signed load-target CloudEvent through Exchange on grid/v1/isv/<isv-id>/loadtarget/set. Use a unique event ID, a UUID correlation ID, known feed tags, and an approved target and time interval. Follow the Exchange message contract, including its tracing fields.

  3. Read the schedule for the test interval with dpsctl nvgrid get-schedule and read status with dpsctl nvgrid get-status. Confirm that DPS accepted the target. Retain the correlation ID when comparing the schedule and subsequent power-state messages.

  4. Confirm ramp events reach the agent webhook and produce signed power-state messages on Exchange. Check both the agent and DPS logs for delivery or processing errors.

  5. Confirm the target’s effects through DPS status and your test environment’s device telemetry. A received message or accepted schedule alone does not prove hardware actuation.

For incoming-command-only deployments with handlers.PowerState.enabled: false, complete steps 2, 3, and 5. Check the accepted schedule’s correlation ID and the agent’s processing logs. Skip the outbound message and webhook checks; this mode does not publish power-state messages or register that webhook.

Do not send a production load-target command solely to test connectivity. Use the approved target, rollback, and telemetry procedure for your site.

Troubleshooting#

Use the following checks to isolate deployment and message-flow failures:

Symptom

Check

Pod does not start

Check chart and image compatibility, image access, Secret names and keys, mounted-file permissions, and configuration parsing errors.

Database migration fails

Check database hostname, TLS CA and hostname verification, credentials, and migration permissions.

MQTT connection or subscription fails

Check broker DNS and egress, certificate trust, token validity or OAuth 2.0 settings, topic permissions, and duplicate client IDs.

JWS setup or message validation fails

Check the algorithm, PEM key formats, signature key ID, trusted sender key IDs, and the release-matched message schema.

Webhook registration fails

Check NvGrid authorization and the server whitelist. A non-network registration error stops webhook startup.

Periodic messages arrive but ramp events do not

Check webhook service DNS, port 9100, NetworkPolicy enforcement, allowed peers, and server delivery logs.

Load target does not appear in NvGrid

Check signature or schema rejection, feed tags, time intervals, and agent queue-processing errors.

Pod is ready but messages stop

Check dependency connectivity and message-processing logs; the readiness endpoint does not inspect these dependencies.

Update Credentials and Configuration#

Update Secrets through your platform’s credential-management process. MQTT and DPS credential Secret references use directory mounts so Kubernetes can project changed files. Token caches and connection lifetimes can delay when a new credential takes effect.

The additional secrets entries use file mounts with subPath. Restart the agent after changing those files, including JWS keys or the database CA. Also restart after changing startup-loaded verification keys or database credentials. Coordinate JWS key changes with Exchange participants before the restart.

After applying your changes, restart the Deployment and repeat the message-flow verification:

kubectl rollout restart deployment/dps-dsx-agent --namespace dps
kubectl rollout status deployment/dps-dsx-agent --namespace dps

If you supply the full configuration through config.existingSecret, keep chart-managed ports and file mounts consistent with that file. Changes to an existing configuration Secret do not add a chart-generated configuration checksum; restart the Deployment to load the updated file.