istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pilot/pkg/serviceregistry/kube/controller/autoserviceexportcontroller_test.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package controller 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 "testing" 22 "time" 23 24 v1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 mcsapi "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1" 28 29 meshconfig "istio.io/api/mesh/v1alpha1" 30 "istio.io/istio/pilot/pkg/features" 31 "istio.io/istio/pilot/pkg/model" 32 "istio.io/istio/pkg/config/mesh" 33 "istio.io/istio/pkg/kube" 34 "istio.io/istio/pkg/kube/kclient/clienttest" 35 "istio.io/istio/pkg/kube/mcs" 36 "istio.io/istio/pkg/test" 37 "istio.io/istio/pkg/test/util/retry" 38 ) 39 40 var serviceExportTimeout = retry.Timeout(time.Second * 2) 41 42 func TestServiceExportController(t *testing.T) { 43 client := kube.NewFakeClient() 44 45 // Configure the environment with cluster-local hosts. 46 m := meshconfig.MeshConfig{ 47 ServiceSettings: []*meshconfig.MeshConfig_ServiceSettings{ 48 { 49 Settings: &meshconfig.MeshConfig_ServiceSettings_Settings{ 50 ClusterLocal: true, 51 }, 52 Hosts: []string{"*.unexportable-ns.svc.cluster.local", "unexportable-svc.*.svc.cluster.local"}, 53 }, 54 }, 55 } 56 env := model.Environment{Watcher: mesh.NewFixedWatcher(&m)} 57 env.Init() 58 59 sc := newAutoServiceExportController(autoServiceExportOptions{ 60 Client: client, 61 ClusterID: "", 62 DomainSuffix: env.DomainSuffix, 63 ClusterLocal: env.ClusterLocal(), 64 }) 65 66 stop := test.NewStop(t) 67 client.RunAndWait(stop) 68 go sc.Run(stop) 69 70 t.Run("exportable", func(t *testing.T) { 71 createSimpleService(t, client, "exportable-ns", "foo") 72 assertServiceExport(t, client, "exportable-ns", "foo", true) 73 }) 74 75 t.Run("unexportable", func(t *testing.T) { 76 createSimpleService(t, client, "unexportable-ns", "foo") 77 assertServiceExport(t, client, "unexportable-ns", "foo", false) 78 }) 79 80 t.Run("no overwrite", func(t *testing.T) { 81 // manually create serviceexport 82 export := mcsapi.ServiceExport{ 83 TypeMeta: metav1.TypeMeta{ 84 Kind: "ServiceExport", 85 APIVersion: features.MCSAPIVersion, 86 }, 87 ObjectMeta: metav1.ObjectMeta{ 88 Namespace: "exportable-ns", 89 Name: "manual-export", 90 }, 91 Status: mcsapi.ServiceExportStatus{ 92 Conditions: []mcsapi.ServiceExportCondition{ 93 { 94 Type: mcsapi.ServiceExportValid, 95 }, 96 }, 97 }, 98 } 99 100 _, err := client.Dynamic().Resource(mcs.ServiceExportGVR).Namespace("exportable-ns").Create( 101 context.TODO(), toUnstructured(&export), metav1.CreateOptions{}) 102 if err != nil { 103 t.Fatalf("Unexpected error %v", err) 104 } 105 106 // create the associated service 107 // no need for assertions, just trying to ensure no errors 108 createSimpleService(t, client, "exportable-ns", "manual-export") 109 110 // assert that we didn't wipe out the pre-existing serviceexport status 111 assertServiceExportHasCondition(t, client, "exportable-ns", "manual-export", 112 mcsapi.ServiceExportValid) 113 }) 114 } 115 116 func createSimpleService(t *testing.T, client kube.Client, ns string, name string) { 117 t.Helper() 118 clienttest.NewWriter[*v1.Service](t, client).Create(&v1.Service{ 119 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: ns}, 120 }) 121 } 122 123 func assertServiceExport(t *testing.T, client kube.Client, ns, name string, shouldBePresent bool) { 124 t.Helper() 125 retry.UntilSuccessOrFail(t, func() error { 126 got, err := client.Dynamic().Resource(mcs.ServiceExportGVR).Namespace(ns).Get(context.TODO(), name, metav1.GetOptions{}) 127 128 if err != nil && !strings.Contains(err.Error(), "not found") { 129 return fmt.Errorf("unexpected error %v", err) 130 } 131 isPresent := got != nil 132 if isPresent != shouldBePresent { 133 return fmt.Errorf("unexpected serviceexport state. IsPresent: %v, ShouldBePresent: %v, name: %v, namespace: %v", isPresent, shouldBePresent, name, ns) 134 } 135 return nil 136 }, serviceExportTimeout) 137 } 138 139 func assertServiceExportHasCondition(t *testing.T, client kube.Client, ns, name string, condition mcsapi.ServiceExportConditionType) { 140 t.Helper() 141 retry.UntilSuccessOrFail(t, func() error { 142 gotU, err := client.Dynamic().Resource(mcs.ServiceExportGVR).Namespace(ns).Get(context.TODO(), name, metav1.GetOptions{}) 143 if err != nil { 144 return fmt.Errorf("unexpected error %v", err) 145 } 146 147 got := &mcsapi.ServiceExport{} 148 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(gotU.Object, got); err != nil { 149 return err 150 } 151 152 if got.Status.Conditions == nil || len(got.Status.Conditions) == 0 || got.Status.Conditions[0].Type != condition { 153 return fmt.Errorf("condition incorrect or not found") 154 } 155 156 return nil 157 }, serviceExportTimeout) 158 }