mirror of
https://github.com/moby/moby.git
synced 2026-08-02 22:26:52 +00:00
When a container is stopped, new connections to a published port will not be routed to that container but any existing connections are permitted to continue uninterrupted for the duration of the container's grace period. Unfortunately recent fixes to overlay networks introduced a regression: existing connections routed over the service mesh to containers on remote nodes are dropped immediately when the container is stopped, irrespective of the grace period. Fix the handling of NetworkDB endpoint table events so that the endpoint is disabled in the load balancer when a service endpoint transitions to ServiceDisabled instead of deleting the endpoint and re-adding it. And fix the other bugged state transitions with the help of a unit test which exhaustively covers all permutations of endpoint event. Signed-off-by: Cory Snider <csnider@mirantis.com>
417 lines
12 KiB
Go
417 lines
12 KiB
Go
package libnetwork
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"net/netip"
|
|
"slices"
|
|
"testing"
|
|
|
|
"github.com/gogo/protobuf/proto"
|
|
"gotest.tools/v3/assert"
|
|
|
|
"github.com/moby/moby/v2/daemon/libnetwork/networkdb"
|
|
)
|
|
|
|
func TestEndpointEvent_EquivalentTo(t *testing.T) {
|
|
assert.Check(t, (&endpointEvent{}).EquivalentTo(&endpointEvent{}))
|
|
|
|
a := endpointEvent{
|
|
EndpointRecord: EndpointRecord{
|
|
Name: "foo",
|
|
ServiceName: "bar",
|
|
ServiceID: "baz",
|
|
IngressPorts: []*PortConfig{
|
|
{
|
|
Protocol: ProtocolTCP,
|
|
TargetPort: 80,
|
|
},
|
|
{
|
|
Name: "dns",
|
|
Protocol: ProtocolUDP,
|
|
TargetPort: 5353,
|
|
PublishedPort: 53,
|
|
},
|
|
},
|
|
},
|
|
VirtualIP: netip.MustParseAddr("10.0.0.42"),
|
|
EndpointIP: netip.MustParseAddr("192.168.69.42"),
|
|
}
|
|
assert.Check(t, a.EquivalentTo(&a))
|
|
|
|
reflexiveEquiv := func(a, b *endpointEvent) bool {
|
|
t.Helper()
|
|
assert.Check(t, a.EquivalentTo(b) == b.EquivalentTo(a), "reflexive equivalence")
|
|
return a.EquivalentTo(b)
|
|
}
|
|
|
|
assert.Check(t, reflexiveEquiv(nil, nil), "nil should be equivalent to nil")
|
|
assert.Check(t, !reflexiveEquiv(&a, nil), "non-nil should not be equivalent to nil")
|
|
|
|
b := a
|
|
b.ServiceDisabled = true
|
|
assert.Check(t, reflexiveEquiv(&a, &b), "ServiceDisabled value should not matter")
|
|
|
|
c := a
|
|
c.IngressPorts = slices.Clone(a.IngressPorts)
|
|
slices.Reverse(c.IngressPorts)
|
|
assert.Check(t, reflexiveEquiv(&a, &c), "IngressPorts order should not matter")
|
|
|
|
d := a
|
|
d.IngressPorts = append(d.IngressPorts, a.IngressPorts[0])
|
|
assert.Check(t, !reflexiveEquiv(&a, &d), "Differing number of copies of IngressPort entries should not be equivalent")
|
|
d.IngressPorts = a.IngressPorts[:1]
|
|
assert.Check(t, !reflexiveEquiv(&a, &d), "Removing an IngressPort entry should not be equivalent")
|
|
|
|
e := a
|
|
e.Aliases = []string{"alias1", "alias2"}
|
|
assert.Check(t, !reflexiveEquiv(&a, &e), "Differing Aliases should not be equivalent")
|
|
|
|
f := a
|
|
f.TaskAliases = []string{"taskalias1", "taskalias2"}
|
|
assert.Check(t, !reflexiveEquiv(&a, &f), "Adding TaskAliases should not be equivalent")
|
|
g := a
|
|
g.TaskAliases = []string{"taskalias2", "taskalias1"}
|
|
assert.Check(t, reflexiveEquiv(&f, &g), "TaskAliases order should not matter")
|
|
g.TaskAliases = g.TaskAliases[:1]
|
|
assert.Check(t, !reflexiveEquiv(&f, &g), "Differing number of TaskAliases should not be equivalent")
|
|
|
|
h := a
|
|
h.EndpointIP = netip.MustParseAddr("192.168.69.43")
|
|
assert.Check(t, !reflexiveEquiv(&a, &h), "Differing EndpointIP should not be equivalent")
|
|
|
|
i := a
|
|
i.VirtualIP = netip.MustParseAddr("10.0.0.69")
|
|
assert.Check(t, !reflexiveEquiv(&a, &i), "Differing VirtualIP should not be equivalent")
|
|
|
|
j := a
|
|
j.ServiceID = "qux"
|
|
assert.Check(t, !reflexiveEquiv(&a, &j), "Differing ServiceID should not be equivalent")
|
|
|
|
k := a
|
|
k.ServiceName = "quux"
|
|
assert.Check(t, !reflexiveEquiv(&a, &k), "Differing ServiceName should not be equivalent")
|
|
|
|
l := a
|
|
l.Name = "aaaaa"
|
|
assert.Check(t, !reflexiveEquiv(&a, &l), "Differing Name should not be equivalent")
|
|
}
|
|
|
|
type mockServiceBinder struct {
|
|
actions []string
|
|
}
|
|
|
|
func (m *mockServiceBinder) addContainerNameResolution(nID, eID, containerName string, _ []string, ip net.IP, _ string) error {
|
|
m.actions = append(m.actions, fmt.Sprintf("addContainerNameResolution(%v, %v, %v, %v)", nID, eID, containerName, ip))
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceBinder) delContainerNameResolution(nID, eID, containerName string, _ []string, ip net.IP, _ string) error {
|
|
m.actions = append(m.actions, fmt.Sprintf("delContainerNameResolution(%v, %v, %v, %v)", nID, eID, containerName, ip))
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceBinder) addServiceBinding(svcName, svcID, nID, eID, containerName string, vip net.IP, _ []*PortConfig, _, _ []string, ip net.IP, _ string) error {
|
|
m.actions = append(m.actions, fmt.Sprintf("addServiceBinding(%v, %v, %v, %v, %v, %v, %v)", svcName, svcID, nID, eID, containerName, vip, ip))
|
|
return nil
|
|
}
|
|
|
|
func (m *mockServiceBinder) rmServiceBinding(svcName, svcID, nID, eID, containerName string, vip net.IP, _ []*PortConfig, _, _ []string, ip net.IP, _ string, deleteSvcRecords bool, fullRemove bool) error {
|
|
m.actions = append(m.actions, fmt.Sprintf("rmServiceBinding(%v, %v, %v, %v, %v, %v, %v, deleteSvcRecords=%v, fullRemove=%v)", svcName, svcID, nID, eID, containerName, vip, ip, deleteSvcRecords, fullRemove))
|
|
return nil
|
|
}
|
|
|
|
func TestHandleEPTableEvent(t *testing.T) {
|
|
svc1 := EndpointRecord{
|
|
Name: "ep1",
|
|
ServiceName: "svc1",
|
|
ServiceID: "id1",
|
|
VirtualIP: "10.0.0.1",
|
|
EndpointIP: "192.168.12.42",
|
|
}
|
|
svc1disabled := svc1
|
|
svc1disabled.ServiceDisabled = true
|
|
|
|
svc2 := EndpointRecord{
|
|
Name: "ep2",
|
|
ServiceName: "svc2",
|
|
ServiceID: "id2",
|
|
VirtualIP: "10.0.0.2",
|
|
EndpointIP: "172.16.69.5",
|
|
}
|
|
svc2disabled := svc2
|
|
svc2disabled.ServiceDisabled = true
|
|
|
|
ctr1 := EndpointRecord{
|
|
Name: "ctr1",
|
|
EndpointIP: "172.18.1.1",
|
|
}
|
|
ctr1disabled := ctr1
|
|
ctr1disabled.ServiceDisabled = true
|
|
|
|
ctr2 := EndpointRecord{
|
|
Name: "ctr2",
|
|
EndpointIP: "172.18.1.2",
|
|
}
|
|
ctr2disabled := ctr2
|
|
ctr2disabled.ServiceDisabled = true
|
|
|
|
tests := []struct {
|
|
name string
|
|
prev, ev *EndpointRecord
|
|
expectedActions []string
|
|
}{
|
|
{
|
|
name: "Insert/Service/ServiceDisabled=false",
|
|
ev: &svc1,
|
|
expectedActions: []string{
|
|
"addServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42)",
|
|
},
|
|
},
|
|
{
|
|
name: "Insert/Service/ServiceDisabled=true",
|
|
ev: &svc1disabled,
|
|
},
|
|
{
|
|
name: "Insert/Container/ServiceDisabled=false",
|
|
ev: &ctr1,
|
|
expectedActions: []string{
|
|
"addContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
},
|
|
},
|
|
{
|
|
name: "Insert/Container/ServiceDisabled=true",
|
|
ev: &ctr1disabled,
|
|
},
|
|
|
|
{
|
|
name: "Update/Service/ServiceDisabled=ft",
|
|
prev: &svc1,
|
|
ev: &svc1disabled,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=false)",
|
|
},
|
|
},
|
|
{
|
|
name: "Update/Service/ServiceDisabled=tf",
|
|
prev: &svc1disabled,
|
|
ev: &svc1,
|
|
expectedActions: []string{
|
|
"addServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42)",
|
|
},
|
|
},
|
|
{
|
|
name: "Update/Service/ServiceDisabled=ff",
|
|
prev: &svc1disabled,
|
|
ev: &svc1disabled,
|
|
},
|
|
{
|
|
name: "Update/Service/ServiceDisabled=tt",
|
|
prev: &svc1,
|
|
ev: &svc1,
|
|
},
|
|
{
|
|
name: "Update/Container/ServiceDisabled=ft",
|
|
prev: &ctr1,
|
|
ev: &ctr1disabled,
|
|
expectedActions: []string{
|
|
"delContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
},
|
|
},
|
|
{
|
|
name: "Update/Container/ServiceDisabled=tf",
|
|
prev: &ctr1disabled,
|
|
ev: &ctr1,
|
|
expectedActions: []string{
|
|
"addContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
},
|
|
},
|
|
{
|
|
name: "Update/Container/ServiceDisabled=ff",
|
|
prev: &ctr1disabled,
|
|
ev: &ctr1disabled,
|
|
},
|
|
{
|
|
name: "Update/Container/ServiceDisabled=tt",
|
|
prev: &ctr1,
|
|
ev: &ctr1,
|
|
},
|
|
|
|
{
|
|
name: "Delete/Service/ServiceDisabled=false",
|
|
prev: &svc1,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
},
|
|
},
|
|
{
|
|
name: "Delete/Service/ServiceDisabled=true",
|
|
prev: &svc1disabled,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
},
|
|
},
|
|
|
|
{
|
|
name: "Delete/Container/ServiceDisabled=false",
|
|
prev: &ctr1,
|
|
expectedActions: []string{
|
|
"delContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
},
|
|
},
|
|
{
|
|
name: "Delete/Container/ServiceDisabled=true",
|
|
prev: &ctr1disabled,
|
|
},
|
|
|
|
{
|
|
name: "Replace/From=Service/To=Service/ServiceDisabled=ff",
|
|
prev: &svc1,
|
|
ev: &svc2,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
"addServiceBinding(svc2, id2, network1, endpoint1, ep2, 10.0.0.2, 172.16.69.5)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Service/ServiceDisabled=ft",
|
|
prev: &svc1,
|
|
ev: &svc2disabled,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Service/ServiceDisabled=tf",
|
|
prev: &svc1disabled,
|
|
ev: &svc2,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
"addServiceBinding(svc2, id2, network1, endpoint1, ep2, 10.0.0.2, 172.16.69.5)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Service/ServiceDisabled=tt",
|
|
prev: &svc1disabled,
|
|
ev: &svc2disabled,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Container/ServiceDisabled=ff",
|
|
prev: &svc1,
|
|
ev: &ctr2,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
"addContainerNameResolution(network1, endpoint1, ctr2, 172.18.1.2)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Container/ServiceDisabled=ft",
|
|
prev: &svc1,
|
|
ev: &ctr2disabled,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Container/ServiceDisabled=tf",
|
|
prev: &svc1disabled,
|
|
ev: &ctr2,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
"addContainerNameResolution(network1, endpoint1, ctr2, 172.18.1.2)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Service/To=Container/ServiceDisabled=tt",
|
|
prev: &svc1disabled,
|
|
ev: &ctr2disabled,
|
|
expectedActions: []string{
|
|
"rmServiceBinding(svc1, id1, network1, endpoint1, ep1, 10.0.0.1, 192.168.12.42, deleteSvcRecords=true, fullRemove=true)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Service/ServiceDisabled=ff",
|
|
prev: &ctr1,
|
|
ev: &svc2,
|
|
expectedActions: []string{
|
|
"delContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
"addServiceBinding(svc2, id2, network1, endpoint1, ep2, 10.0.0.2, 172.16.69.5)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Service/ServiceDisabled=ft",
|
|
prev: &ctr1,
|
|
ev: &svc2disabled,
|
|
expectedActions: []string{
|
|
"delContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Service/ServiceDisabled=tf",
|
|
prev: &ctr1disabled,
|
|
ev: &svc2,
|
|
expectedActions: []string{
|
|
"addServiceBinding(svc2, id2, network1, endpoint1, ep2, 10.0.0.2, 172.16.69.5)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Service/ServiceDisabled=tt",
|
|
prev: &ctr1disabled,
|
|
ev: &svc2disabled,
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Container/ServiceDisabled=ff",
|
|
prev: &ctr1,
|
|
ev: &ctr2,
|
|
expectedActions: []string{
|
|
"delContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
"addContainerNameResolution(network1, endpoint1, ctr2, 172.18.1.2)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Container/ServiceDisabled=ft",
|
|
prev: &ctr1,
|
|
ev: &ctr2disabled,
|
|
expectedActions: []string{
|
|
"delContainerNameResolution(network1, endpoint1, ctr1, 172.18.1.1)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Container/ServiceDisabled=tf",
|
|
prev: &ctr1disabled,
|
|
ev: &ctr2,
|
|
expectedActions: []string{
|
|
"addContainerNameResolution(network1, endpoint1, ctr2, 172.18.1.2)",
|
|
},
|
|
},
|
|
{
|
|
name: "Replace/From=Container/To=Container/ServiceDisabled=tt",
|
|
prev: &ctr1disabled,
|
|
ev: &ctr2disabled,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
msb := &mockServiceBinder{}
|
|
event := networkdb.WatchEvent{
|
|
NetworkID: "network1",
|
|
Key: "endpoint1",
|
|
}
|
|
var err error
|
|
if tt.prev != nil {
|
|
event.Prev, err = proto.Marshal(tt.prev)
|
|
assert.NilError(t, err)
|
|
}
|
|
if tt.ev != nil {
|
|
event.Value, err = proto.Marshal(tt.ev)
|
|
assert.NilError(t, err)
|
|
}
|
|
handleEpTableEvent(msb, event)
|
|
assert.DeepEqual(t, tt.expectedActions, msb.actions)
|
|
})
|
|
}
|
|
}
|