all: apply godoc [Name] link conventions across comments
Every Go-identifier reference in // and /* */ comments now uses
godoc's [Name] linking syntax so pkg.go.dev and `go doc` render
them as clickable cross-references. No behaviour change.
Pattern applied across the tree:
In-package [Foo], [Foo.Bar]
Cross-package [pkg.Foo], [pkg.Foo.Bar]
Stdlib [netip.Prefix], [errors.Is], [context.Context]
Tailscale [tailcfg.MapResponse], [tailcfg.Node.CapMap],
[tailcfg.NodeAttrSuggestExitNode]
Skip rules:
- File:line refs left as plain text
- HuJSON wire keys inside backtick raw strings untouched
- ACL/policy syntax tokens (tag:foo, autogroup:self, ...) not Go
symbols, left as plain text
- JSON/OIDC wire keys, gorm tags, RFC IPv6 placeholders, markdown
link tags, decorative dividers — all left as-is
This commit is contained in:
parent
17236fd284
commit
4cca63155d
124 changed files with 1037 additions and 1011 deletions
|
|
@ -150,7 +150,7 @@ func (s *State) DebugOverview() string {
|
|||
return sb.String()
|
||||
}
|
||||
|
||||
// DebugNodeStore returns debug information about the NodeStore.
|
||||
// DebugNodeStore returns debug information about the [NodeStore].
|
||||
func (s *State) DebugNodeStore() string {
|
||||
return s.nodeStore.DebugString()
|
||||
}
|
||||
|
|
@ -257,7 +257,7 @@ func (s *State) DebugFilter() ([]tailcfg.FilterRule, error) {
|
|||
}
|
||||
|
||||
// DebugRoutes returns the current primary routes information as a
|
||||
// structured object built from the NodeStore snapshot.
|
||||
// structured object built from the [NodeStore] snapshot.
|
||||
func (s *State) DebugRoutes() types.DebugRoutes {
|
||||
debug := types.DebugRoutes{
|
||||
AvailableRoutes: make(map[types.NodeID][]netip.Prefix),
|
||||
|
|
@ -421,7 +421,7 @@ func (s *State) DebugDERPJSON() DebugDERPInfo {
|
|||
return info
|
||||
}
|
||||
|
||||
// DebugNodeStoreJSON returns the actual nodes map from the current NodeStore snapshot.
|
||||
// DebugNodeStoreJSON returns the actual nodes map from the current [NodeStore] snapshot.
|
||||
func (s *State) DebugNodeStoreJSON() map[types.NodeID]types.Node {
|
||||
snapshot := s.nodeStore.data.Load()
|
||||
return snapshot.nodesByID
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ type HAHealthProber struct {
|
|||
lastStableSession *xsync.Map[types.NodeID, uint64]
|
||||
}
|
||||
|
||||
// NewHAHealthProber creates a prober that uses the given State for
|
||||
// NewHAHealthProber creates a prober that uses the given [State] for
|
||||
// ping tracking and primary route management.
|
||||
// isConnected should return true if a node has an active map session.
|
||||
func NewHAHealthProber(
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Package state provides pure functions for processing MapRequest data.
|
||||
// These functions are extracted from UpdateNodeFromMapRequest to improve
|
||||
// Package state provides pure functions for processing [tailcfg.MapRequest] data.
|
||||
// These functions are extracted from [State.UpdateNodeFromMapRequest] to improve
|
||||
// testability and maintainability.
|
||||
|
||||
package state
|
||||
|
|
@ -10,19 +10,19 @@ import (
|
|||
"tailscale.com/tailcfg"
|
||||
)
|
||||
|
||||
// netInfoFromMapRequest determines the correct NetInfo to use.
|
||||
// Returns the NetInfo that should be used for this request.
|
||||
// netInfoFromMapRequest determines the correct [tailcfg.NetInfo] to use.
|
||||
// Returns the [tailcfg.NetInfo] that should be used for this request.
|
||||
func netInfoFromMapRequest(
|
||||
nodeID types.NodeID,
|
||||
currentHostinfo *tailcfg.Hostinfo,
|
||||
reqHostinfo *tailcfg.Hostinfo,
|
||||
) *tailcfg.NetInfo {
|
||||
// If request has NetInfo, use it
|
||||
// If request has [tailcfg.NetInfo], use it
|
||||
if reqHostinfo != nil && reqHostinfo.NetInfo != nil {
|
||||
return reqHostinfo.NetInfo
|
||||
}
|
||||
|
||||
// Otherwise, use current NetInfo if available
|
||||
// Otherwise, use current [tailcfg.NetInfo] if available
|
||||
if currentHostinfo != nil && currentHostinfo.NetInfo != nil {
|
||||
log.Debug().
|
||||
Caller().
|
||||
|
|
@ -33,7 +33,7 @@ func netInfoFromMapRequest(
|
|||
return currentHostinfo.NetInfo
|
||||
}
|
||||
|
||||
// No NetInfo available anywhere - log for debugging
|
||||
// No [tailcfg.NetInfo] available anywhere - log for debugging
|
||||
var hostname string
|
||||
if reqHostinfo != nil {
|
||||
hostname = reqHostinfo.Hostname
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func TestNetInfoPreservationInRegistrationFlow(t *testing.T) {
|
|||
nodeID := types.NodeID(1)
|
||||
|
||||
// This test reproduces the bug in registration flows where NetInfo was lost
|
||||
// because we used the wrong hostinfo reference when calling NetInfoFromMapRequest
|
||||
// because we used the wrong hostinfo reference when calling [netInfoFromMapRequest]
|
||||
t.Run("registration_flow_bug_reproduction", func(t *testing.T) {
|
||||
// Simulate existing node with NetInfo (before re-registration)
|
||||
existingNodeHostinfo := &tailcfg.Hostinfo{
|
||||
|
|
|
|||
|
|
@ -21,12 +21,12 @@ import (
|
|||
)
|
||||
|
||||
// fallbackGivenName is the DNS label used when a node is written with
|
||||
// an empty GivenName. Matches Tailscale SaaS behaviour for empty
|
||||
// sanitised labels.
|
||||
// an empty [types.Node.GivenName]. Matches Tailscale SaaS behaviour
|
||||
// for empty sanitised labels.
|
||||
const fallbackGivenName = "node"
|
||||
|
||||
// Errors returned by SetGivenName. ErrNodeNotFound is defined in
|
||||
// state.go and reused here.
|
||||
// Errors returned by [NodeStore.SetGivenName]. [ErrNodeNotFound] is defined
|
||||
// in state.go and reused here.
|
||||
var (
|
||||
ErrGivenNameTaken = errors.New("given name already in use by another node")
|
||||
ErrGivenNameInvalid = errors.New("given name is not a valid DNS label")
|
||||
|
|
@ -132,10 +132,10 @@ func NewNodeStore(allNodes types.Nodes, peersFunc PeersFunc, batchSize int, batc
|
|||
return store
|
||||
}
|
||||
|
||||
// Snapshot is the representation of the current state of the NodeStore.
|
||||
// Snapshot is the representation of the current state of the [NodeStore].
|
||||
// It contains all nodes and their relationships.
|
||||
// It is a copy-on-write structure, meaning that when a write occurs,
|
||||
// a new Snapshot is created with the updated state,
|
||||
// a new [Snapshot] is created with the updated state,
|
||||
// and replaces the old one atomically.
|
||||
type Snapshot struct {
|
||||
// nodesByID is the main source of truth for nodes.
|
||||
|
|
@ -161,7 +161,7 @@ type Snapshot struct {
|
|||
// based on the current policy.
|
||||
type PeersFunc func(nodes []types.NodeView) map[types.NodeID][]types.NodeView
|
||||
|
||||
// work represents a single operation to be performed on the NodeStore.
|
||||
// work represents a single operation to be performed on the [NodeStore].
|
||||
type work struct {
|
||||
op int
|
||||
nodeID types.NodeID
|
||||
|
|
@ -211,18 +211,19 @@ func (s *NodeStore) PutNode(n types.Node) types.NodeView {
|
|||
return resultNode
|
||||
}
|
||||
|
||||
// UpdateNodeFunc is a function type that takes a pointer to a Node and modifies it.
|
||||
// UpdateNodeFunc is a function type that takes a pointer to a [types.Node] and modifies it.
|
||||
type UpdateNodeFunc func(n *types.Node)
|
||||
|
||||
// UpdateNode applies a function to modify a specific node in the
|
||||
// store. Single-node convenience wrapper around [NodeStore.UpdateNodes]
|
||||
// — the writer goroutine signals completion only after the post-batch
|
||||
// snapshot has been stored, so the follow-up GetNode read sees the
|
||||
// applied update. Returns the resulting node and whether it exists.
|
||||
// snapshot has been stored, so the follow-up [NodeStore.GetNode] read
|
||||
// sees the applied update. Returns the resulting node and whether it
|
||||
// exists.
|
||||
//
|
||||
// Callers that need to change several nodes atomically should call
|
||||
// UpdateNodes directly; collecting changes into one batch keeps the
|
||||
// election from running on a half-applied snapshot.
|
||||
// [NodeStore.UpdateNodes] directly; collecting changes into one batch
|
||||
// keeps the election from running on a half-applied snapshot.
|
||||
func (s *NodeStore) UpdateNode(nodeID types.NodeID, updateFn UpdateNodeFunc) (types.NodeView, bool) {
|
||||
timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("update"))
|
||||
defer timer.ObserveDuration()
|
||||
|
|
@ -287,19 +288,20 @@ func (s *NodeStore) DeleteNode(id types.NodeID) {
|
|||
nodeStoreOperations.WithLabelValues("delete").Inc()
|
||||
}
|
||||
|
||||
// SetGivenName sets node.GivenName on the node identified by id,
|
||||
// SetGivenName sets [types.Node.GivenName] on the node identified by id,
|
||||
// rejecting the write if the name is already held by another node.
|
||||
// Intended for the admin rename path, where auto-bumping a
|
||||
// user-supplied name would be surprising.
|
||||
//
|
||||
// Returns:
|
||||
// - the stored NodeView and nil on success
|
||||
// - ErrGivenNameInvalid if name is not a valid DNS label
|
||||
// - ErrGivenNameTaken if another node already holds name
|
||||
// - ErrNodeNotFound if no node with id exists
|
||||
// - the stored [types.NodeView] and nil on success
|
||||
// - [ErrGivenNameInvalid] if name is not a valid DNS label
|
||||
// - [ErrGivenNameTaken] if another node already holds name
|
||||
// - [ErrNodeNotFound] if no node with id exists
|
||||
//
|
||||
// Runs as a single writer-goroutine op, so the uniqueness check and
|
||||
// the write are atomic with respect to concurrent PutNode/UpdateNode.
|
||||
// Runs as a single writer-goroutine op, so the uniqueness check and the
|
||||
// write are atomic with respect to concurrent
|
||||
// [NodeStore.PutNode]/[NodeStore.UpdateNode].
|
||||
func (s *NodeStore) SetGivenName(id types.NodeID, name string) (types.NodeView, error) {
|
||||
timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("set_name"))
|
||||
defer timer.ObserveDuration()
|
||||
|
|
@ -330,13 +332,13 @@ func (s *NodeStore) SetGivenName(id types.NodeID, name string) (types.NodeView,
|
|||
return <-w.nodeResult, nil
|
||||
}
|
||||
|
||||
// Start initializes the NodeStore and starts processing the write queue.
|
||||
// Start initializes the [NodeStore] and starts processing the write queue.
|
||||
func (s *NodeStore) Start() {
|
||||
s.writeQueue = make(chan work)
|
||||
go s.processWrite()
|
||||
}
|
||||
|
||||
// Stop stops the NodeStore.
|
||||
// Stop stops the [NodeStore].
|
||||
func (s *NodeStore) Stop() {
|
||||
close(s.writeQueue)
|
||||
}
|
||||
|
|
@ -536,14 +538,14 @@ func (s *NodeStore) applyBatch(batch []work) {
|
|||
|
||||
// resolveGivenName returns a unique DNS label for the node identified
|
||||
// by self, based on the caller-supplied base label. If base is empty
|
||||
// it falls back to fallbackGivenName ("node"). The label's own holder
|
||||
// it falls back to [fallbackGivenName] ("node"). The label's own holder
|
||||
// (self) is excluded from the collision scan so an idempotent write
|
||||
// keeps the current label.
|
||||
//
|
||||
// On collision the label is bumped as base, base-1, base-2, …, first
|
||||
// unused wins. Must be called from the NodeStore writer goroutine
|
||||
// (inside applyBatch) so the nodes map reflects all earlier ops in
|
||||
// the batch and no other writer can interleave.
|
||||
// unused wins. Must be called from the [NodeStore] writer goroutine
|
||||
// (inside [NodeStore.applyBatch]) so the nodes map reflects all earlier
|
||||
// ops in the batch and no other writer can interleave.
|
||||
func resolveGivenName(nodes map[types.NodeID]types.Node, self types.NodeID, base string) string {
|
||||
if base == "" {
|
||||
base = fallbackGivenName
|
||||
|
|
@ -569,7 +571,7 @@ func resolveGivenName(nodes map[types.NodeID]types.Node, self types.NodeID, base
|
|||
}
|
||||
|
||||
// snapshotFromNodes builds the index maps and primary-route table for
|
||||
// a new Snapshot. prevRoutes carries forward the previous primary
|
||||
// a new [Snapshot]. prevRoutes carries forward the previous primary
|
||||
// assignment so a still-valid choice survives unrelated batches.
|
||||
func snapshotFromNodes(
|
||||
nodes map[types.NodeID]types.Node,
|
||||
|
|
@ -719,8 +721,8 @@ func electPrimaryRoutes(
|
|||
|
||||
// GetNode retrieves a node by its ID.
|
||||
// The bool indicates if the node exists or is available (like "err not found").
|
||||
// The NodeView might be invalid, so it must be checked with .Valid(), which must be used to ensure
|
||||
// it isn't an invalid node (this is more of a node error or node is broken).
|
||||
// The [types.NodeView] might be invalid, so it must be checked with .Valid(), which must
|
||||
// be used to ensure it isn't an invalid node (this is more of a node error or node is broken).
|
||||
func (s *NodeStore) GetNode(id types.NodeID) (types.NodeView, bool) {
|
||||
timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get"))
|
||||
defer timer.ObserveDuration()
|
||||
|
|
@ -735,10 +737,10 @@ func (s *NodeStore) GetNode(id types.NodeID) (types.NodeView, bool) {
|
|||
return n.View(), true
|
||||
}
|
||||
|
||||
// GetNodeByNodeKey retrieves a node by its NodeKey.
|
||||
// GetNodeByNodeKey retrieves a node by its [key.NodePublic].
|
||||
// The bool indicates if the node exists or is available (like "err not found").
|
||||
// The NodeView might be invalid, so it must be checked with .Valid(), which must be used to ensure
|
||||
// it isn't an invalid node (this is more of a node error or node is broken).
|
||||
// The [types.NodeView] might be invalid, so it must be checked with .Valid(), which must
|
||||
// be used to ensure it isn't an invalid node (this is more of a node error or node is broken).
|
||||
func (s *NodeStore) GetNodeByNodeKey(nodeKey key.NodePublic) (types.NodeView, bool) {
|
||||
timer := prometheus.NewTimer(nodeStoreOperationDuration.WithLabelValues("get_by_key"))
|
||||
defer timer.ObserveDuration()
|
||||
|
|
@ -790,7 +792,7 @@ func (s *NodeStore) GetNodeByMachineKeyAnyUser(machineKey key.MachinePublic) (ty
|
|||
return types.NodeView{}, false
|
||||
}
|
||||
|
||||
// DebugString returns debug information about the NodeStore.
|
||||
// DebugString returns debug information about the [NodeStore].
|
||||
func (s *NodeStore) DebugString() string {
|
||||
snapshot := s.data.Load()
|
||||
|
||||
|
|
@ -971,8 +973,8 @@ func (s *NodeStore) PrimaryRoutesString() string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
// RebuildPeerMaps rebuilds the peer relationship map using the current peersFunc.
|
||||
// This must be called after policy changes because peersFunc uses PolicyManager's
|
||||
// RebuildPeerMaps rebuilds the peer relationship map using the current [PeersFunc].
|
||||
// This must be called after policy changes because [PeersFunc] uses [policy.PolicyManager]'s
|
||||
// filters to determine which nodes can see each other. Without rebuilding, the
|
||||
// peer map would use stale filter data until the next node add/delete.
|
||||
func (s *NodeStore) RebuildPeerMaps() {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,10 @@ import (
|
|||
|
||||
const pingIDLength = 16
|
||||
|
||||
// pingTracker correlates outgoing PingRequests with incoming HEAD
|
||||
// pingTracker correlates outgoing [tailcfg.PingRequest]s with incoming HEAD
|
||||
// callbacks. Entries have no server-side TTL: callers are responsible
|
||||
// for cleaning up via CancelPing or by reading from the response channel
|
||||
// within their own timeout.
|
||||
// for cleaning up via [pingTracker.cancel] or by reading from the response
|
||||
// channel within their own timeout.
|
||||
type pingTracker struct {
|
||||
mu sync.Mutex
|
||||
pending map[string]*pendingPing
|
||||
|
|
@ -80,7 +80,7 @@ func (pt *pingTracker) cancel(pingID string) {
|
|||
}
|
||||
|
||||
// drain closes every outstanding response channel and clears the map.
|
||||
// Called from State.Close to unblock any caller still waiting on a
|
||||
// Called from [State.Close] to unblock any caller still waiting on a
|
||||
// channel that will never receive.
|
||||
func (pt *pingTracker) drain() {
|
||||
pt.mu.Lock()
|
||||
|
|
@ -93,7 +93,7 @@ func (pt *pingTracker) drain() {
|
|||
}
|
||||
|
||||
// RegisterPing tracks a pending ping and returns its ID and a channel
|
||||
// for the latency. Callers must defer CancelPing or read the channel
|
||||
// for the latency. Callers must defer [State.CancelPing] or read the channel
|
||||
// within their own timeout; there is no server-side TTL.
|
||||
func (s *State) RegisterPing(nodeID types.NodeID) (string, <-chan time.Duration) {
|
||||
return s.pings.register(nodeID)
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ var (
|
|||
ErrRequestedTagsInvalidOrNotPermitted = errors.New("requested tags")
|
||||
)
|
||||
|
||||
// ErrTaggedNodeHasUser is returned when a tagged node has a UserID set.
|
||||
// ErrTaggedNodeHasUser is returned when a tagged node has a [types.Node.UserID] set.
|
||||
var ErrTaggedNodeHasUser = errors.New("tagged node must not have user_id set")
|
||||
|
||||
// validateNodeOwnership ensures proper node ownership model.
|
||||
// A node must be either user-owned or tagged, and these are mutually exclusive:
|
||||
// tagged nodes must not have a UserID, and user-owned nodes must not have tags.
|
||||
// tagged nodes must not have a [types.Node.UserID], and user-owned nodes must
|
||||
// not have tags.
|
||||
func validateNodeOwnership(node *types.Node) error {
|
||||
if node.IsTagged() {
|
||||
if len(node.Tags) == 0 {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
// Test configuration for NodeStore batching.
|
||||
// Test configuration for [NodeStore] batching.
|
||||
// These values are optimized for test speed rather than production use.
|
||||
const (
|
||||
TestBatchSize = 5
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue