Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ go 1.25.7
replace (
github.com/Microsoft/go-winio => github.com/endocrimes/go-winio v0.4.13-0.20190628114223-fb47a8b41948
github.com/hashicorp/hcl => github.com/hashicorp/hcl v1.0.1-nomad-1

github.com/hashicorp/nomad/plugin_interface => ./plugin_interface
)

// Nomad is built using the current source of the API module.
Expand Down
222 changes: 222 additions & 0 deletions plugin_interface/base/base.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// Copyright IBM Corp. 2015, 2025
// SPDX-License-Identifier: MPL-2.0

package base

import (
"github.com/hashicorp/nomad/plugin-interface/base/proto"
"github.com/hashicorp/nomad/plugin-interface/helper"
"github.com/hashicorp/nomad/plugin-interface/lib/idset"
"github.com/hashicorp/nomad/plugin-interface/shared/hclspec"
)

// BasePlugin is the interface that all Nomad plugins must support.
type BasePlugin interface {
// PluginInfo describes the type and version of a plugin.
PluginInfo() (*PluginInfoResponse, error)

// ConfigSchema returns the schema for parsing the plugins configuration.
ConfigSchema() (*hclspec.Spec, error)

// SetConfig is used to set the configuration by passing a MessagePack
// encoding of it.
SetConfig(c *Config) error
}

// PluginInfoResponse returns basic information about the plugin such that Nomad
// can decide whether to load the plugin or not.
type PluginInfoResponse struct {
// Type returns the plugins type
Type string

// PluginApiVersions returns the versions of the Nomad plugin API that the
// plugin supports.
PluginApiVersions []string

// PluginVersion is the version of the plugin.
PluginVersion string

// Name is the plugins name.
Name string
}

// Config contains the configuration for the plugin.
type Config struct {
// ApiVersion is the negotiated plugin API version to use.
ApiVersion string

// PluginConfig is the MessagePack encoding of the plugins user
// configuration.
PluginConfig []byte

// AgentConfig is the Nomad agents configuration as applicable to plugins
AgentConfig *AgentConfig
}

// AgentConfig is the Nomad agent's configuration sent to all plugins
type AgentConfig struct {
Driver *ClientDriverConfig
}

// Compute gets the basic cpu compute availablility necessary for drivers.
func (ac *AgentConfig) Compute() Compute {
if ac == nil || ac.Driver == nil || ac.Driver.Topology == nil {
return Compute{}
}
return ac.Driver.Topology.Compute()
}

// ClientDriverConfig is the driver specific configuration for all driver plugins
type ClientDriverConfig struct {
// ClientMaxPort is the upper range of the ports that the client uses for
// communicating with plugin subsystems over loopback
ClientMaxPort uint

// ClientMinPort is the lower range of the ports that the client uses for
// communicating with plugin subsystems over loopback
ClientMinPort uint

// Topology is the system hardware topology that is the result of scanning
// hardware combined with client configuration.
// Topology_deprecated *numalib.Topology

Topology *Topology
}

func (ac *AgentConfig) toProto() *proto.NomadConfig {
if ac == nil {
return nil
}
cfg := &proto.NomadConfig{}
if ac.Driver != nil {
cfg.Driver = &proto.NomadDriverConfig{
ClientMaxPort: uint32(ac.Driver.ClientMaxPort),
ClientMinPort: uint32(ac.Driver.ClientMinPort),
Topology: nomadTopologyToProto(ac.Driver.Topology),
}
}
return cfg
}

func nomadConfigFromProto(pb *proto.NomadConfig) *AgentConfig {
if pb == nil {
return nil
}
cfg := &AgentConfig{}
if pb.Driver != nil {
cfg.Driver = &ClientDriverConfig{
ClientMaxPort: uint(pb.Driver.ClientMaxPort),
ClientMinPort: uint(pb.Driver.ClientMinPort),
Topology: nomadTopologyFromProto(pb.Driver.Topology),
}
}
return cfg
}

func nomadTopologyFromProto(pb *proto.ClientTopology) *Topology {
if pb == nil {
return nil
}
t := &Topology{
Distances: nomadTopologyDistancesFromProto(pb.Distances),
Cores: nomadTopologyCoresFromProto(pb.Cores),
OverrideTotalCompute: MHz(pb.OverrideTotalCompute),
OverrideWitholdCompute: MHz(pb.OverrideWitholdCompute),
}
t.SetNodes(idset.FromFunc(pb.NodeIds, func(i uint32) NodeID { return NodeID(i) }))

return t
}

