diff --git a/README.md b/README.md index 5b89c664..fa7aee74 100644 --- a/README.md +++ b/README.md @@ -411,6 +411,39 @@ CCM itself does **not** deploy the load-balancer or any part of it, including th modifies an existing `ConfigMap`. This can be deployed by the administrator separately, using the manifest provided in the releases page, or in any other manner. +In order to instruct metallb which IPs to announce and from where, CCM takes direct responsibility for managing the +metallb `ConfigMap`. As described above, this is normally at `metallb-system/config`. + +You **should not** attempt to modify this `ConfigMap` separately, as CCM will modify it with each loop. Modifying it +separately is likely to break metallb's functioning. + +In addition to the usual entries in the `ConfigMap`, CCM adds +[nodeSelector](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) entries +that are specifically structured to be ignored by metallb. + +For example: + +```yaml + node-selectors: + - match-labels: + kubernetes.io/hostname: dc-worker-1 + - match-labels: + nomatch.metal.equinix.com/service-namespace: default + nomatch.metal.equinix.com/service-name: nginx-deployment + - match-labels: + nomatch.metal.equinix.com/service-namespace: ai + nomatch.metal.equinix.com/service-name: trainer +``` + +`node-selectors` are grouped together with a logical OR. The above thus means that it will match +_any_ node that has any of the 3 sets of labels. The node with the hostname `dc-worker-1` will be matched, +independent of the other selectors. + +The remaining selectros are used to allow CCM to track which services are being announced by which node. +These are ignored, as long as no such labels exist on any nodes. This is why the labels are called the clearly +non-matching names of `nomatch.metal.equinix.com/service-namespace` and +`nomatch.metal.equinix.com/service-name` + ##### empty When the `empty` option is enabled, for user-deployed Kubernetes `Service` of `type=LoadBalancer`, diff --git a/metal/loadbalancers.go b/metal/loadbalancers.go index 069b3e1d..a407eaf1 100644 --- a/metal/loadbalancers.go +++ b/metal/loadbalancers.go @@ -209,7 +209,7 @@ func (l *loadBalancers) UpdateLoadBalancer(ctx context.Context, clusterName stri Password: peer.Md5Password, }) } - return l.implementor.UpdateService(ctx, service.Name, n) + return l.implementor.UpdateService(ctx, service.Namespace, service.Name, n) } // EnsureLoadBalancerDeleted deletes the specified load balancer if it @@ -252,7 +252,7 @@ func (l *loadBalancers) EnsureLoadBalancerDeleted(ctx context.Context, clusterNa // remove it from any implementation-specific parts svcIPCidr = fmt.Sprintf("%s/%d", ipReservation.Address, ipReservation.CIDR) klog.V(2).Infof("EnsureLoadBalancerDeleted(): remove: for %s entry %s", svcName, svcIPCidr) - if err := l.implementor.RemoveService(ctx, svcName, svcIPCidr); err != nil { + if err := l.implementor.RemoveService(ctx, service.Namespace, service.Name, svcIPCidr); err != nil { return fmt.Errorf("error removing IP from configmap for %s: %w", svcName, err) } klog.V(2).Infof("EnsureLoadBalancerDeleted(): remove: removed service %s from implementation", svcName) @@ -483,7 +483,7 @@ func (l *loadBalancers) addService(ctx context.Context, svc *v1.Service, ips []p }) } - return svcIPCidr, l.implementor.AddService(ctx, svcName, svcIPCidr, n) + return svcIPCidr, l.implementor.AddService(ctx, svc.Namespace, svc.Name, svcIPCidr, n) } func serviceRep(svc *v1.Service) string { diff --git a/metal/loadbalancers/empty/empty.go b/metal/loadbalancers/empty/empty.go index b7b9316f..d28a50b3 100644 --- a/metal/loadbalancers/empty/empty.go +++ b/metal/loadbalancers/empty/empty.go @@ -15,14 +15,14 @@ func NewLB(k8sclient kubernetes.Interface, config string) *LB { return &LB{} } -func (l *LB) AddService(ctx context.Context, svc, ip string, nodes []loadbalancers.Node) error { +func (l *LB) AddService(ctx context.Context, svcNamespace, svcName, ip string, nodes []loadbalancers.Node) error { return nil } -func (l *LB) RemoveService(ctx context.Context, svc, ip string) error { +func (l *LB) RemoveService(ctx context.Context, svcNamespace, svcName, ip string) error { return nil } -func (l *LB) UpdateService(ctx context.Context, svc string, nodes []loadbalancers.Node) error { +func (l *LB) UpdateService(ctx context.Context, svcNamespace, svcName string, nodes []loadbalancers.Node) error { return nil } diff --git a/metal/loadbalancers/interface.go b/metal/loadbalancers/interface.go index 5be25a35..887f13db 100644 --- a/metal/loadbalancers/interface.go +++ b/metal/loadbalancers/interface.go @@ -6,9 +6,9 @@ import ( type LB interface { // AddService add a service with the provided name and IP - AddService(ctx context.Context, svc, ip string, nodes []Node) error + AddService(ctx context.Context, svcNamespace, svcName, ip string, nodes []Node) error // RemoveService remove service with the given IP - RemoveService(ctx context.Context, svc, ip string) error + RemoveService(ctx context.Context, svcNamespace, svcName, ip string) error // UpdateService ensure that the nodes handled by the service are correct - UpdateService(ctx context.Context, svc string, nodes []Node) error + UpdateService(ctx context.Context, svcNamespace, svcName string, nodes []Node) error } diff --git a/metal/loadbalancers/kubevip/kubevip.go b/metal/loadbalancers/kubevip/kubevip.go index 23db72b3..aa66771e 100644 --- a/metal/loadbalancers/kubevip/kubevip.go +++ b/metal/loadbalancers/kubevip/kubevip.go @@ -15,14 +15,14 @@ func NewLB(k8sclient kubernetes.Interface, config string) *LB { return &LB{} } -func (l *LB) AddService(ctx context.Context, svc, ip string, nodes []loadbalancers.Node) error { +func (l *LB) AddService(ctx context.Context, svcNamespace, svcName, ip string, nodes []loadbalancers.Node) error { return nil } -func (l *LB) RemoveService(ctx context.Context, svc, ip string) error { +func (l *LB) RemoveService(ctx context.Context, svcNamespace, svcName, ip string) error { return nil } -func (l *LB) UpdateService(ctx context.Context, svc string, nodes []loadbalancers.Node) error { +func (l *LB) UpdateService(ctx context.Context, svcNamespace, svcName string, nodes []loadbalancers.Node) error { return nil } diff --git a/metal/loadbalancers/metallb/configmap.go b/metal/loadbalancers/metallb/configmap.go index c44bc767..d8043abd 100644 --- a/metal/loadbalancers/metallb/configmap.go +++ b/metal/loadbalancers/metallb/configmap.go @@ -57,7 +57,7 @@ func (cfg *ConfigFile) AddPeer(add *Peer) bool { // If a matching peer already exists with the service, do not change anything. // If a matching peer already exists but does not have the service, add it. // Returns if anything changed. -func (cfg *ConfigFile) AddPeerByService(add *Peer, svc string) bool { +func (cfg *ConfigFile) AddPeerByService(add *Peer, svcNamespace, svcName string) bool { var found bool // ignore empty peer; nothing to add if add == nil { @@ -70,16 +70,19 @@ func (cfg *ConfigFile) AddPeerByService(add *Peer, svc string) bool { // - ASN matches // - Addr matches // - NodeSelectors all match (but order is ignored) + var peers []Peer for _, peer := range cfg.Peers { if peer.EqualIgnoreService(add) { found = true - peer.AddService(svc) + peer.AddService(svcNamespace, svcName) } + peers = append(peers, peer) } + cfg.Peers = peers if found { return true } - add.AddService(svc) + add.AddService(svcNamespace, svcName) cfg.Peers = append(cfg.Peers, *add) return true } @@ -104,14 +107,14 @@ func (cfg *ConfigFile) RemovePeer(remove *Peer) { // For any peers that have this services in the special MatchLabel, remove // the service from the label. If there are no services left on a peer, remove the // peer entirely. -func (cfg *ConfigFile) RemovePeersByService(svc string) bool { +func (cfg *ConfigFile) RemovePeersByService(svcNamespace, svcName string) bool { var changed bool // go through the peers and see if we have a match peers := make([]Peer, 0) // remove that one, keep all others for _, peer := range cfg.Peers { // get the services for which this peer works - peerChanged, size := peer.RemoveService(svc) + peerChanged, size := peer.RemoveService(svcNamespace, svcName) // if not changed, or it has at least one service left, we can keep this node if !peerChanged || size >= 1 { @@ -332,13 +335,13 @@ func (n NodeSelectors) EqualIgnoreService(o NodeSelectors) bool { // whose sole entry is a MatchLabels for the special service one. var ns1, os1 NodeSelectors for _, ns := range n { - if len(ns.MatchLabels) == 1 && len(ns.MatchExpressions) == 0 && ns.MatchLabels[serviceNameKey] != "" { + if len(ns.MatchLabels) <= 2 && len(ns.MatchExpressions) == 0 && (ns.MatchLabels[serviceNameKey] != "" || ns.MatchLabels[serviceNamespaceKey] != "") { continue } ns1 = append(ns1, ns) } for _, ns := range o { - if len(ns.MatchLabels) == 1 && len(ns.MatchExpressions) == 0 && ns.MatchLabels[serviceNameKey] != "" { + if len(ns.MatchLabels) <= 2 && len(ns.MatchExpressions) == 0 && (ns.MatchLabels[serviceNameKey] != "" || ns.MatchLabels[serviceNamespaceKey] != "") { continue } os1 = append(os1, ns) @@ -484,51 +487,67 @@ func (p *Peer) EqualIgnoreService(o *Peer) bool { } // Services list of services that this peer supports -func (p *Peer) Services() []string { +func (p *Peer) Services() []Resource { + var services []Resource for _, ns := range p.NodeSelectors { + var name, namespace string for k, v := range ns.MatchLabels { - if k == serviceNameKey { - return strings.Split(v, ",") + switch k { + case serviceNameKey: + name = v + case serviceNamespaceKey: + namespace = v } } + if name != "" && namespace != "" { + services = append(services, Resource{Namespace: namespace, Name: name}) + } } - return nil + return services } // AddService ensures that the provided service is in the list of linked services. -func (p *Peer) AddService(svc string) bool { +func (p *Peer) AddService(svcNamespace, svcName string) bool { var ( - found bool - services = map[string]bool{} - serviceList []string + services = []Resource{ + {Namespace: svcNamespace, Name: svcName}, + } + selectors []NodeSelector ) for _, ns := range p.NodeSelectors { + var namespace, name string for k, v := range ns.MatchLabels { - if k != serviceNameKey { - continue + switch k { + case serviceNameKey: + name = v + case serviceNamespaceKey: + namespace = v } - found = true - for _, s := range strings.Split(v, ",") { - // if it already had it, nothing to do, nothing change - if s == svc { - return false - } - services[s] = true - } - services[svc] = true - for k := range services { - serviceList = append(serviceList, k) + } + // if this was not a service namespace/name selector, just add it + if name == "" && namespace == "" { + selectors = append(selectors, ns) + } + if name != "" && namespace != "" { + // if it already had it, nothing to do, nothing change + if svcNamespace == namespace && svcName == name { + return false } - sort.Strings(serviceList) - ns.MatchLabels[serviceNameKey] = strings.Join(serviceList, ",") - break + services = append(services, Resource{Namespace: namespace, Name: name}) } } + // replace the NodeSelectors with everything except for the services + p.NodeSelectors = selectors + + // now add the services + sort.Sort(Resources(services)) + // if we did not find it, add it - if !found { + for _, svc := range services { p.NodeSelectors = append(p.NodeSelectors, NodeSelector{ MatchLabels: map[string]string{ - serviceNameKey: svc, + serviceNamespaceKey: svc.Namespace, + serviceNameKey: svc.Name, }, }) } @@ -537,36 +556,44 @@ func (p *Peer) AddService(svc string) bool { // RemoveService removes a given service from the peer. Returns whether or not it was // changed, and how many services are left for this peer. -func (p *Peer) RemoveService(svc string) (bool, int) { +func (p *Peer) RemoveService(svcNamespace, svcName string) (bool, int) { var ( - found bool - size int + found bool + size int + services = []Resource{} + selectors []NodeSelector ) for _, ns := range p.NodeSelectors { + var name, namespace string for k, v := range ns.MatchLabels { - if k != serviceNameKey { - continue + switch k { + case serviceNameKey: + name = v + case serviceNamespaceKey: + namespace = v } - var ( - services = map[string]bool{} - serviceList []string - ) - for _, s := range strings.Split(v, ",") { - // if it already had it, nothing to do, nothing change - if s != svc { - services[s] = true - } else { - found = true - } - } - for k := range services { - serviceList = append(serviceList, k) - } - sort.Strings(serviceList) - size = len(serviceList) - ns.MatchLabels[serviceNameKey] = strings.Join(serviceList, ",") - break } + switch { + case name == "" && namespace == "": + selectors = append(selectors, ns) + case name == svcName && namespace == svcNamespace: + found = true + case name != "" && namespace != "" && (name != svcName || namespace != svcNamespace): + services = append(services, Resource{Namespace: namespace, Name: name}) + } + } + // first put back all of the previous selectors except for the services + p.NodeSelectors = selectors + // then add all of the services + sort.Sort(Resources(services)) + size = len(services) + for _, svc := range services { + p.NodeSelectors = append(p.NodeSelectors, NodeSelector{ + MatchLabels: map[string]string{ + serviceNamespaceKey: svc.Namespace, + serviceNameKey: svc.Name, + }, + }) } return found, size } diff --git a/metal/loadbalancers/metallb/configmap_test.go b/metal/loadbalancers/metallb/configmap_test.go index 8eb3a26d..86109b4f 100644 --- a/metal/loadbalancers/metallb/configmap_test.go +++ b/metal/loadbalancers/metallb/configmap_test.go @@ -1,7 +1,7 @@ package metallb import ( - "reflect" + "fmt" "strings" "testing" ) @@ -36,48 +36,55 @@ func TestConfigFileAddPeer(t *testing.T) { func TestConfigFileAddPeerByService(t *testing.T) { peers := []Peer{ - genPeer("a"), - genPeer("a"), + genPeer(Resource{"default", "a"}), + genPeer(Resource{"default", "a"}), } cfg := ConfigFile{ Peers: peers, } tests := []struct { - peer Peer - total int - index int - addService string - expectedServices []string - message string + peer Peer + total int + index int + addServiceNamespace string + addServicename string + expectedServices []string + message string }{ - {peers[0], len(peers), 0, "a", []string{"a"}, "add existing peer with existing service"}, - {peers[1], len(peers), 1, "b", []string{"a", "b"}, "add existing peer with new service"}, - {genPeer(), len(peers) + 1, len(peers), "c", []string{"c"}, "add new peer"}, + {peers[0], len(peers), 0, "default", "a", []string{"a"}, "add existing peer with existing service"}, + {peers[1], len(peers), 1, "default", "b", []string{"a", "b"}, "add existing peer with new service"}, + {genPeer(), len(peers) + 1, len(peers), "default", "c", []string{"c"}, "add new peer"}, } for i, tt := range tests { // get a clean set of peers cfg.Peers = peers[:] - cfg.AddPeerByService(&tt.peer, tt.addService) + cfg.AddPeerByService(&tt.peer, tt.addServiceNamespace, tt.addServicename) // make sure the number of peers is as expected if len(cfg.Peers) != tt.total { t.Fatalf("%d: mismatch actual %d vs expected %d: %s", i, len(cfg.Peers), tt.total, tt.message) } // make sure the particular peer has the right services annotated p := cfg.Peers[tt.index] - // get the correct node selector - var found bool + + var ( + svcs []string + found bool + ) for _, ns := range p.NodeSelectors { + var namespace, name string for k, v := range ns.MatchLabels { - if k == serviceNameKey { + switch k { + case serviceNamespaceKey: + namespace = v + case serviceNameKey: + name = v + } + if namespace == tt.addServiceNamespace && name == tt.addServicename { found = true - // look for the desired service - svcs := strings.Split(v, ",") - if !reflect.DeepEqual(svcs, tt.expectedServices) { - t.Fatalf("%d: mismatched services, actual %v, expected %v", i, svcs, tt.expectedServices) - } } + svcs = append(svcs, fmt.Sprintf("%s/%s", namespace, name)) } } if !found { @@ -116,21 +123,22 @@ func TestConfigFileRemovePeer(t *testing.T) { } func TestConfigFileRemovePeersByService(t *testing.T) { - services := [][]string{ - {"a"}, - {"a", "b"}, - {"c"}, + services := [][]Resource{ + {Resource{"default", "a"}}, + {Resource{"default", "a"}, Resource{"default", "b"}}, + {Resource{"default", "c"}}, } tests := []struct { - left [][]string - svc string - message string + left [][]Resource + svcNamespace string + svcName string + message string }{ - {services[0:2], "c", "remove lone service from existing peer"}, - {services, "b", "remove non-lone service from existent peer"}, - {services[1:3], "a", "remove service from multiple peers"}, - {services[:], "d", "remove service from non-existent peer"}, + {services[0:2], "default", "c", "remove lone service from existing peer"}, + {services, "default", "b", "remove non-lone service from existent peer"}, + {services[1:3], "default", "a", "remove service from multiple peers"}, + {services[:], "default", "d", "remove service from non-existent peer"}, } for i, tt := range tests { @@ -142,25 +150,34 @@ func TestConfigFileRemovePeersByService(t *testing.T) { cfg := ConfigFile{ Peers: peers, } - cfg.RemovePeersByService(tt.svc) + cfg.RemovePeersByService(tt.svcNamespace, tt.svcName) if len(cfg.Peers) != len(tt.left) { t.Errorf("%d: mismatch actual %d vs expected %d: %s", i, len(cfg.Peers), len(tt.left), tt.message) } // make sure no peer has the removed service annotated for _, p := range cfg.Peers { + var ( + svcs []string + found bool + ) for _, ns := range p.NodeSelectors { + var namespace, name string for k, v := range ns.MatchLabels { - if k == serviceNameKey { - // look for the desired service - svcs := strings.Split(v, ",") - for _, s := range svcs { - if s == tt.svc { - t.Errorf("%d: still has service '%s' after removal, list: %s", i, tt.svc, svcs) - } - } + switch k { + case serviceNamespaceKey: + namespace = v + case serviceNameKey: + name = v } + if namespace == tt.svcNamespace && name == tt.svcName { + found = true + } + svcs = append(svcs, fmt.Sprintf("%s/%s", namespace, name)) } } + if found { + t.Errorf("%d: still has service '%s/%s' after removal, list: %s", i, tt.svcNamespace, tt.svcName, svcs) + } } } } diff --git a/metal/loadbalancers/metallb/metallb.go b/metal/loadbalancers/metallb/metallb.go index c1bdb6ba..02cbd37e 100644 --- a/metal/loadbalancers/metallb/metallb.go +++ b/metal/loadbalancers/metallb/metallb.go @@ -15,10 +15,11 @@ import ( ) const ( - hostnameKey = "kubernetes.io/hostname" - serviceNameKey = "nomatch.metal.equinix.com/service-name" - defaultNamespace = "metallb-system" - defaultName = "config" + hostnameKey = "kubernetes.io/hostname" + serviceNameKey = "nomatch.metal.equinix.com/service-name" + serviceNamespaceKey = "nomatch.metal.equinix.com/service-namespace" + defaultNamespace = "metallb-system" + defaultName = "config" ) type LB struct { @@ -53,24 +54,24 @@ func NewLB(k8sclient kubernetes.Interface, config string) *LB { } } -func (l *LB) AddService(ctx context.Context, svc, ip string, nodes []loadbalancers.Node) error { +func (l *LB) AddService(ctx context.Context, svcNamespace, svcName, ip string, nodes []loadbalancers.Node) error { config, err := l.getConfigMap(ctx) if err != nil { return fmt.Errorf("unable to retrieve metallb config map %s:%s : %w", l.configMapNamespace, l.configMapName, err) } // Update the service and configmap and save them - err = mapIP(ctx, config, ip, svc, l.configMapName, l.configMapInterface) + err = mapIP(ctx, config, ip, svcNamespace, svcName, l.configMapName, l.configMapInterface) if err != nil { return fmt.Errorf("unable to map IP to service: %w", err) } - if err := l.addNodes(ctx, svc, nodes); err != nil { + if err := l.addNodes(ctx, svcNamespace, svcName, nodes); err != nil { return fmt.Errorf("failed to add nodes: %w", err) } return nil } -func (l *LB) RemoveService(ctx context.Context, svc, ip string) error { +func (l *LB) RemoveService(ctx context.Context, svcNamespace, svcName, ip string) error { config, err := l.getConfigMap(ctx) if err != nil { return fmt.Errorf("unable to retrieve metallb config map %s:%s : %w", l.configMapNamespace, l.configMapName, err) @@ -83,24 +84,24 @@ func (l *LB) RemoveService(ctx context.Context, svc, ip string) error { // remove any node entries for this service // go through the peers and see if we have one with our hostname. - if config.RemovePeersByService(svc) { + if config.RemovePeersByService(svcNamespace, svcName) { return saveUpdatedConfigMap(ctx, l.configMapInterface, l.configMapName, config) } return nil } -func (l *LB) UpdateService(ctx context.Context, svc string, nodes []loadbalancers.Node) error { +func (l *LB) UpdateService(ctx context.Context, svcNamespace, svcName string, nodes []loadbalancers.Node) error { // find the service whose name matches the requested svc // ensure nodes are correct - if err := l.addNodes(ctx, svc, nodes); err != nil { + if err := l.addNodes(ctx, svcNamespace, svcName, nodes); err != nil { return fmt.Errorf("failed to add nodes: %w", err) } return nil } // addNodes add one or more nodes with the provided name, srcIP, and bgp information -func (l *LB) addNodes(ctx context.Context, svc string, nodes []loadbalancers.Node) error { +func (l *LB) addNodes(ctx context.Context, svcNamespace, svcName string, nodes []loadbalancers.Node) error { config, err := l.getConfigMap(ctx) if err != nil { return fmt.Errorf("unable to retrieve metallb config map %s:%s : %w", l.configMapNamespace, l.configMapName, err) @@ -122,7 +123,7 @@ func (l *LB) addNodes(ctx context.Context, svc string, nodes []loadbalancers.Nod SrcAddr: node.SourceIP, NodeSelectors: ns, } - if config.AddPeerByService(&p, svc) { + if config.AddPeerByService(&p, svcNamespace, svcName) { changed = true } } @@ -166,18 +167,18 @@ func (l *LB) getConfigMap(ctx context.Context) (*ConfigFile, error) { } // mapIP add a given ip address to the metallb configmap -func mapIP(ctx context.Context, config *ConfigFile, addr, svcName, configmapname string, cmInterface typedv1.ConfigMapInterface) error { +func mapIP(ctx context.Context, config *ConfigFile, addr, svcNamespace, svcName, configmapname string, cmInterface typedv1.ConfigMapInterface) error { klog.V(2).Infof("mapping IP %s", addr) - return updateMapIP(ctx, config, addr, svcName, configmapname, cmInterface, true) + return updateMapIP(ctx, config, addr, svcNamespace, svcName, configmapname, cmInterface, true) } // unmapIP remove a given IP address from the metalllb config map func unmapIP(ctx context.Context, config *ConfigFile, addr, configmapname string, cmInterface typedv1.ConfigMapInterface) error { klog.V(2).Infof("unmapping IP %s", addr) - return updateMapIP(ctx, config, addr, "", configmapname, cmInterface, false) + return updateMapIP(ctx, config, addr, "", "", configmapname, cmInterface, false) } -func updateMapIP(ctx context.Context, config *ConfigFile, addr, svcName, configmapname string, cmInterface typedv1.ConfigMapInterface, add bool) error { +func updateMapIP(ctx context.Context, config *ConfigFile, addr, svcNamespace, svcName, configmapname string, cmInterface typedv1.ConfigMapInterface, add bool) error { if config == nil { klog.V(2).Info("config unchanged, not updating") return nil diff --git a/metal/loadbalancers/metallb/resource.go b/metal/loadbalancers/metallb/resource.go new file mode 100644 index 00000000..a44250e0 --- /dev/null +++ b/metal/loadbalancers/metallb/resource.go @@ -0,0 +1,28 @@ +package metallb + +type Resource struct { + Namespace string + Name string +} +type Resources []Resource + +// Len is part of sort.Interface. +func (r Resources) Len() int { + return len(r) +} + +// Swap is part of sort.Interface. +func (r Resources) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +// Less is part of sort.Interface +func (r Resources) Less(i, j int) bool { + if r[i].Namespace < r[j].Namespace { + return true + } + if r[i].Name < r[j].Name { + return true + } + return false +} diff --git a/metal/loadbalancers/metallb/util_test.go b/metal/loadbalancers/metallb/util_test.go index a75da693..f7fb4d7f 100644 --- a/metal/loadbalancers/metallb/util_test.go +++ b/metal/loadbalancers/metallb/util_test.go @@ -2,8 +2,6 @@ package metallb import ( "math/rand" - "sort" - "strings" ) func genRandomString(l int) string { @@ -73,7 +71,7 @@ func genSelectorRequirements() SelectorRequirements { } } -func genPeer(svcs ...string) Peer { +func genPeer(svcs ...Resource) Peer { p := Peer{ MyASN: uint32(rand.Intn(75000)), ASN: uint32(rand.Intn(75000)), @@ -87,11 +85,11 @@ func genPeer(svcs ...string) Peer { genNodeSelector(), }, } - if len(svcs) > 0 { - sort.Strings(svcs) + for _, svc := range svcs { p.NodeSelectors = append(p.NodeSelectors, NodeSelector{ MatchLabels: map[string]string{ - serviceNameKey: strings.Join(svcs, ","), + serviceNamespaceKey: svc.Namespace, + serviceNameKey: svc.Name, }, }) }