1
// Copyright Istio Authors
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
7
// http://www.apache.org/licenses/LICENSE-2.0
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.
25
fuzz "github.com/AdaLogics/go-fuzz-headers"
26
"github.com/davecgh/go-spew/spew"
27
legacyproto "github.com/golang/protobuf/proto" // nolint: staticcheck
28
"github.com/google/go-cmp/cmp"
29
"github.com/google/go-cmp/cmp/cmpopts"
30
"google.golang.org/protobuf/testing/protocmp"
31
apimeta "k8s.io/apimachinery/pkg/api/meta"
32
"k8s.io/apimachinery/pkg/runtime"
33
"k8s.io/apimachinery/pkg/runtime/schema"
34
"k8s.io/apimachinery/pkg/runtime/serializer/json"
36
clientextensions "istio.io/client-go/pkg/apis/extensions/v1alpha1"
37
clientnetworkingalpha "istio.io/client-go/pkg/apis/networking/v1alpha3"
38
clientnetworkingbeta "istio.io/client-go/pkg/apis/networking/v1beta1"
39
clientsecurity "istio.io/client-go/pkg/apis/security/v1beta1"
40
clienttelemetry "istio.io/client-go/pkg/apis/telemetry/v1alpha1"
41
"istio.io/istio/pkg/config/schema/collections"
45
scheme = runtime.NewScheme()
50
clientnetworkingalpha.AddToScheme(scheme)
51
clientnetworkingbeta.AddToScheme(scheme)
52
clientsecurity.AddToScheme(scheme)
53
clientextensions.AddToScheme(scheme)
54
clienttelemetry.AddToScheme(scheme)
57
// FuzzRoundtrip tests whether the pilot CRDs
58
// can be encoded and decoded.
59
func FuzzCRDRoundtrip(data []byte) int {
60
initter.Do(initRoundTrip)
66
r := collections.Pilot.All()[int(data[0])%len(collections.Pilot.All())]
67
gvk := r.GroupVersionKind()
68
kgvk := schema.GroupVersionKind{
73
object, err := scheme.New(kgvk)
78
typeAcc, err := apimeta.TypeAccessor(object)
80
panic(fmt.Sprintf("%q is not a TypeMeta and cannot be tested - add it to nonRoundTrippableInternalTypes: %v\n", kgvk, err))
82
f := fuzz.NewConsumer(data[1:])
83
err = f.GenerateStruct(object)
87
err = checkForNilValues(object)
91
typeAcc.SetKind(kgvk.Kind)
92
typeAcc.SetAPIVersion(kgvk.GroupVersion().String())
94
roundTrip(json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false), object)
98
// roundTrip performs the roundtrip of the object.
99
func roundTrip(codec runtime.Codec, object runtime.Object) {
100
printer := spew.ConfigState{DisableMethods: true}
102
// deep copy the original object
103
object = object.DeepCopyObject()
104
name := reflect.TypeOf(object).Elem().Name()
106
// encode (serialize) the deep copy using the provided codec
107
data, err := runtime.Encode(codec, object)
112
// encode (serialize) a second time to verify that it was not varying
113
secondData, err := runtime.Encode(codec, object)
115
panic("This should not fail since we are encoding for the second time")
118
// serialization to the wire must be stable to ensure that we don't write twice to the DB
119
// when the object hasn't changed.
120
if !bytes.Equal(data, secondData) {
121
panic(fmt.Sprintf("%v: serialization is not stable: %s\n", name, printer.Sprintf("%#v", object)))
124
// decode (deserialize) the encoded data back into an object
125
obj2, err := runtime.Decode(codec, data)
127
panic(fmt.Sprintf("%v: %v\nCodec: %#v\nData: %s\nSource: %#v\n", name, err, codec, dataAsString(data), printer.Sprintf("%#v", object)))
130
// decode the encoded data into a new object (instead of letting the codec
131
// create a new object)
132
obj3 := reflect.New(reflect.TypeOf(object).Elem()).Interface().(runtime.Object)
133
if err := runtime.DecodeInto(codec, data, obj3); err != nil {
134
panic(fmt.Sprintf("%v: %v\n", name, err))
137
// ensure that the object produced from decoding the encoded data is equal
138
// to the original object
139
if diff := cmp.Diff(obj2, obj3, protocmp.Transform(), cmpopts.EquateNaNs()); diff != "" {
140
panic("These should not be different: " + diff)
144
// dataAsString is a simple helper.
145
func dataAsString(data []byte) string {
146
dataString := string(data)
147
if !strings.HasPrefix(dataString, "{") {
148
dataString = "\n" + hex.Dump(data)
149
legacyproto.NewBuffer(make([]byte, 0, 1024)).DebugPrint("decoded object", data)
154
// checkForNilValues is a helper to check for nil
155
// values in the runtime objects.
156
// This part only converts the interface to a reflect.Value.
157
func checkForNilValues(targetStruct any) error {
158
v := reflect.ValueOf(targetStruct)
160
err := checkForNil(e)
167
// Checks for nil values in a reflect.Value.
168
func checkForNil(e reflect.Value) error {
171
for i := 0; i < e.NumField(); i++ {
172
err := checkForNil(e.Field(i))
177
case reflect.Array, reflect.Slice:
178
for i := 0; i < e.Len(); i++ {
179
err := checkForNil(e.Index(i))
186
return fmt.Errorf("field is nil")
188
for _, k := range e.MapKeys() {
190
return fmt.Errorf("field is nil")
192
err := checkForNil(e.MapIndex(k))
199
return fmt.Errorf("field is nil")
202
err := checkForNil(e.Elem())