Skip to content

Commit

Permalink
Dynamic validation (istio#86)
Browse files Browse the repository at this point in the history
* Dynamic validation

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Fix lint failure

Signed-off-by: Serguei Bezverkhi <[email protected]>

* make validation a separate func to be called by type Validation func

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Add root of Values and root of IstioControlPlaneSpec to pass to Validation

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Refactor to use the recursion

Signed-off-by: Serguei Bezverkhi <[email protected]>

* pushing again

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Address comments

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Addressing comment

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Moving validation out of v1alpha2 package

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Start adding per type validations

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Address lint messages

Signed-off-by: Serguei Bezverkhi <[email protected]>

* rebase

Signed-off-by: Serguei Bezverkhi <[email protected]>

* refactor validate to validate slices

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Fix lint warning

Signed-off-by: Serguei Bezverkhi <[email protected]>

* remove rebase leftover

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Another lint warning

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Add unit tests

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Split map and slice processing

Signed-off-by: Serguei Bezverkhi <[email protected]>

* add License to test file

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Lint error

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Addressing comments

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Addressing comments

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Addressing lint error

Signed-off-by: Serguei Bezverkhi <[email protected]>

* Addressing lint error

Signed-off-by: Serguei Bezverkhi <[email protected]>
  • Loading branch information
sbezverk authored and istio-testing committed Aug 22, 2019
1 parent a119f42 commit f93c2a8
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 1 deletion.
106 changes: 106 additions & 0 deletions pkg/apis/istio/v1alpha2/validation/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2019 Istio 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 validation

import (
"fmt"
"reflect"

"istio.io/operator/pkg/apis/istio/v1alpha2"
"istio.io/operator/pkg/util"
)

const (
validationMethodName = "Validate"
)

// ValidateConfig calls validation func for every defined element in Values
func ValidateConfig(failOnMissingValidation bool, values *v1alpha2.Values, icpls *v1alpha2.IstioControlPlaneSpec) util.Errors {
var validationErrors util.Errors
validationErrors = util.AppendErrs(validationErrors, validateSubTypes(reflect.ValueOf(values).Elem(), failOnMissingValidation, values, icpls))

return validationErrors
}

func validateSubTypes(e reflect.Value, failOnMissingValidation bool, values *v1alpha2.Values, icpls *v1alpha2.IstioControlPlaneSpec) util.Errors {
// Dealing with receiver pointer and receiver value
ptr := e
k := e.Kind()
if k == reflect.Ptr || k == reflect.Interface {
e = e.Elem()
}
// check for method on value
method := e.MethodByName(validationMethodName)
if !method.IsValid() {
method = ptr.MethodByName(validationMethodName)
}

var validationErrors util.Errors
if util.IsNilOrInvalidValue(method) {
if failOnMissingValidation {
validationErrors = append(validationErrors, fmt.Errorf("type %s is missing Validation method", e.Type().String()))
}
} else {
r := method.Call([]reflect.Value{reflect.ValueOf(failOnMissingValidation), reflect.ValueOf(values), reflect.ValueOf(icpls)})[0].Interface().(util.Errors)
if len(r) != 0 {
validationErrors = append(validationErrors, r...)
}
}
// If it is not a struct nothing to do, returning previously collected validation errors
if e.Kind() != reflect.Struct {
return validationErrors
}
for i := 0; i < e.NumField(); i++ {
// Corner case of a slice of something, if something is defined type, then process it recursiveley.
if e.Field(i).Kind() == reflect.Slice {
validationErrors = append(validationErrors, processSlice(e.Field(i), failOnMissingValidation, values, icpls)...)
continue
}
if e.Field(i).Kind() == reflect.Map {
validationErrors = append(validationErrors, processMap(e.Field(i), failOnMissingValidation, values, icpls)...)
continue
}
// Validation is not required if it is not a defined type
if e.Field(i).Kind() != reflect.Interface && e.Field(i).Kind() != reflect.Ptr {
continue
}
val := e.Field(i).Elem()
if util.IsNilOrInvalidValue(val) {
continue
}
validationErrors = append(validationErrors, validateSubTypes(e.Field(i), failOnMissingValidation, values, icpls)...)
}

return validationErrors
}

func processSlice(e reflect.Value, failOnMissingValidation bool, values *v1alpha2.Values, icpls *v1alpha2.IstioControlPlaneSpec) util.Errors {
var validationErrors util.Errors
for i := 0; i < e.Len(); i++ {
validationErrors = append(validationErrors, validateSubTypes(e.Index(i), failOnMissingValidation, values, icpls)...)
}

return validationErrors
}

func processMap(e reflect.Value, failOnMissingValidation bool, values *v1alpha2.Values, icpls *v1alpha2.IstioControlPlaneSpec) util.Errors {
var validationErrors util.Errors
for _, k := range e.MapKeys() {
v := e.MapIndex(k)
validationErrors = append(validationErrors, validateSubTypes(v, failOnMissingValidation, values, icpls)...)
}

return validationErrors
}
81 changes: 81 additions & 0 deletions pkg/apis/istio/v1alpha2/validation/validation_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2019 Istio 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 validation

import (
"reflect"
"testing"

"istio.io/operator/pkg/apis/istio/v1alpha2"
)

func TestValidate(t *testing.T) {
tests := []struct {
name string
toValidate *v1alpha2.Values
validated bool
}{
{
name: "Empty struct",
toValidate: &v1alpha2.Values{},
validated: true,
},
{
name: "With CNI defined",
toValidate: &v1alpha2.Values{
CNI: &v1alpha2.CNIConfig{
Enabled: makeBoolPtr(true),
},
},
validated: true,
},
{
name: "With Slice",
toValidate: &v1alpha2.Values{
Gateways: &v1alpha2.GatewaysConfig{
Enabled: makeBoolPtr(true),
EgressGateway: &v1alpha2.EgressGatewayConfig{
Ports: []*v1alpha2.PortsConfig{
{
Name: makeStringPtr("port1"),
},
{
Name: makeStringPtr("port2"),
},
},
},
},
},
validated: true,
},
}

for _, tt := range tests {
err := validateSubTypes(reflect.ValueOf(tt.toValidate).Elem(), false, tt.toValidate, nil)
if len(err) != 0 && tt.validated {
t.Fatalf("Test %s failed with errors: %+v but supposed to succeed", tt.name, err)
}
if len(err) == 0 && !tt.validated {
t.Fatalf("Test %s failed as it is supposed to fail but succeeded", tt.name)
}
}
}

func makeBoolPtr(v bool) *bool {
return &v
}
func makeStringPtr(v string) *string {
return &v
}
21 changes: 20 additions & 1 deletion pkg/apis/istio/v1alpha2/values_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@ package v1alpha2

import (
"github.com/gogo/protobuf/jsonpb"

corev1 "k8s.io/api/core/v1"
intstr "k8s.io/apimachinery/pkg/util/intstr"

"istio.io/operator/pkg/util"
)

// Values is described in istio.io documentation.
Expand Down Expand Up @@ -644,3 +645,21 @@ func FromInt(val int) IntOrStringForPB {
func FromString(val string) IntOrStringForPB {
return IntOrStringForPB{intstr.FromString(val)}
}

// Validate checks
func (t *PilotConfig) Validate(failOnMissingValidation bool, values *Values, icpls *IstioControlPlaneSpec) util.Errors {
var validationErrors util.Errors
// Exmple
// validationErrors = util.AppendErr(validationErrors, fmt.Errorf("pilotconfig has not been yet implemented"))

return validationErrors
}

// Validate checks CNIConfig confiugration
func (t *CNIConfig) Validate(failOnMissingValidation bool, values *Values, icpls *IstioControlPlaneSpec) util.Errors {
var validationErrors util.Errors
// Example
// validationErrors = util.AppendErr(validationErrors, fmt.Errorf("cniconfig has not been yet implemented"))

return validationErrors
}

0 comments on commit f93c2a8

Please sign in to comment.