tailcfg: add accumulator API
Signed-off-by: David Crawshaw <crawshaw@tailscale.com>pull/5922/head
parent
3555a49518
commit
31d7ab09a6
|
@ -241,6 +241,15 @@ type Node struct {
|
||||||
|
|
||||||
// DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging.
|
// DataPlaneAuditLogID is the per-node logtail ID used for data plane audit logging.
|
||||||
DataPlaneAuditLogID string `json:",omitempty"`
|
DataPlaneAuditLogID string `json:",omitempty"`
|
||||||
|
|
||||||
|
// Accumulators are scoped accumulator values allocated to this node.
|
||||||
|
Accumulators []Accumulator `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accumulator is a scoped monotonically-increasing accumulator value.
|
||||||
|
type Accumulator struct {
|
||||||
|
Scope string // default scope is "*"
|
||||||
|
Accumulator uint64 // allocated values are greater than zero
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisplayName returns the user-facing name for a node which should
|
// DisplayName returns the user-facing name for a node which should
|
||||||
|
@ -513,6 +522,7 @@ type Hostinfo struct {
|
||||||
Cloud string `json:",omitempty"`
|
Cloud string `json:",omitempty"`
|
||||||
Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode
|
Userspace opt.Bool `json:",omitempty"` // if the client is running in userspace (netstack) mode
|
||||||
UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode
|
UserspaceRouter opt.Bool `json:",omitempty"` // if the client's subnet router is running in userspace (netstack) mode
|
||||||
|
Accumulator opt.Bool `json:",omitempty"` // node participates in tailnet accumulator store
|
||||||
|
|
||||||
// NOTE: any new fields containing pointers in this type
|
// NOTE: any new fields containing pointers in this type
|
||||||
// require changes to Hostinfo.Equal.
|
// require changes to Hostinfo.Equal.
|
||||||
|
@ -1391,6 +1401,10 @@ type MapResponse struct {
|
||||||
// server. An initial nil is equivalent to new(ControlDialPlan).
|
// server. An initial nil is equivalent to new(ControlDialPlan).
|
||||||
// A subsequent streamed nil means no change.
|
// A subsequent streamed nil means no change.
|
||||||
ControlDialPlan *ControlDialPlan `json:",omitempty"`
|
ControlDialPlan *ControlDialPlan `json:",omitempty"`
|
||||||
|
|
||||||
|
// MaxAccumulator is the maximum accumulator allocated in this
|
||||||
|
// tailnet for each scope.
|
||||||
|
MaxAccumulators []Accumulator `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ControlDialPlan is instructions from the control server to the client on how
|
// ControlDialPlan is instructions from the control server to the client on how
|
||||||
|
@ -1557,7 +1571,8 @@ func (n *Node) Equal(n2 *Node) bool {
|
||||||
n.ComputedName == n2.ComputedName &&
|
n.ComputedName == n2.ComputedName &&
|
||||||
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
|
n.computedHostIfDifferent == n2.computedHostIfDifferent &&
|
||||||
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
|
n.ComputedNameWithHost == n2.ComputedNameWithHost &&
|
||||||
eqStrings(n.Tags, n2.Tags)
|
eqStrings(n.Tags, n2.Tags) &&
|
||||||
|
eqAccumulators(n.Accumulators, n2.Accumulators)
|
||||||
}
|
}
|
||||||
|
|
||||||
func eqBoolPtr(a, b *bool) bool {
|
func eqBoolPtr(a, b *bool) bool {
|
||||||
|
@ -1571,6 +1586,18 @@ func eqBoolPtr(a, b *bool) bool {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func eqAccumulators(a, b []Accumulator) bool {
|
||||||
|
if len(a) != len(b) || ((a == nil) != (b == nil)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i, v := range a {
|
||||||
|
if v != b[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func eqStrings(a, b []string) bool {
|
func eqStrings(a, b []string) bool {
|
||||||
if len(a) != len(b) || ((a == nil) != (b == nil)) {
|
if len(a) != len(b) || ((a == nil) != (b == nil)) {
|
||||||
return false
|
return false
|
||||||
|
@ -1846,6 +1873,25 @@ type OverTLSPublicKeyResponse struct {
|
||||||
PublicKey key.MachinePublic `json:"publicKey"`
|
PublicKey key.MachinePublic `json:"publicKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AccumulatorRequest requests the accumulator for a scope be incremented.
|
||||||
|
//
|
||||||
|
// It is JSON-encoded and sent over Noise to "/machine/accumulator".
|
||||||
|
type AccumulatorRequest struct {
|
||||||
|
CapVersion CapabilityVersion // the clients' current CapabilityVersion
|
||||||
|
NodeKey key.NodePublic // the client's current node key
|
||||||
|
Scope string // always "*" for now
|
||||||
|
CurValue uint64
|
||||||
|
// ToValue, if set, means the node does not want to bump the
|
||||||
|
// MaxAccumulator, but has upgraded its own local state to ToValue.
|
||||||
|
ToValue uint64 `json:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// An AccumulatorResponse is the response to an AccumulatorRequest,
|
||||||
|
// it is sent when the value is successfully incremented.
|
||||||
|
type AccumulatorResponse struct {
|
||||||
|
NewValue uint64
|
||||||
|
}
|
||||||
|
|
||||||
// TokenRequest is a request to get an OIDC ID token for an audience.
|
// TokenRequest is a request to get an OIDC ID token for an audience.
|
||||||
// The token can be presented to any resource provider which offers OIDC
|
// The token can be presented to any resource provider which offers OIDC
|
||||||
// Federation.
|
// Federation.
|
||||||
|
|
|
@ -64,6 +64,7 @@ func (src *Node) Clone() *Node {
|
||||||
*dst.Online = *src.Online
|
*dst.Online = *src.Online
|
||||||
}
|
}
|
||||||
dst.Capabilities = append(src.Capabilities[:0:0], src.Capabilities...)
|
dst.Capabilities = append(src.Capabilities[:0:0], src.Capabilities...)
|
||||||
|
dst.Accumulators = append(src.Accumulators[:0:0], src.Accumulators...)
|
||||||
return dst
|
return dst
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -96,6 +97,7 @@ var _NodeCloneNeedsRegeneration = Node(struct {
|
||||||
computedHostIfDifferent string
|
computedHostIfDifferent string
|
||||||
ComputedNameWithHost string
|
ComputedNameWithHost string
|
||||||
DataPlaneAuditLogID string
|
DataPlaneAuditLogID string
|
||||||
|
Accumulators []Accumulator
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// Clone makes a deep copy of Hostinfo.
|
// Clone makes a deep copy of Hostinfo.
|
||||||
|
@ -143,6 +145,7 @@ var _HostinfoCloneNeedsRegeneration = Hostinfo(struct {
|
||||||
Cloud string
|
Cloud string
|
||||||
Userspace opt.Bool
|
Userspace opt.Bool
|
||||||
UserspaceRouter opt.Bool
|
UserspaceRouter opt.Bool
|
||||||
|
Accumulator opt.Bool
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// Clone makes a deep copy of NetInfo.
|
// Clone makes a deep copy of NetInfo.
|
||||||
|
|
|
@ -58,6 +58,7 @@ func TestHostinfoEqual(t *testing.T) {
|
||||||
"Cloud",
|
"Cloud",
|
||||||
"Userspace",
|
"Userspace",
|
||||||
"UserspaceRouter",
|
"UserspaceRouter",
|
||||||
|
"Accumulator",
|
||||||
}
|
}
|
||||||
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
|
if have := fieldsOf(reflect.TypeOf(Hostinfo{})); !reflect.DeepEqual(have, hiHandles) {
|
||||||
t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
t.Errorf("Hostinfo.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||||
|
@ -333,6 +334,7 @@ func TestNodeEqual(t *testing.T) {
|
||||||
"Capabilities",
|
"Capabilities",
|
||||||
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
|
"ComputedName", "computedHostIfDifferent", "ComputedNameWithHost",
|
||||||
"DataPlaneAuditLogID",
|
"DataPlaneAuditLogID",
|
||||||
|
"Accumulators",
|
||||||
}
|
}
|
||||||
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
|
if have := fieldsOf(reflect.TypeOf(Node{})); !reflect.DeepEqual(have, nodeHandles) {
|
||||||
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
t.Errorf("Node.Equal check might be out of sync\nfields: %q\nhandled: %q\n",
|
||||||
|
|
|
@ -168,13 +168,14 @@ func (v NodeView) Online() *bool {
|
||||||
return &x
|
return &x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v NodeView) KeepAlive() bool { return v.ж.KeepAlive }
|
func (v NodeView) KeepAlive() bool { return v.ж.KeepAlive }
|
||||||
func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
|
func (v NodeView) MachineAuthorized() bool { return v.ж.MachineAuthorized }
|
||||||
func (v NodeView) Capabilities() views.Slice[string] { return views.SliceOf(v.ж.Capabilities) }
|
func (v NodeView) Capabilities() views.Slice[string] { return views.SliceOf(v.ж.Capabilities) }
|
||||||
func (v NodeView) ComputedName() string { return v.ж.ComputedName }
|
func (v NodeView) ComputedName() string { return v.ж.ComputedName }
|
||||||
func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
|
func (v NodeView) ComputedNameWithHost() string { return v.ж.ComputedNameWithHost }
|
||||||
func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID }
|
func (v NodeView) DataPlaneAuditLogID() string { return v.ж.DataPlaneAuditLogID }
|
||||||
func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
|
func (v NodeView) Accumulators() views.Slice[Accumulator] { return views.SliceOf(v.ж.Accumulators) }
|
||||||
|
func (v NodeView) Equal(v2 NodeView) bool { return v.ж.Equal(v2.ж) }
|
||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
var _NodeViewNeedsRegeneration = Node(struct {
|
var _NodeViewNeedsRegeneration = Node(struct {
|
||||||
|
@ -205,6 +206,7 @@ var _NodeViewNeedsRegeneration = Node(struct {
|
||||||
computedHostIfDifferent string
|
computedHostIfDifferent string
|
||||||
ComputedNameWithHost string
|
ComputedNameWithHost string
|
||||||
DataPlaneAuditLogID string
|
DataPlaneAuditLogID string
|
||||||
|
Accumulators []Accumulator
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// View returns a readonly view of Hostinfo.
|
// View returns a readonly view of Hostinfo.
|
||||||
|
@ -281,6 +283,7 @@ func (v HostinfoView) SSH_HostKeys() views.Slice[string] { return views.SliceOf(
|
||||||
func (v HostinfoView) Cloud() string { return v.ж.Cloud }
|
func (v HostinfoView) Cloud() string { return v.ж.Cloud }
|
||||||
func (v HostinfoView) Userspace() opt.Bool { return v.ж.Userspace }
|
func (v HostinfoView) Userspace() opt.Bool { return v.ж.Userspace }
|
||||||
func (v HostinfoView) UserspaceRouter() opt.Bool { return v.ж.UserspaceRouter }
|
func (v HostinfoView) UserspaceRouter() opt.Bool { return v.ж.UserspaceRouter }
|
||||||
|
func (v HostinfoView) Accumulator() opt.Bool { return v.ж.Accumulator }
|
||||||
func (v HostinfoView) Equal(v2 HostinfoView) bool { return v.ж.Equal(v2.ж) }
|
func (v HostinfoView) Equal(v2 HostinfoView) bool { return v.ж.Equal(v2.ж) }
|
||||||
|
|
||||||
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
// A compilation failure here means this code must be regenerated, with the command at the top of this file.
|
||||||
|
@ -312,6 +315,7 @@ var _HostinfoViewNeedsRegeneration = Hostinfo(struct {
|
||||||
Cloud string
|
Cloud string
|
||||||
Userspace opt.Bool
|
Userspace opt.Bool
|
||||||
UserspaceRouter opt.Bool
|
UserspaceRouter opt.Bool
|
||||||
|
Accumulator opt.Bool
|
||||||
}{})
|
}{})
|
||||||
|
|
||||||
// View returns a readonly view of NetInfo.
|
// View returns a readonly view of NetInfo.
|
||||||
|
|
|
@ -575,7 +575,7 @@ func TestGetTypeHasher(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "tailcfg.Node",
|
name: "tailcfg.Node",
|
||||||
val: &tailcfg.Node{},
|
val: &tailcfg.Node{},
|
||||||
out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
out: "\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\tn\x88\xf1\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
@ -594,7 +594,7 @@ func TestGetTypeHasher(t *testing.T) {
|
||||||
}
|
}
|
||||||
h.sum()
|
h.sum()
|
||||||
if got := string(hb.B); got != tt.out {
|
if got := string(hb.B); got != tt.out {
|
||||||
t.Fatalf("got %q; want %q", got, tt.out)
|
t.Fatalf("\n got %q;\nwant %q", got, tt.out)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue