110 lines
2.7 KiB
Go
110 lines
2.7 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package client
|
|
|
|
import (
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/armon/go-metrics"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"golang.org/x/exp/maps"
|
|
)
|
|
|
|
type NodeMeta struct {
|
|
c *Client
|
|
}
|
|
|
|
func newNodeMetaEndpoint(c *Client) *NodeMeta {
|
|
n := &NodeMeta{c: c}
|
|
return n
|
|
}
|
|
|
|
func (n *NodeMeta) Apply(args *structs.NodeMetaApplyRequest, reply *structs.NodeMetaResponse) error {
|
|
defer metrics.MeasureSince([]string{"client", "node_meta", "apply"}, time.Now())
|
|
|
|
// Check node write permissions
|
|
if aclObj, err := n.c.ResolveToken(args.AuthToken); err != nil {
|
|
return err
|
|
} else if aclObj != nil && !aclObj.AllowNodeWrite() {
|
|
return structs.ErrPermissionDenied
|
|
}
|
|
|
|
if err := args.Validate(); err != nil {
|
|
return structs.NewErrRPCCoded(http.StatusBadRequest, err.Error())
|
|
}
|
|
|
|
var stateErr error
|
|
var dyn map[string]*string
|
|
|
|
newNode := n.c.UpdateNode(func(node *structs.Node) {
|
|
// First update the Client's state store. This must be done
|
|
// atomically with updating the metadata inmemory to avoid
|
|
// bad interleaving between concurrent updates.
|
|
dyn = maps.Clone(n.c.metaDynamic)
|
|
maps.Copy(dyn, args.Meta)
|
|
|
|
// Delete null values from the dynamic metadata if they are also not
|
|
// static. Static null values must be kept so their removal is
|
|
// persisted in client state.
|
|
for k, v := range args.Meta {
|
|
_, static := n.c.metaStatic[k]
|
|
if v == nil && !static {
|
|
delete(dyn, k)
|
|
}
|
|
}
|
|
|
|
if stateErr = n.c.stateDB.PutNodeMeta(dyn); stateErr != nil {
|
|
return
|
|
}
|
|
|
|
// Apply updated dynamic metadata to client and node now that the part of
|
|
// the operation that can fail succeeded (persistence). Must clone as dyn
|
|
// is read outside of UpdateNode.
|
|
n.c.metaDynamic = maps.Clone(dyn)
|
|
|
|
for k, v := range args.Meta {
|
|
if v == nil {
|
|
delete(node.Meta, k)
|
|
continue
|
|
}
|
|
|
|
node.Meta[k] = *v
|
|
}
|
|
})
|
|
|
|
if stateErr != nil {
|
|
return stateErr
|
|
}
|
|
|
|
// Trigger an async node update
|
|
n.c.updateNode()
|
|
|
|
reply.Meta = newNode.Meta
|
|
reply.Dynamic = dyn
|
|
reply.Static = n.c.metaStatic
|
|
return nil
|
|
}
|
|
|
|
func (n *NodeMeta) Read(args *structs.NodeSpecificRequest, reply *structs.NodeMetaResponse) error {
|
|
defer metrics.MeasureSince([]string{"client", "node_meta", "read"}, time.Now())
|
|
|
|
// Check node read permissions
|
|
if aclObj, err := n.c.ResolveToken(args.AuthToken); err != nil {
|
|
return err
|
|
} else if aclObj != nil && !aclObj.AllowNodeRead() {
|
|
return structs.ErrPermissionDenied
|
|
}
|
|
|
|
// Must acquire configLock to ensure reads aren't interleaved with
|
|
// writes
|
|
n.c.configLock.Lock()
|
|
defer n.c.configLock.Unlock()
|
|
|
|
reply.Meta = n.c.config.Node.Meta
|
|
reply.Dynamic = maps.Clone(n.c.metaDynamic)
|
|
reply.Static = n.c.metaStatic
|
|
return nil
|
|
}
|