func nomadTopologyDistancesFromProto(pb *proto.ClientTopologySLIT) SLIT {
if pb == nil {
return nil
}
size := int(pb.Dimension)
slit := make(SLIT, size)
for row := 0; row < size; row++ {
slit[row] = make([]Cost, size)
for col := 0; col < size; col++ {
index := row*size + col
slit[row][col] = Cost(pb.Values[index])
}
}
return slit
}

func nomadTopologyCoresFromProto(pb []*proto.ClientTopologyCore) []Core {
if len(pb) == 0 {
return nil
}
return helper.ConvertSlice(pb, func(pbcore *proto.ClientTopologyCore) Core {
return Core{
SocketID: SocketID(pbcore.SocketId),
NodeID: NodeID(pbcore.NodeId),
ID: CoreID(pbcore.CoreId),
Grade: nomadCoreGradeFromProto(pbcore.CoreGrade),
Disable: pbcore.Disable,
BaseSpeed: MHz(pbcore.BaseSpeed),
MaxSpeed: MHz(pbcore.MaxSpeed),
GuessSpeed: MHz(pbcore.GuessSpeed),
}
})
}

func nomadTopologyToProto(top *Topology) *proto.ClientTopology {
if top == nil {
return nil
}
return &proto.ClientTopology{
NodeIds: helper.ConvertSlice(top.GetNodes().Slice(), func(id NodeID) uint32 { return uint32(id) }),
Distances: nomadTopologyDistancesToProto(top.Distances),
Cores: nomadTopologyCoresToProto(top.Cores),
OverrideTotalCompute: uint64(top.OverrideTotalCompute),
OverrideWitholdCompute: uint64(top.OverrideWitholdCompute),
}
}

func nomadTopologyDistancesToProto(slit SLIT) *proto.ClientTopologySLIT {
dimension := len(slit)
values := make([]uint32, 0, dimension)
for row := 0; row < dimension; row++ {
for col := 0; col < dimension; col++ {
values = append(values, uint32(slit[row][col]))
}
}
return &proto.ClientTopologySLIT{
Dimension: uint32(dimension),
Values: values,
}
}

func nomadTopologyCoresToProto(cores []Core) []*proto.ClientTopologyCore {
if len(cores) == 0 {
return nil
}
return helper.ConvertSlice(cores, func(core Core) *proto.ClientTopologyCore {
return &proto.ClientTopologyCore{
SocketId: uint32(core.SocketID),
NodeId: uint32(core.NodeID),
CoreId: uint32(core.ID),
CoreGrade: nomadCoreGradeToProto(core.Grade),
Disable: core.Disable,
BaseSpeed: uint64(core.BaseSpeed),
MaxSpeed: uint64(core.MaxSpeed),
GuessSpeed: uint64(core.GuessSpeed),
}
})
}

func nomadCoreGradeFromProto(grade proto.CoreGrade) CoreGrade {
if grade == proto.CoreGrade_Performance {
return Performance
}
return Efficiency
}

func nomadCoreGradeToProto(grade CoreGrade) proto.CoreGrade {
if grade == Performance {
return proto.CoreGrade_Performance
}
return proto.CoreGrade_Efficiency
}
68 changes: 68 additions & 0 deletions plugin_interface/base/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright IBM Corp. 2015, 2025
// SPDX-License-Identifier: MPL-2.0

package base

import (
"context"
"fmt"

"github.com/hashicorp/nomad/plugin-interface/base/proto"
"github.com/hashicorp/nomad/plugin-interface/helper"
"github.com/hashicorp/nomad/plugin-interface/shared/hclspec"
)

// BasePluginClient implements the client side of a remote base plugin, using
// gRPC to communicate to the remote plugin.
type BasePluginClient struct {
Client proto.BasePluginClient

// DoneCtx is closed when the plugin exits
DoneCtx context.Context
}

