Skip to content

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

gridappsd-go

Build, vet, and test CodeQL Go 1.24

A Go client library for GridAPPS-D / GOSS. It mirrors the connection and message-bus layer of the gridappsd-python client in idiomatic Go: connection and two-step token authentication, STOMP transport, publish/subscribe messaging, and correlated request/reply. The query API is not ported; see Status below.

Status

The connection, messaging, and transport layers described below are implemented and covered by tests. The typed CIM query API (the gridappsd-python GridAPPSDClient request helpers) is not yet ported; see CHANGELOG.md for what has shipped so far.

Installation

go get github.com/GRIDAPPSD/gridappsd-go

Requires Go 1.24 or later.

Quick start

Connecting

gridappsd.Connect dials the broker and performs GOSS's two-step token authentication. The zero-value Config is fail-closed: it dials TLS using the system trust store. Set AllowPlaintext: true to opt into a plain TCP connection, for example against a local development broker that has no TLS terminator in front of it. A plaintext connection sends the connect credentials and the GOSS auth token unencrypted on the wire, so only opt in against a broker and network path you trust. Connect and the examples below read GRIDAPPSD_USER and GRIDAPPSD_PASSWORD from the environment; neither is checked for emptiness before dialing, so an unset variable becomes an empty credential rather than a local error. Check your own environment first if a connection fails unexpectedly.

If the TLS handshake itself does not complete, Connect returns an error matching gridappsd.ErrHandshakeTimeout (comparable with errors.Is) once either the caller's own deadline or, absent one, gridappsd.DefaultHandshakeTimeout (5s) is reached. The peer having accepted the TCP connection but never completing the handshake is the signature of a plaintext broker sitting on what the caller believes is a TLS port, such as the dev broker above without AllowPlaintext set.

import (
	"context"
	"os"
	"time"

	"github.com/GRIDAPPSD/gridappsd-go/gridappsd"
)

// Connect blocks on the TLS dial and the GOSS token exchange; bound the
// context so a broker that accepts the connection but never answers cannot
// hang forever. Read credentials from the environment: the platform's
// default "manager" password must be changed on any broker beyond local
// development.
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Fail-closed default: dials TLS.
conn, err := gridappsd.Connect(ctx, gridappsd.Config{
	Address:  "gridappsd.example.org:61613",
	User:     os.Getenv("GRIDAPPSD_USER"),
	Password: os.Getenv("GRIDAPPSD_PASSWORD"),
})
if err != nil {
	// handle error
}
defer conn.Disconnect()

// Explicit opt-in for a plaintext dev broker.
devCtx, devCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer devCancel()

devConn, err := gridappsd.Connect(devCtx, gridappsd.Config{
	Address:        "localhost:61613",
	User:           os.Getenv("GRIDAPPSD_USER"),
	Password:       os.Getenv("GRIDAPPSD_PASSWORD"),
	AllowPlaintext: true,
})
if err != nil {
	// handle error
}
defer devConn.Disconnect()

Connect returns a transport.Conn. Most callers instead construct a fieldbus.MessageBus, which wraps Connect and adds subscription and request/reply routing.

Publish and subscribe

import (
	"context"
	"log"
	"os"
	"time"

	"github.com/GRIDAPPSD/gridappsd-go/fieldbus"
	"github.com/GRIDAPPSD/gridappsd-go/gridappsd"
)

// No Address set: dials DefaultAddress ("localhost:61613") over TLS, the
// fail-closed default; that port is the plaintext dev broker's port, so set
// Address explicitly against a remote broker.
bus := fieldbus.New(gridappsd.Config{
	User:     os.Getenv("GRIDAPPSD_USER"),
	Password: os.Getenv("GRIDAPPSD_PASSWORD"),
})
connectCtx, connectCancel := context.WithTimeout(context.Background(), 5*time.Second)
defer connectCancel()
if err := bus.Connect(connectCtx); err != nil {
	// handle error
}
defer bus.Disconnect()

// Subscription failures (a broker error frame, or the peer closing the
// subscription) arrive here, not as a return from Subscribe or Send. The
// channel holds 16 entries and drops the oldest when full; it is never
// closed, so the drain goroutine needs its own stop signal. On shutdown,
// the select may pick done while errors are queued; drain the channel
// again after close(done) if all errors must be seen.
done := make(chan struct{})
go func() {
	for {
		select {
		case err := <-bus.Errors():
			log.Println("subscription error:", err)
		case <-done:
			return
		}
	}
}()
defer close(done)

token, err := bus.Subscribe(context.Background(), "/topic/goss.gridappsd.field.output",
	func(headers map[string]string, body []byte) {
		// handle the delivered frame
	})
if err != nil {
	// handle error
}
defer bus.Unsubscribe(context.Background(), "/topic/goss.gridappsd.field.output", token)

// Field output carries measurements and events out of the field or
// simulation; field input carries messages, including control commands,
// into it. Do not publish to field input against a live deployment unless
// that is what's intended.
if err := bus.Send(context.Background(), "/topic/goss.gridappsd.field.input", "application/json", []byte(`{}`)); err != nil {
	// handle error
}

A destination with no /topic/, /queue/, or /temp-queue/ prefix is treated as a queue automatically (topics.NormalizeDestination).

Request and reply

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

reply, err := bus.GetResponse(ctx,
	"goss.gridappsd.process.request.status.platform", "application/json", []byte(`{}`))
if err != nil {
	// handle error
}
log.Println("platform status:", string(reply))

GetResponse sends body with a temporary reply-to destination and returns the first reply, bounded by the context deadline: there is no separate timeout argument. context.Background() never expires, so a request nobody answers would block forever; bound it as shown above.

Packages

  • gridappsd: Connect and Config, the broker dial and GOSS authentication.
  • fieldbus: MessageBus, the publish/subscribe and request/reply abstraction built on gridappsd.Connect.
  • message: STOMP frame header name constants used by the GOSS protocol.
  • topics: destination-naming helpers and well-known GridAPPS-D topic constants.
  • transport: the STOMP connection interfaces (Conn, Subscription, Dialer) that fieldbus and gridappsd are built on. The concrete implementation is internal; tests and examples use in-process fakes.

Runnable examples for the packages above are in each package's example_test.go and render on pkg.go.dev.

Versioning and releases

This module follows Go's semantic-import-versioning conventions: releases are tagged vMAJOR.MINOR.PATCH on main. See CHANGELOG.md for a running record of what changed between releases, and .github/release-notes/ for the per-release notes published alongside each tag.

License and notice

Licensed under the terms in LICENSE, the same license as the IEEE 2030.5 server, core and client repositories. See NOTICE for third-party attributions. The Battelle Memorial Institute disclaimers are in LICENSE.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages