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