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) }) } }