istio.io/istio@v0.0.0-20240520182934-d79c90f27776/operator/pkg/apis/istio/v1alpha1/validation/validation_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 validation_test
    16  
    17  import (
    18  	"os"
    19  	"path/filepath"
    20  	"reflect"
    21  	"strings"
    22  	"testing"
    23  
    24  	wrappers "google.golang.org/protobuf/types/known/wrapperspb"
    25  
    26  	v1alpha12 "istio.io/api/operator/v1alpha1"
    27  	"istio.io/istio/operator/pkg/apis/istio/v1alpha1"
    28  	"istio.io/istio/operator/pkg/apis/istio/v1alpha1/validation"
    29  	"istio.io/istio/operator/pkg/helm"
    30  	"istio.io/istio/operator/pkg/manifest"
    31  	"istio.io/istio/operator/pkg/util"
    32  	"istio.io/istio/operator/pkg/util/clog"
    33  	"istio.io/istio/pkg/test/env"
    34  )
    35  
    36  const operatorSubdirFilePath = "manifests"
    37  
    38  // nolint: lll
    39  func TestValidateConfig(t *testing.T) {
    40  	tests := []struct {
    41  		name     string
    42  		value    *v1alpha12.IstioOperatorSpec
    43  		values   string
    44  		errors   string
    45  		warnings string
    46  	}{
    47  		{
    48  			name: "addons",
    49  			value: &v1alpha12.IstioOperatorSpec{
    50  				AddonComponents: map[string]*v1alpha12.ExternalComponentSpec{
    51  					"grafana": {
    52  						Enabled: &wrappers.BoolValue{Value: true},
    53  					},
    54  				},
    55  				Values: util.MustStruct(map[string]any{
    56  					"grafana": map[string]any{
    57  						"enabled": true,
    58  					},
    59  				}),
    60  			},
    61  			errors: `! values.grafana.enabled is deprecated; use the samples/addons/ deployments instead
    62  , ! addonComponents.grafana.enabled is deprecated; use the samples/addons/ deployments instead
    63  `,
    64  		},
    65  		{
    66  			name: "global",
    67  			value: &v1alpha12.IstioOperatorSpec{
    68  				Values: util.MustStruct(map[string]any{
    69  					"global": map[string]any{
    70  						"localityLbSetting": map[string]any{"foo": "bar"},
    71  					},
    72  				}),
    73  			},
    74  			warnings: `! values.global.localityLbSetting is deprecated; use meshConfig.localityLbSetting instead`,
    75  		},
    76  		{
    77  			name: "unset target port",
    78  			values: `
    79  components:
    80    ingressGateways:
    81      - name: istio-ingressgateway
    82        enabled: true
    83      - name: cluster-local-gateway
    84        enabled: true
    85        k8s:
    86          service:
    87            type: ClusterIP
    88            ports:
    89            - port: 15020
    90              name: status-port
    91            - port: 80
    92              name: http2
    93  `,
    94  			errors: `port http2/80 in gateway cluster-local-gateway invalid: targetPort is set to 0, which requires root. Set targetPort to be greater than 1024 or configure values.gateways.istio-ingressgateway.runAsRoot=true`,
    95  		},
    96  		{
    97  			name: "explicitly invalid target port",
    98  			values: `
    99  components:
   100    ingressGateways:
   101      - name: istio-ingressgateway
   102        enabled: true
   103      - name: cluster-local-gateway
   104        enabled: true
   105        k8s:
   106          service:
   107            type: ClusterIP
   108            ports:
   109            - port: 15020
   110              name: status-port
   111            - port: 80
   112              name: http2
   113              targetPort: 90
   114  `,
   115  			errors: `port http2/80 in gateway cluster-local-gateway invalid: targetPort is set to 90, which requires root. Set targetPort to be greater than 1024 or configure values.gateways.istio-ingressgateway.runAsRoot=true`,
   116  		},
   117  		{
   118  			name: "explicitly invalid target port for egress",
   119  			values: `
   120  components:
   121    egressGateways:
   122      - name: egress-gateway
   123        enabled: true
   124        k8s:
   125          service:
   126            type: ClusterIP
   127            ports:
   128            - port: 15020
   129              name: status-port
   130            - port: 80
   131              name: http2
   132              targetPort: 90
   133  `,
   134  			errors: `port http2/80 in gateway egress-gateway invalid: targetPort is set to 90, which requires root. Set targetPort to be greater than 1024 or configure values.gateways.istio-egressgateway.runAsRoot=true`,
   135  		},
   136  		{
   137  			name: "low target port with root",
   138  			values: `
   139  components:
   140    ingressGateways:
   141      - name: istio-ingressgateway
   142        enabled: true
   143      - name: cluster-local-gateway
   144        enabled: true
   145        k8s:
   146          service:
   147            type: ClusterIP
   148            ports:
   149            - port: 15020
   150              name: status-port
   151            - port: 80
   152              name: http2
   153              targetPort: 90
   154  values:
   155    gateways:
   156      istio-ingressgateway:
   157        runAsRoot: true
   158  `,
   159  			errors: ``,
   160  		},
   161  		{
   162  			name: "legacy values ports config empty targetPort",
   163  			values: `
   164  values:
   165    gateways:
   166      istio-ingressgateway:
   167        ingressPorts:
   168        - name: http
   169          port: 80
   170  `,
   171  			errors: `port 80 is invalid: targetPort is set to 0, which requires root. Set targetPort to be greater than 1024 or configure values.gateways.istio-ingressgateway.runAsRoot=true`,
   172  		},
   173  		{
   174  			name: "legacy values ports config explicit targetPort",
   175  			values: `
   176  values:
   177    gateways:
   178      istio-ingressgateway:
   179        ingressPorts:
   180        - name: http
   181          port: 80
   182          targetPort: 90
   183  `,
   184  			errors: `port 80 is invalid: targetPort is set to 90, which requires root. Set targetPort to be greater than 1024 or configure values.gateways.istio-ingressgateway.runAsRoot=true`,
   185  		},
   186  		{
   187  			name: "legacy values ports valid",
   188  			values: `
   189  values:
   190    gateways:
   191      istio-ingressgateway:
   192        ingressPorts:
   193        - name: http
   194          port: 80
   195          targetPort: 8080
   196  `,
   197  			errors: ``,
   198  		},
   199  		{
   200  			name: "replicaCount set when autoscaleEnabled is true",
   201  			values: `
   202  values:
   203    pilot:
   204      autoscaleEnabled: true
   205    gateways:
   206      istio-ingressgateway:
   207        autoscaleEnabled: true
   208      istio-egressgateway:
   209        autoscaleEnabled: true
   210  components:
   211    pilot:
   212      k8s:
   213        replicaCount: 2
   214    ingressGateways:
   215      - name: istio-ingressgateway
   216        enabled: true
   217        k8s:
   218          replicaCount: 2
   219    egressGateways:
   220      - name: istio-egressgateway
   221        enabled: true
   222        k8s:
   223          replicaCount: 2
   224  `,
   225  			warnings: strings.TrimSpace(`
   226  components.pilot.k8s.replicaCount should not be set when values.pilot.autoscaleEnabled is true
   227  components.ingressGateways[name=istio-ingressgateway].k8s.replicaCount should not be set when values.gateways.istio-ingressgateway.autoscaleEnabled is true
   228  components.egressGateways[name=istio-egressgateway].k8s.replicaCount should not be set when values.gateways.istio-egressgateway.autoscaleEnabled is true
   229  `),
   230  		},
   231  		{
   232  			name: "pilot.k8s.replicaCount is default value set when autoscaleEnabled is true",
   233  			values: `
   234  values:
   235    pilot:
   236      autoscaleEnabled: true
   237    gateways:
   238      istio-ingressgateway:
   239        autoscaleEnabled: true
   240      istio-egressgateway:
   241        autoscaleEnabled: true
   242  components:
   243    pilot:
   244      k8s:
   245        replicaCount: 1
   246  `,
   247  			warnings: strings.TrimSpace(``),
   248  		},
   249  	}
   250  	for _, tt := range tests {
   251  		t.Run(tt.name, func(t *testing.T) {
   252  			iop := tt.value
   253  			if tt.values != "" {
   254  				iop = &v1alpha12.IstioOperatorSpec{}
   255  				if err := util.UnmarshalWithJSONPB(tt.values, iop, true); err != nil {
   256  					t.Fatal(err)
   257  				}
   258  			}
   259  			err, warnings := validation.ValidateConfig(false, iop)
   260  			if tt.errors != err.String() {
   261  				t.Fatalf("expected errors: \n%q\n got: \n%q\n", tt.errors, err.String())
   262  			}
   263  			if tt.warnings != warnings {
   264  				t.Fatalf("expected warnings: \n%q\n got \n%q\n", tt.warnings, warnings)
   265  			}
   266  		})
   267  	}
   268  }
   269  
   270  func TestValidateProfiles(t *testing.T) {
   271  	manifests := filepath.Join(env.IstioSrc, operatorSubdirFilePath)
   272  	profiles, err := helm.ListProfiles(manifests)
   273  	if err != nil {
   274  		t.Fatal(err)
   275  	}
   276  	if len(profiles) < 2 {
   277  		// Just ensure we find some profiles, in case this code breaks
   278  		t.Fatalf("Maybe have failed getting profiles, got %v", profiles)
   279  	}
   280  	l := clog.NewConsoleLogger(os.Stdout, os.Stderr, nil)
   281  	for _, tt := range profiles {
   282  		t.Run(tt, func(t *testing.T) {
   283  			_, s, err := manifest.GenIOPFromProfile(tt, "", []string{"installPackagePath=" + manifests}, false, false, nil, l)
   284  			if err != nil {
   285  				t.Fatal(err)
   286  			}
   287  			verr, warnings := validation.ValidateConfig(false, s.Spec)
   288  			if verr != nil {
   289  				t.Fatalf("got error validating: %v", verr)
   290  			}
   291  			if warnings != "" {
   292  				t.Fatalf("got warning validating: %v", warnings)
   293  			}
   294  		})
   295  	}
   296  }
   297  
   298  func TestValidate(t *testing.T) {
   299  	tests := []struct {
   300  		name       string
   301  		toValidate *v1alpha1.Values
   302  		validated  bool
   303  	}{
   304  		{
   305  			name:       "Empty struct",
   306  			toValidate: &v1alpha1.Values{},
   307  			validated:  true,
   308  		},
   309  		{
   310  			name: "With CNI defined",
   311  			toValidate: &v1alpha1.Values{
   312  				Cni: &v1alpha1.CNIConfig{
   313  					Enabled: &wrappers.BoolValue{Value: true},
   314  				},
   315  			},
   316  			validated: true,
   317  		},
   318  	}
   319  
   320  	for _, tt := range tests {
   321  		err := validation.ValidateSubTypes(reflect.ValueOf(tt.toValidate).Elem(), false, tt.toValidate, nil)
   322  		if len(err) != 0 && tt.validated {
   323  			t.Fatalf("Test %s failed with errors: %+v but supposed to succeed", tt.name, err)
   324  		}
   325  		if len(err) == 0 && !tt.validated {
   326  			t.Fatalf("Test %s failed as it is supposed to fail but succeeded", tt.name)
   327  		}
   328  	}
   329  }