func (b *BasePluginClient) PluginInfo() (*PluginInfoResponse, error) {
presp, err := b.Client.PluginInfo(b.DoneCtx, &proto.PluginInfoRequest{})
if err != nil {
return nil, helper.HandleGrpcErr(err, b.DoneCtx)
}

var ptype string
switch presp.GetType() {
case proto.PluginType_DRIVER:
ptype = PluginTypeDriver
case proto.PluginType_DEVICE:
ptype = PluginTypeDevice
default:
return nil, fmt.Errorf("plugin is of unknown type: %q", presp.GetType().String())
}

resp := &PluginInfoResponse{
Type: ptype,
PluginApiVersions: presp.GetPluginApiVersions(),
PluginVersion: presp.GetPluginVersion(),
Name: presp.GetName(),
}

return resp, nil
}

func (b *BasePluginClient) ConfigSchema() (*hclspec.Spec, error) {
presp, err := b.Client.ConfigSchema(b.DoneCtx, &proto.ConfigSchemaRequest{})
if err != nil {
return nil, helper.HandleGrpcErr(err, b.DoneCtx)
}

return presp.GetSpec(), nil
}

func (b *BasePluginClient) SetConfig(c *Config) error {
// Send the config
_, err := b.Client.SetConfig(b.DoneCtx, &proto.SetConfigRequest{
MsgpackConfig: c.PluginConfig,
NomadConfig: c.AgentConfig.toProto(),
PluginApiVersion: c.ApiVersion,
})

return helper.HandleGrpcErr(err, b.DoneCtx)
}
86 changes: 86 additions & 0 deletions plugin_interface/base/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Copyright IBM Corp. 2015, 2025
// SPDX-License-Identifier: MPL-2.0

package base

import (
"bytes"
"context"
"reflect"

"github.com/hashicorp/go-msgpack/v2/codec"
plugin "github.com/hashicorp/go-plugin"
"github.com/hashicorp/nomad/plugin-interface/base/proto"
"google.golang.org/grpc"
)

const (
// PluginTypeBase implements the base plugin interface
PluginTypeBase = "base"

// PluginTypeDriver implements the driver plugin interface
PluginTypeDriver = "driver"

// PluginTypeDevice implements the device plugin interface
PluginTypeDevice = "device"
)

var (
// Handshake is a common handshake that is shared by all plugins and Nomad.
Handshake = plugin.HandshakeConfig{
// ProtocolVersion for the executor protocol.
// Version 1: pre 0.9 netrpc based executor
// Version 2: 0.9+ grpc based executor
ProtocolVersion: 2,
MagicCookieKey: "NOMAD_PLUGIN_MAGIC_COOKIE",
MagicCookieValue: "e4327c2e01eabfd75a8a67adb114fb34a757d57eee7728d857a8cec6e91a7255",
}
)

// PluginBase is wraps a BasePlugin and implements go-plugins GRPCPlugin
// interface to expose the interface over gRPC.
type PluginBase struct {
plugin.NetRPCUnsupportedPlugin
Impl BasePlugin
}

func (p *PluginBase) GRPCServer(broker *plugin.GRPCBroker, s *grpc.Server) error {
proto.RegisterBasePluginServer(s, &basePluginServer{
impl: p.Impl,
broker: broker,
})
return nil
}

func (p *PluginBase) GRPCClient(ctx context.Context, broker *plugin.GRPCBroker, c *grpc.ClientConn) (any, error) {
return &BasePluginClient{
Client: proto.NewBasePluginClient(c),
DoneCtx: ctx,
}, nil
}

// MsgpackHandle is a shared handle for encoding/decoding of structs
var MsgpackHandle = func() *codec.MsgpackHandle {
h := &codec.MsgpackHandle{}
h.RawToString = true

// maintain binary format from time prior to upgrading latest ugorji
h.BasicHandle.TimeNotBuiltin = true

h.MapType = reflect.TypeOf(map[string]any(nil))

// only review struct codec tags - ignore `json` flags
h.TypeInfos = codec.NewTypeInfos([]string{"codec"})

return h
}()

// MsgPackDecode is used to decode a MsgPack encoded object
func MsgPackDecode(buf []byte, out any) error {
return codec.NewDecoder(bytes.NewReader(buf), MsgpackHandle).Decode(out)
}

// MsgPackEncode is used to encode an object to MsgPack
func MsgPackEncode(b *[]byte, in any) error {
return codec.NewEncoderBytes(b, MsgpackHandle).Encode(in)
}
Loading
Loading