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  }