diff --git a/cmd/yurthub/app/options/filters.go b/cmd/yurthub/app/options/filters.go index 298bfde9490..b2a93512040 100644 --- a/cmd/yurthub/app/options/filters.go +++ b/cmd/yurthub/app/options/filters.go @@ -19,6 +19,7 @@ package options import ( "github.com/openyurtio/openyurt/pkg/yurthub/filter/base" "github.com/openyurtio/openyurt/pkg/yurthub/filter/discardcloudservice" + "github.com/openyurtio/openyurt/pkg/yurthub/filter/forwardkubesvctraffic" "github.com/openyurtio/openyurt/pkg/yurthub/filter/inclusterconfig" "github.com/openyurtio/openyurt/pkg/yurthub/filter/masterservice" "github.com/openyurtio/openyurt/pkg/yurthub/filter/nodeportisolation" @@ -27,15 +28,16 @@ import ( var ( // DisabledInCloudMode contains the filters that should be disabled when yurthub is working in cloud mode. - DisabledInCloudMode = []string{discardcloudservice.FilterName} + DisabledInCloudMode = []string{discardcloudservice.FilterName, forwardkubesvctraffic.FilterName} // SupportedComponentsForFilter is used for specifying which components are supported by filters as default setting. SupportedComponentsForFilter = map[string]string{ - masterservice.FilterName: "kubelet", - discardcloudservice.FilterName: "kube-proxy", - servicetopology.FilterName: "kube-proxy, coredns, nginx-ingress-controller", - inclusterconfig.FilterName: "kubelet", - nodeportisolation.FilterName: "kube-proxy", + masterservice.FilterName: "kubelet", + discardcloudservice.FilterName: "kube-proxy", + servicetopology.FilterName: "kube-proxy, coredns, nginx-ingress-controller", + inclusterconfig.FilterName: "kubelet", + nodeportisolation.FilterName: "kube-proxy", + forwardkubesvctraffic.FilterName: "kube-proxy", } ) @@ -49,4 +51,5 @@ func RegisterAllFilters(filters *base.Filters) { discardcloudservice.Register(filters) inclusterconfig.Register(filters) nodeportisolation.Register(filters) + forwardkubesvctraffic.Register(filters) } diff --git a/pkg/yurthub/certificate/server/server.go b/pkg/yurthub/certificate/server/server.go index eae2f136e9a..7f973face9b 100644 --- a/pkg/yurthub/certificate/server/server.go +++ b/pkg/yurthub/certificate/server/server.go @@ -17,6 +17,7 @@ limitations under the License. package server import ( + "context" "crypto/tls" "fmt" "net" @@ -24,6 +25,7 @@ import ( "github.com/pkg/errors" certificatesv1 "k8s.io/api/certificates/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apiserver/pkg/authentication/user" clientset "k8s.io/client-go/kubernetes" @@ -41,6 +43,7 @@ import ( type hubServerCertificateManager struct { hubServerCertManager certificate.Manager hubServerCertStore certificate.FileStore + kubeSvcClusterIP net.IP } func NewHubServerCertificateManager(client clientset.Interface, clientCertManager hubCert.YurtClientCertificateManager, nodeName, pkiDir string, certIPs []net.IP) (hubCert.YurtServerCertificateManager, error) { @@ -49,6 +52,10 @@ func NewHubServerCertificateManager(client clientset.Interface, clientCertManage return nil, errors.Wrap(err, "couldn't new hub server cert store") } + hscm := &hubServerCertificateManager{ + hubServerCertStore: hubServerCertStore, + } + kubeClientFn := func(current *tls.Certificate) (clientset.Interface, error) { // waiting for the certificate is generated _ = wait.PollInfinite(5*time.Second, func() (bool, error) { @@ -67,22 +74,61 @@ func NewHubServerCertificateManager(client clientset.Interface, clientCertManage return kubeconfigutil.ClientSetFromFile(clientCertManager.GetHubConfFile()) } + serverIPsGetter := func() ([]net.IP, error) { + ips := []net.IP{} + if hscm.kubeSvcClusterIP != nil { + ips = append(ips, hscm.kubeSvcClusterIP) + ips = append(ips, certIPs...) + return ips, nil + } + if clientCertManager.GetAPIServerClientCert() == nil { + return ips, fmt.Errorf("client certificate(%s) of hub agent is not ready", clientCertManager.GetHubConfFile()) + } + + if !yurtutil.IsNil(client) { + return certIPs, nil + } + + kubeClient, err := kubeconfigutil.ClientSetFromFile(clientCertManager.GetHubConfFile()) + if err != nil { + return ips, err + } + + kubeSvc, err := kubeClient.CoreV1().Services("default").Get(context.Background(), "kubernetes", metav1.GetOptions{}) + if err != nil { + return ips, err + } else if kubeSvc == nil { + return ips, fmt.Errorf("couldn't get default/kubernetes service") + } else { + ip := net.ParseIP(kubeSvc.Spec.ClusterIP) + if ip == nil { + return ips, fmt.Errorf("couldn't get clusterIP from default/kubernetes service") + } + klog.Infof("completed to prepare default/kubernetes service clusterIP(%s) for server certificate", ip.String()) + hscm.kubeSvcClusterIP = ip + ips = append(ips, ip) + } + + ips = append(ips, certIPs...) + + return ips, nil + } + hubServerCertManager, sErr := certfactory.NewCertManagerFactoryWithFnAndStore(kubeClientFn, hubServerCertStore).New(&certfactory.CertManagerConfig{ ComponentName: fmt.Sprintf("%s-server", projectinfo.GetHubName()), SignerName: certificatesv1.KubeletServingSignerName, ForServerUsage: true, CommonName: fmt.Sprintf("system:node:%s", nodeName), Organizations: []string{user.NodesGroup}, - IPs: certIPs, + IPGetter: serverIPsGetter, + DNSNames: []string{"kubernetes", "kubernetes.default", "kubernetes.default.svc", "kubernetes.default.svc.cluster.local"}, }) if sErr != nil { return nil, sErr } - return &hubServerCertificateManager{ - hubServerCertManager: hubServerCertManager, - hubServerCertStore: hubServerCertStore, - }, nil + hscm.hubServerCertManager = hubServerCertManager + return hscm, nil } func (hcm *hubServerCertificateManager) Start() { diff --git a/pkg/yurthub/filter/forwardkubesvctraffic/filter.go b/pkg/yurthub/filter/forwardkubesvctraffic/filter.go new file mode 100644 index 00000000000..02e5176a5af --- /dev/null +++ b/pkg/yurthub/filter/forwardkubesvctraffic/filter.go @@ -0,0 +1,121 @@ +/* +Copyright 2024 The OpenYurt Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package forwardkubesvctraffic + +import ( + "strconv" + + discovery "k8s.io/api/discovery/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/klog/v2" + utilnet "k8s.io/utils/net" + + "github.com/openyurtio/openyurt/pkg/yurthub/filter" + "github.com/openyurtio/openyurt/pkg/yurthub/filter/base" +) + +const ( + // FilterName filter is used to mutate the default/kubernetes endpointslices + // in order to make pods on edge nodes can access kube-apiserver directly by default/kubernetes service. + FilterName = "forwardkubesvctraffic" + + KubeSVCNamespace = "default" + KubeSVCName = "kubernetes" + KubeSVCPortName = "https" +) + +// Register registers a filter +func Register(filters *base.Filters) { + filters.Register(FilterName, func() (filter.ObjectFilter, error) { + return NewForwardKubeSVCTrafficFilter() + }) +} + +func NewForwardKubeSVCTrafficFilter() (*forwardKubeSVCTrafficFilter, error) { + return &forwardKubeSVCTrafficFilter{addressType: discovery.AddressTypeIPv4}, nil +} + +type forwardKubeSVCTrafficFilter struct { + addressType discovery.AddressType + host string + port int32 +} + +func (fkst *forwardKubeSVCTrafficFilter) Name() string { + return FilterName +} + +func (fkst *forwardKubeSVCTrafficFilter) SupportedResourceAndVerbs() map[string]sets.String { + return map[string]sets.String{ + "endpointslices": sets.NewString("list", "watch"), + } +} + +func (fkst *forwardKubeSVCTrafficFilter) SetMasterServiceHost(host string) error { + fkst.host = host + if utilnet.IsIPv6String(host) { + fkst.addressType = discovery.AddressTypeIPv6 + } + return nil + +} + +func (fkst *forwardKubeSVCTrafficFilter) SetMasterServicePort(portStr string) error { + port, err := strconv.ParseInt(portStr, 10, 32) + if err != nil { + return err + } + fkst.port = int32(port) + return nil +} + +func (fkst *forwardKubeSVCTrafficFilter) Filter(obj runtime.Object, stopCh <-chan struct{}) runtime.Object { + switch v := obj.(type) { + case *discovery.EndpointSlice: + fkst.mutateDefaultKubernetesEps(v) + return v + default: + return obj + } +} + +func (fkst *forwardKubeSVCTrafficFilter) mutateDefaultKubernetesEps(eps *discovery.EndpointSlice) { + trueCondition := true + if eps.Namespace == KubeSVCNamespace && eps.Name == KubeSVCName { + if eps.AddressType != fkst.addressType { + klog.Warningf("address type of default/kubernetes endpoinstlice(%s) and hub server is different(%s), hub server address type need to be configured", eps.AddressType, fkst.addressType) + return + } + for j := range eps.Ports { + if eps.Ports[j].Name != nil && *eps.Ports[j].Name == KubeSVCPortName { + eps.Ports[j].Port = &fkst.port + break + } + } + eps.Endpoints = []discovery.Endpoint{ + { + Addresses: []string{fkst.host}, + Conditions: discovery.EndpointConditions{ + Ready: &trueCondition, + }, + }, + } + klog.V(2).Infof("mutate default/kubernetes endpointslice to %v in forwardkubesvctraffic filter", *eps) + } + return +} diff --git a/pkg/yurthub/filter/forwardkubesvctraffic/filter_test.go b/pkg/yurthub/filter/forwardkubesvctraffic/filter_test.go new file mode 100644 index 00000000000..c658cfac73c --- /dev/null +++ b/pkg/yurthub/filter/forwardkubesvctraffic/filter_test.go @@ -0,0 +1,251 @@ +/* +Copyright 2024 The OpenYurt Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package forwardkubesvctraffic + +import ( + "fmt" + "reflect" + "testing" + + corev1 "k8s.io/api/core/v1" + discovery "k8s.io/api/discovery/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/utils/pointer" + + "github.com/openyurtio/openyurt/pkg/util" + "github.com/openyurtio/openyurt/pkg/yurthub/filter/base" +) + +func TestRegister(t *testing.T) { + filters := base.NewFilters([]string{}) + Register(filters) + if !filters.Enabled(FilterName) { + t.Errorf("couldn't register %s filter", FilterName) + } +} + +func TestName(t *testing.T) { + fkst, _ := NewForwardKubeSVCTrafficFilter() + if fkst.Name() != FilterName { + t.Errorf("expect %s, but got %s", FilterName, fkst.Name()) + } +} + +func TestSupportedResourceAndVerbs(t *testing.T) { + fkst, _ := NewForwardKubeSVCTrafficFilter() + rvs := fkst.SupportedResourceAndVerbs() + if len(rvs) != 1 { + t.Errorf("supported more than one resources, %v", rvs) + } + + for resource, verbs := range rvs { + if resource != "endpointslices" { + t.Errorf("expect resource is endpointslices, but got %s", resource) + } + + if !verbs.Equal(sets.NewString("list", "watch")) { + t.Errorf("expect verbs are list/watch, but got %v", verbs.UnsortedList()) + } + } +} + +func TestFilter(t *testing.T) { + portName := "https" + + readyCondition := pointer.Bool(true) + var kasPort, masterPort int32 + kasPort = 6443 + masterHost := "169.251.2.1" + masterPort = 10268 + + testcases := map[string]struct { + host string + port string + responseObject runtime.Object + expectObject runtime.Object + }{ + "endpointslice is kubernetes endpointslice": { + host: masterHost, + port: fmt.Sprintf("%d", masterPort), + responseObject: &discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeSVCName, + Namespace: KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.14"}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.15"}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + expectObject: &discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: KubeSVCName, + Namespace: KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{masterHost}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &masterPort, + }, + }, + }, + }, + "endpointslice is not kubernetes endpointslice": { + host: masterHost, + port: fmt.Sprintf("%d", masterPort), + responseObject: &discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.14"}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.15"}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + expectObject: &discovery.EndpointSlice{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.14"}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.15"}, + Conditions: discovery.EndpointConditions{ + Ready: readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + }, + "it is not an endpointslice": { + host: masterHost, + port: fmt.Sprintf("%d", masterPort), + responseObject: &corev1.PodList{ + Items: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + expectObject: &corev1.PodList{ + Items: []corev1.Pod{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "default", + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "nginx", + Image: "nginx", + }, + }, + }, + }, + }, + }, + }, + } + + stopCh := make(<-chan struct{}) + for k, tt := range testcases { + t.Run(k, func(t *testing.T) { + fkst, _ := NewForwardKubeSVCTrafficFilter() + fkst.SetMasterServiceHost(tt.host) + fkst.SetMasterServicePort(tt.port) + newObj := fkst.Filter(tt.responseObject, stopCh) + if tt.expectObject == nil { + if !util.IsNil(newObj) { + t.Errorf("Filter expect nil obj, but got %v", newObj) + } + } else if !reflect.DeepEqual(newObj, tt.expectObject) { + t.Errorf("Filter got error, expected: \n%v\nbut got: \n%v\n", tt.expectObject, newObj) + } + }) + } +} diff --git a/pkg/yurthub/filter/responsefilter/filter_test.go b/pkg/yurthub/filter/responsefilter/filter_test.go index d17d56f2ca7..c6595345d68 100644 --- a/pkg/yurthub/filter/responsefilter/filter_test.go +++ b/pkg/yurthub/filter/responsefilter/filter_test.go @@ -50,6 +50,7 @@ import ( "github.com/openyurtio/openyurt/pkg/yurthub/filter" "github.com/openyurtio/openyurt/pkg/yurthub/filter/base" "github.com/openyurtio/openyurt/pkg/yurthub/filter/discardcloudservice" + "github.com/openyurtio/openyurt/pkg/yurthub/filter/forwardkubesvctraffic" "github.com/openyurtio/openyurt/pkg/yurthub/filter/inclusterconfig" "github.com/openyurtio/openyurt/pkg/yurthub/filter/initializer" "github.com/openyurtio/openyurt/pkg/yurthub/filter/masterservice" @@ -423,6 +424,10 @@ func TestResponseFilterForListRequest(t *testing.T) { masterPort := "10268" var masterPortInt int32 masterPortInt = 10268 + readyCondition := true + portName := "https" + var kasPort int32 + kasPort = 443 scheme := runtime.NewScheme() apis.AddToScheme(scheme) nodeBucketGVRToListKind := map[schema.GroupVersionResource]string{ @@ -431,7 +436,6 @@ func TestResponseFilterForListRequest(t *testing.T) { gvrToListKind := map[schema.GroupVersionResource]string{ {Group: "apps.openyurt.io", Version: "v1beta1", Resource: "nodepools"}: "NodePoolList", } - serializerManager := serializer.NewSerializerManager() testcases := map[string]struct { @@ -1956,6 +1960,266 @@ func TestResponseFilterForListRequest(t *testing.T) { }, }, }, + "forwardkubesvctraffic: endpointsliceList contains kubernetes endpointslice": { + masterHost: masterHost, + masterPort: masterPort, + kubeClient: &k8sfake.Clientset{}, + yurtClient: &fake.FakeDynamicClient{}, + group: "discovery.k8s.io", + version: "v1", + resource: "endpointslices", + userAgent: "kube-proxy", + verb: "GET", + path: "/apis/discovery.k8s.io/v1/endpointslices", + accept: "application/json", + inputObj: &discovery.EndpointSliceList{ + Items: []discovery.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: forwardkubesvctraffic.KubeSVCName, + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.14"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.15"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.16"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.17"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + }, + }, + expectedObj: &discovery.EndpointSliceList{ + TypeMeta: metav1.TypeMeta{ + Kind: "EndpointSliceList", + APIVersion: "discovery.k8s.io/v1", + }, + Items: []discovery.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: forwardkubesvctraffic.KubeSVCName, + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{masterHost}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &masterPortInt, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.16"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.17"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + }, + }, + }, + "forwardkubesvctraffic: endpointsliceList doesn't contain kubernetes endpointslice": { + masterHost: masterHost, + masterPort: masterPort, + kubeClient: &k8sfake.Clientset{}, + yurtClient: &fake.FakeDynamicClient{}, + group: "discovery.k8s.io", + version: "v1", + resource: "endpointslices", + userAgent: "kube-proxy", + verb: "GET", + path: "/apis/discovery.k8s.io/v1/endpointslices", + accept: "application/json", + inputObj: &discovery.EndpointSliceList{ + Items: []discovery.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.14"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.15"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.16"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.17"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + }, + }, + expectedObj: &discovery.EndpointSliceList{ + TypeMeta: metav1.TypeMeta{ + Kind: "EndpointSliceList", + APIVersion: "discovery.k8s.io/v1", + }, + Items: []discovery.EndpointSlice{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "bar", + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.14"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.15"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: forwardkubesvctraffic.KubeSVCNamespace, + }, + AddressType: discovery.AddressTypeIPv4, + Endpoints: []discovery.Endpoint{ + { + Addresses: []string{"172.16.0.16"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + { + Addresses: []string{"172.16.0.17"}, + Conditions: discovery.EndpointConditions{ + Ready: &readyCondition, + }, + }, + }, + Ports: []discovery.EndpointPort{ + { + Name: &portName, + Port: &kasPort, + }, + }, + }, + }, + }, + }, } resolver := newTestRequestInfoResolver()