types/netmap, ipn/ipnlocal, control/controlclient: rename NodeMutationAdd to NodeMutationUpsert
NodeMutationAdd was a misleading name: a PeersChanged entry in a MapResponse can represent either a truly new peer or a full replacement for an existing peer that couldn't be expressed as a PeerChangedPatch. Calling it "Add" implied it was always a completely new node, which is wrong. (I'd changed my mind on the design of mapping add/delete events to NodeMutations halfway through #19607 and forgot to update the name, even though I'd updated half the docs) Rename it to NodeMutationUpsert to reflect the actual semantics: the node should be inserted or replaced in the peer map regardless of whether it already existed. Updates #19607 Updates #12542 Change-Id: Iebd3daddb3318cba02e115a1b184fcb3ee8f83d6 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com>
This commit is contained in:
committed by
Brad Fitzpatrick
parent
a8f40a2ca5
commit
2c965ab540
@@ -228,9 +228,9 @@ type NetmapUpdater interface {
|
|||||||
// rather than just full updates.
|
// rather than just full updates.
|
||||||
type NetmapDeltaUpdater interface {
|
type NetmapDeltaUpdater interface {
|
||||||
// UpdateNetmapDelta is called with discrete changes to the network map.
|
// UpdateNetmapDelta is called with discrete changes to the network map.
|
||||||
// The mutation slice may contain [netmap.NodeMutationAdd] and
|
// The mutation slice may contain [netmap.NodeMutationUpsert] entries when
|
||||||
// [netmap.NodeMutationRemove] entries when peers were added or removed,
|
// peers are inserted or replaced, and [netmap.NodeMutationRemove] entries
|
||||||
// alongside per-field patches.
|
// when peers are removed, alongside per-field patches.
|
||||||
//
|
//
|
||||||
// The ok result is whether the implementation was able to apply the
|
// The ok result is whether the implementation was able to apply the
|
||||||
// mutations. It might return false if its internal state doesn't
|
// mutations. It might return false if its internal state doesn't
|
||||||
|
|||||||
+15
-15
@@ -2319,21 +2319,21 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
|||||||
cn := b.currentNode()
|
cn := b.currentNode()
|
||||||
cn.UpdateNetmapDelta(muts)
|
cn.UpdateNetmapDelta(muts)
|
||||||
|
|
||||||
// Dispatch Add/Remove per-peer to magicsock, and any per-field
|
// Dispatch Upsert/Remove per-peer to magicsock, and any per-field
|
||||||
// patches via the existing UpdateNetmapDelta path. The per-peer
|
// patches via the existing UpdateNetmapDelta path. The per-peer
|
||||||
// methods take c.mu themselves, so we can't call them from inside
|
// methods take c.mu themselves, so we can't call them from inside
|
||||||
// magicsock.UpdateNetmapDelta which already holds c.mu.
|
// magicsock.UpdateNetmapDelta which already holds c.mu.
|
||||||
peersAddedOrRemoved := false
|
peersUpsertedOrRemoved := false
|
||||||
ms := b.MagicConn()
|
ms := b.MagicConn()
|
||||||
for _, m := range muts {
|
for _, m := range muts {
|
||||||
switch m := m.(type) {
|
switch m := m.(type) {
|
||||||
case netmap.NodeMutationAdd:
|
case netmap.NodeMutationUpsert:
|
||||||
ms.UpsertPeer(m.Node)
|
ms.UpsertPeer(m.Node)
|
||||||
peersAddedOrRemoved = true
|
peersUpsertedOrRemoved = true
|
||||||
metricNetmapDeltaPeerAdded.Add(1)
|
metricNetmapDeltaPeerUpserted.Add(1)
|
||||||
case netmap.NodeMutationRemove:
|
case netmap.NodeMutationRemove:
|
||||||
ms.RemovePeer(m.NodeIDBeingMutated())
|
ms.RemovePeer(m.NodeIDBeingMutated())
|
||||||
peersAddedOrRemoved = true
|
peersUpsertedOrRemoved = true
|
||||||
metricNetmapDeltaPeerRemoved.Add(1)
|
metricNetmapDeltaPeerRemoved.Add(1)
|
||||||
default:
|
default:
|
||||||
metricNetmapDeltaPeerPatched.Add(1)
|
metricNetmapDeltaPeerPatched.Add(1)
|
||||||
@@ -2370,17 +2370,17 @@ func (b *LocalBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// A single MapResponse can carry adds/removes (full Nodes) AND
|
// A single MapResponse can carry upserts/removes (full Nodes) AND
|
||||||
// per-field patches in the same delta. Build one Notify that
|
// per-field patches in the same delta. Build one Notify that
|
||||||
// reflects all of them; per-session stripping in [sendToLocked]
|
// reflects all of them; per-session stripping in [sendToLocked]
|
||||||
// hides fields the watcher didn't opt in to (and promotes patches
|
// hides fields the watcher didn't opt in to (and promotes patches
|
||||||
// into full Nodes for watchers that asked for PeerChanges but not
|
// into full Nodes for watchers that asked for PeerChanges but not
|
||||||
// PeerPatches).
|
// PeerPatches).
|
||||||
if peersAddedOrRemoved || mutationsAreWorthyOfTellingIPNBus(muts) {
|
if peersUpsertedOrRemoved || mutationsAreWorthyOfTellingIPNBus(muts) {
|
||||||
notify = &ipn.Notify{}
|
notify = &ipn.Notify{}
|
||||||
for _, m := range muts {
|
for _, m := range muts {
|
||||||
switch m := m.(type) {
|
switch m := m.(type) {
|
||||||
case netmap.NodeMutationAdd:
|
case netmap.NodeMutationUpsert:
|
||||||
notify.PeersChanged = append(notify.PeersChanged, m.Node.AsStruct())
|
notify.PeersChanged = append(notify.PeersChanged, m.Node.AsStruct())
|
||||||
case netmap.NodeMutationRemove:
|
case netmap.NodeMutationRemove:
|
||||||
notify.PeersRemoved = append(notify.PeersRemoved, m.NodeIDBeingMutated())
|
notify.PeersRemoved = append(notify.PeersRemoved, m.NodeIDBeingMutated())
|
||||||
@@ -2516,7 +2516,7 @@ func ipnBusPeerChangedPatchFromNodeMutations(muts []netmap.NodeMutation) ([]*tai
|
|||||||
}
|
}
|
||||||
for _, m := range muts {
|
for _, m := range muts {
|
||||||
switch v := m.(type) {
|
switch v := m.(type) {
|
||||||
case netmap.NodeMutationAdd, netmap.NodeMutationRemove:
|
case netmap.NodeMutationUpsert, netmap.NodeMutationRemove:
|
||||||
// These go in PeersChanged / PeersRemoved, not as patches.
|
// These go in PeersChanged / PeersRemoved, not as patches.
|
||||||
continue
|
continue
|
||||||
case netmap.NodeMutationOnline:
|
case netmap.NodeMutationOnline:
|
||||||
@@ -8609,11 +8609,11 @@ var (
|
|||||||
// [mapSession.tryHandleIncrementally]. Useful as test signals that a
|
// [mapSession.tryHandleIncrementally]. Useful as test signals that a
|
||||||
// MapResponse landed on the incremental path with the expected
|
// MapResponse landed on the incremental path with the expected
|
||||||
// payload shape.
|
// payload shape.
|
||||||
metricNetmapDeltaPeerAdded = clientmetric.NewCounter("localbackend_netmap_delta_peer_added")
|
metricNetmapDeltaPeerUpserted = clientmetric.NewCounter("localbackend_netmap_delta_peer_upserted")
|
||||||
metricNetmapDeltaPeerRemoved = clientmetric.NewCounter("localbackend_netmap_delta_peer_removed")
|
metricNetmapDeltaPeerRemoved = clientmetric.NewCounter("localbackend_netmap_delta_peer_removed")
|
||||||
metricNetmapDeltaPeerPatched = clientmetric.NewCounter("localbackend_netmap_delta_peer_patched")
|
metricNetmapDeltaPeerPatched = clientmetric.NewCounter("localbackend_netmap_delta_peer_patched")
|
||||||
metricUpdatePacketFilter = clientmetric.NewCounter("localbackend_update_packet_filter")
|
metricUpdatePacketFilter = clientmetric.NewCounter("localbackend_update_packet_filter")
|
||||||
metricUpdateUserProfiles = clientmetric.NewCounter("localbackend_update_user_profiles")
|
metricUpdateUserProfiles = clientmetric.NewCounter("localbackend_update_user_profiles")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (b *LocalBackend) stateEncrypted() opt.Bool {
|
func (b *LocalBackend) stateEncrypted() opt.Bool {
|
||||||
|
|||||||
@@ -675,7 +675,7 @@ func (nb *nodeBackend) UpdateNetmapDelta(muts []netmap.NodeMutation) (handled bo
|
|||||||
|
|
||||||
for _, m := range muts {
|
for _, m := range muts {
|
||||||
switch m := m.(type) {
|
switch m := m.(type) {
|
||||||
case netmap.NodeMutationAdd:
|
case netmap.NodeMutationUpsert:
|
||||||
nid := m.Node.ID()
|
nid := m.Node.ID()
|
||||||
mak.Set(&nb.peers, nid, m.Node)
|
mak.Set(&nb.peers, nid, m.Node)
|
||||||
for _, ipp := range m.Node.Addresses().All() {
|
for _, ipp := range m.Node.Addresses().All() {
|
||||||
|
|||||||
@@ -108,19 +108,19 @@ func TestNetmapDeltaFastPath(t *testing.T) {
|
|||||||
// parallel here, hence tstest.Shard above).
|
// parallel here, hence tstest.Shard above).
|
||||||
mFast := metricByName(t, "controlclient_map_response_handled_incrementally")
|
mFast := metricByName(t, "controlclient_map_response_handled_incrementally")
|
||||||
mFull := metricByName(t, "controlclient_map_response_handled_full_rebuild")
|
mFull := metricByName(t, "controlclient_map_response_handled_full_rebuild")
|
||||||
mAdd := metricByName(t, "localbackend_netmap_delta_peer_added")
|
mUpsert := metricByName(t, "localbackend_netmap_delta_peer_upserted")
|
||||||
mRem := metricByName(t, "localbackend_netmap_delta_peer_removed")
|
mRem := metricByName(t, "localbackend_netmap_delta_peer_removed")
|
||||||
mPatch := metricByName(t, "localbackend_netmap_delta_peer_patched")
|
mPatch := metricByName(t, "localbackend_netmap_delta_peer_patched")
|
||||||
mFilter := metricByName(t, "localbackend_update_packet_filter")
|
mFilter := metricByName(t, "localbackend_update_packet_filter")
|
||||||
mUsers := metricByName(t, "localbackend_update_user_profiles")
|
mUsers := metricByName(t, "localbackend_update_user_profiles")
|
||||||
baseline := map[*clientmetric.Metric]int64{
|
baseline := map[*clientmetric.Metric]int64{
|
||||||
mFast: mFast.Value(), mFull: mFull.Value(),
|
mFast: mFast.Value(), mFull: mFull.Value(),
|
||||||
mAdd: mAdd.Value(), mRem: mRem.Value(), mPatch: mPatch.Value(),
|
mUpsert: mUpsert.Value(), mRem: mRem.Value(), mPatch: mPatch.Value(),
|
||||||
mFilter: mFilter.Value(), mUsers: mUsers.Value(),
|
mFilter: mFilter.Value(), mUsers: mUsers.Value(),
|
||||||
}
|
}
|
||||||
dumpMetrics := func(t *testing.T) {
|
dumpMetrics := func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
for _, m := range []*clientmetric.Metric{mFast, mFull, mAdd, mRem, mPatch, mFilter, mUsers} {
|
for _, m := range []*clientmetric.Metric{mFast, mFull, mUpsert, mRem, mPatch, mFilter, mUsers} {
|
||||||
t.Logf("metric %s = %d (baseline %d, delta %d)", m.Name(), m.Value(), baseline[m], m.Value()-baseline[m])
|
t.Logf("metric %s = %d (baseline %d, delta %d)", m.Name(), m.Value(), baseline[m], m.Value()-baseline[m])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,7 +188,7 @@ func TestNetmapDeltaFastPath(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
waitDelta(t, mFast, 1)
|
waitDelta(t, mFast, 1)
|
||||||
waitDelta(t, mAdd, 1)
|
waitDelta(t, mUpsert, 1)
|
||||||
waitDelta(t, mFilter, 1)
|
waitDelta(t, mFilter, 1)
|
||||||
waitDelta(t, mUsers, 1)
|
waitDelta(t, mUsers, 1)
|
||||||
waitDelta(t, mFull, 0)
|
waitDelta(t, mFull, 0)
|
||||||
|
|||||||
+16
-12
@@ -68,15 +68,17 @@ func (m NodeMutationLastSeen) Apply(n *tailcfg.Node) {
|
|||||||
n.LastSeen = new(m.LastSeen)
|
n.LastSeen = new(m.LastSeen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeMutationAdd is a NodeMutation that says a new peer has been added.
|
// NodeMutationUpsert is a NodeMutation that says a peer's full Node value
|
||||||
// Apply is a no-op: consumers of NodeMutationAdd must type-switch to handle
|
// should be inserted or replaced.
|
||||||
// adds by inserting Node into their peer map.
|
//
|
||||||
type NodeMutationAdd struct {
|
// Apply is a no-op: consumers of NodeMutationUpsert must type-switch to handle
|
||||||
|
// upserts by storing Node in their peer map.
|
||||||
|
type NodeMutationUpsert struct {
|
||||||
Node tailcfg.NodeView
|
Node tailcfg.NodeView
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m NodeMutationAdd) NodeIDBeingMutated() tailcfg.NodeID { return m.Node.ID() }
|
func (m NodeMutationUpsert) NodeIDBeingMutated() tailcfg.NodeID { return m.Node.ID() }
|
||||||
func (m NodeMutationAdd) Apply(*tailcfg.Node) {}
|
func (m NodeMutationUpsert) Apply(*tailcfg.Node) {}
|
||||||
|
|
||||||
// NodeMutationRemove is a NodeMutation that says a peer has been removed.
|
// NodeMutationRemove is a NodeMutation that says a peer has been removed.
|
||||||
// Apply is a no-op: consumers of NodeMutationRemove must type-switch to handle
|
// Apply is a no-op: consumers of NodeMutationRemove must type-switch to handle
|
||||||
@@ -132,9 +134,11 @@ func NodeMutationsFromPatch(p *tailcfg.PeerChange) (_ []NodeMutation, ok bool) {
|
|||||||
// by res. It returns ok=false if res contains any non-delta field as defined
|
// by res. It returns ok=false if res contains any non-delta field as defined
|
||||||
// by mapResponseContainsNonPatchFields.
|
// by mapResponseContainsNonPatchFields.
|
||||||
//
|
//
|
||||||
// Adds and removes (from res.PeersChanged / res.PeersRemoved) are emitted as
|
// Upserts and removes (from res.PeersChanged / res.PeersRemoved) are emitted
|
||||||
// NodeMutationAdd / NodeMutationRemove entries. Callers must type-switch to
|
// as NodeMutationUpsert / NodeMutationRemove entries. A PeersChanged entry can
|
||||||
// handle those alongside field mutations.
|
// be either a new peer or a full replacement for an existing peer that couldn't
|
||||||
|
// be represented as PeerChangedPatch. Callers must type-switch to handle those
|
||||||
|
// alongside field mutations.
|
||||||
func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []NodeMutation, ok bool) {
|
func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []NodeMutation, ok bool) {
|
||||||
if now.IsZero() {
|
if now.IsZero() {
|
||||||
now = time.Now()
|
now = time.Now()
|
||||||
@@ -149,7 +153,7 @@ func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []No
|
|||||||
for _, n := range res.PeersChanged {
|
for _, n := range res.PeersChanged {
|
||||||
// Any n still in PeersChanged after patchifyPeersChanged is a
|
// Any n still in PeersChanged after patchifyPeersChanged is a
|
||||||
// truly-new (or replaced) peer.
|
// truly-new (or replaced) peer.
|
||||||
ret = append(ret, NodeMutationAdd{Node: n.View()})
|
ret = append(ret, NodeMutationUpsert{Node: n.View()})
|
||||||
}
|
}
|
||||||
for _, p := range res.PeersChangedPatch {
|
for _, p := range res.PeersChangedPatch {
|
||||||
deltas, ok := NodeMutationsFromPatch(p)
|
deltas, ok := NodeMutationsFromPatch(p)
|
||||||
@@ -174,7 +178,7 @@ func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []No
|
|||||||
|
|
||||||
// mapResponseContainsNonPatchFields reports whether res contains any field
|
// mapResponseContainsNonPatchFields reports whether res contains any field
|
||||||
// that can't be expressed as a per-peer NodeMutation (including the new
|
// that can't be expressed as a per-peer NodeMutation (including the new
|
||||||
// NodeMutationAdd / NodeMutationRemove variants) or via the sibling narrow
|
// NodeMutationUpsert / NodeMutationRemove variants) or via the sibling narrow
|
||||||
// setter methods on the map-session backend (e.g. UpdatePacketFilter).
|
// setter methods on the map-session backend (e.g. UpdatePacketFilter).
|
||||||
//
|
//
|
||||||
// When this returns true, the caller must fall back to rebuilding and
|
// When this returns true, the caller must fall back to rebuilding and
|
||||||
@@ -182,7 +186,7 @@ func MutationsFromMapResponse(res *tailcfg.MapResponse, now time.Time) (ret []No
|
|||||||
// handled incrementally.
|
// handled incrementally.
|
||||||
//
|
//
|
||||||
// PeersChanged, PeersRemoved, and PacketFilter(s) are intentionally not in
|
// PeersChanged, PeersRemoved, and PacketFilter(s) are intentionally not in
|
||||||
// this list: new/removed peers ride NodeMutationAdd/Remove, packet
|
// this list: upserted/removed peers ride NodeMutationUpsert/Remove, packet
|
||||||
// filter updates are delivered via the backend's UpdatePacketFilter
|
// filter updates are delivered via the backend's UpdatePacketFilter
|
||||||
// method, and UserProfile updates ride the backend's UpdateUserProfiles
|
// method, and UserProfile updates ride the backend's UpdateUserProfiles
|
||||||
// method.
|
// method.
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func TestMapResponseContainsNonPatchFields(t *testing.T) {
|
|||||||
// The three legacy delta fields handled via NodeMutation patches.
|
// The three legacy delta fields handled via NodeMutation patches.
|
||||||
want = false
|
want = false
|
||||||
case "PeersChanged", "PeersRemoved":
|
case "PeersChanged", "PeersRemoved":
|
||||||
// Now carried as NodeMutationAdd / NodeMutationRemove entries.
|
// Now carried as NodeMutationUpsert / NodeMutationRemove entries.
|
||||||
want = false
|
want = false
|
||||||
case "PacketFilter", "PacketFilters":
|
case "PacketFilter", "PacketFilters":
|
||||||
// Now delivered separately via PacketFilterUpdater.
|
// Now delivered separately via PacketFilterUpdater.
|
||||||
@@ -196,7 +196,7 @@ func TestMutationsFromMapResponse(t *testing.T) {
|
|||||||
mr: &tailcfg.MapResponse{
|
mr: &tailcfg.MapResponse{
|
||||||
PeersChanged: []*tailcfg.Node{{ID: 7}},
|
PeersChanged: []*tailcfg.Node{{ID: 7}},
|
||||||
},
|
},
|
||||||
want: muts(NodeMutationAdd{Node: (&tailcfg.Node{ID: 7}).View()}),
|
want: muts(NodeMutationUpsert{Node: (&tailcfg.Node{ID: 7}).View()}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "add-and-remove-mixed-with-patch",
|
name: "add-and-remove-mixed-with-patch",
|
||||||
@@ -211,7 +211,7 @@ func TestMutationsFromMapResponse(t *testing.T) {
|
|||||||
want: muts(
|
want: muts(
|
||||||
NodeMutationRemove{3},
|
NodeMutationRemove{3},
|
||||||
NodeMutationDERPHome{5, 2},
|
NodeMutationDERPHome{5, 2},
|
||||||
NodeMutationAdd{Node: (&tailcfg.Node{ID: 7}).View()},
|
NodeMutationUpsert{Node: (&tailcfg.Node{ID: 7}).View()},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user