Skip to content

Commit 04dae58

Browse files
committed
Adds initial e2e tests and tooling
Signed-off-by: Daneyon Hansen <[email protected]>
1 parent 26d2765 commit 04dae58

13 files changed

+2165
-189
lines changed

api/v1alpha1/inferencemodel_types.go

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ import (
2020
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2121
)
2222

23+
const (
24+
// KindInferenceModel is the InferenceModel kind.
25+
KindInferenceModel = "InferenceModel"
26+
// ResourceInferenceModel is the name of the inferencemodels resource.
27+
ResourceInferenceModel = "inferencemodels"
28+
)
29+
2330
// InferenceModel is the Schema for the InferenceModels API.
2431
//
2532
// +kubebuilder:object:root=true

api/v1alpha1/inferencepool_types.go

+7
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,13 @@ import (
2020
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2121
)
2222

23+
const (
24+
// KindInferencePool is the InferencePool kind.
25+
KindInferencePool = "InferencePool"
26+
// ResourceInferencePool is the name of the inferencepools resource.
27+
ResourceInferencePool = "inferencepools"
28+
)
29+
2330
// InferencePool is the Schema for the InferencePools API.
2431
//
2532
// +kubebuilder:object:root=true

go.mod

+6-2
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,13 @@ require (
2020
google.golang.org/grpc v1.69.4
2121
google.golang.org/protobuf v1.36.3
2222
k8s.io/api v0.31.5
23+
k8s.io/apiextensions-apiserver v0.31.0
2324
k8s.io/apimachinery v0.31.5
2425
k8s.io/client-go v0.31.5
2526
k8s.io/code-generator v0.31.5
2627
k8s.io/component-base v0.31.5
2728
k8s.io/klog/v2 v2.130.1
29+
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
2830
sigs.k8s.io/controller-runtime v0.19.4
2931
sigs.k8s.io/structured-merge-diff/v4 v4.5.0
3032
)
@@ -73,6 +75,7 @@ require (
7375
github.com/google/gofuzz v1.2.0 // indirect
7476
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect
7577
github.com/google/uuid v1.6.0 // indirect
78+
github.com/gorilla/websocket v1.5.0 // indirect
7679
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
7780
github.com/huandu/xstrings v1.3.3 // indirect
7881
github.com/imdario/mergo v0.3.11 // indirect
@@ -87,9 +90,11 @@ require (
8790
github.com/mattn/go-isatty v0.0.20 // indirect
8891
github.com/mitchellh/copystructure v1.2.0 // indirect
8992
github.com/mitchellh/reflectwalk v1.0.2 // indirect
93+
github.com/moby/spdystream v0.4.0 // indirect
9094
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
9195
github.com/modern-go/reflect2 v1.0.2 // indirect
9296
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
97+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
9398
github.com/pkg/errors v0.9.1 // indirect
9499
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
95100
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
@@ -99,6 +104,7 @@ require (
99104
github.com/spf13/cobra v1.8.1 // indirect
100105
github.com/spf13/pflag v1.0.5 // indirect
101106
github.com/stoewer/go-strcase v1.2.0 // indirect
107+
github.com/stretchr/objx v0.5.2 // indirect
102108
github.com/x448/float16 v0.8.4 // indirect
103109
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
104110
go.opentelemetry.io/otel v1.31.0 // indirect
@@ -128,11 +134,9 @@ require (
128134
gopkg.in/inf.v0 v0.9.1 // indirect
129135
gopkg.in/yaml.v2 v2.4.0 // indirect
130136
gopkg.in/yaml.v3 v3.0.1 // indirect
131-
k8s.io/apiextensions-apiserver v0.31.0 // indirect
132137
k8s.io/apiserver v0.31.4 // indirect
133138
k8s.io/gengo/v2 v2.0.0-20240228010128-51d4e06bde70 // indirect
134139
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
135-
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 // indirect
136140
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect
137141
sigs.k8s.io/controller-tools v0.14.0 // indirect
138142
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect

go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
1919
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
2020
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
2121
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
22+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
23+
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
2224
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
2325
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
2426
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -115,6 +117,8 @@ github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAx
115117
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
116118
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
117119
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
120+
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
121+
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
118122
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
119123
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
120124
github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4=
@@ -159,13 +163,17 @@ github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HK
159163
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
160164
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
161165
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
166+
github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
167+
github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
162168
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
163169
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
164170
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
165171
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
166172
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
167173
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
168174
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
175+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
176+
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
169177
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
170178
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
171179
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
@@ -206,6 +214,8 @@ github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag
206214
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
207215
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
208216
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
217+
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
218+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
209219
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
210220
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
211221
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=

pkg/crd/install.go

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crd
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"errors"
23+
"fmt"
24+
"io"
25+
"os"
26+
"path/filepath"
27+
"strings"
28+
29+
apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
30+
"k8s.io/apimachinery/pkg/util/yaml"
31+
"k8s.io/client-go/util/retry"
32+
klog "k8s.io/klog/v2"
33+
"sigs.k8s.io/controller-runtime/pkg/client"
34+
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
35+
)
36+
37+
// InstallCRDs reads YAML files containing CustomResourceDefinitions from the given directory path.
38+
// It processes only valid YAML files with `apiVersion: apiextensions.k8s.io/v1` and `kind: CustomResourceDefinition`.
39+
func InstallCRDs(ctx context.Context, cli client.Client, dirPath string) error {
40+
var aggregatedErrors []string
41+
42+
// Read all files in the directory
43+
files, err := os.ReadDir(dirPath)
44+
if err != nil {
45+
return fmt.Errorf("failed to read directory %s: %v", dirPath, err)
46+
}
47+
48+
for _, file := range files {
49+
// Skip directories and non-YAML files
50+
if file.IsDir() || (!strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml")) {
51+
klog.Infof("Skipping non-YAML file: %s", file.Name())
52+
continue
53+
}
54+
55+
// Construct full file path
56+
filePath := filepath.Join(dirPath, file.Name())
57+
58+
// Read the file
59+
content, err := os.ReadFile(filePath)
60+
if err != nil {
61+
klog.Infof("Failed to read file %s: %v", filePath, err)
62+
aggregatedErrors = append(aggregatedErrors, err.Error())
63+
continue
64+
}
65+
66+
// Decode YAML content and filter by `apiVersion` and `kind`
67+
klog.Infof("Processing file: %s", filePath)
68+
decoder := yaml.NewYAMLOrJSONDecoder(bytes.NewReader(content), 1024)
69+
for {
70+
obj := &apiextv1.CustomResourceDefinition{}
71+
err := decoder.Decode(obj)
72+
if err != nil {
73+
if errors.Is(err, io.EOF) {
74+
break // End of file
75+
}
76+
klog.Errorf("Failed to decode CRD object from file %s: %v", filePath, err)
77+
aggregatedErrors = append(aggregatedErrors, err.Error())
78+
break
79+
}
80+
81+
// Check if the decoded object is a valid CRD
82+
if obj.APIVersion != "apiextensions.k8s.io/v1" || obj.Kind != "CustomResourceDefinition" {
83+
klog.Infof("Skipping invalid CRD object in file %s", filePath)
84+
continue
85+
}
86+
87+
// Apply the CRD using the controller-runtime client
88+
err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
89+
_, err := controllerutil.CreateOrUpdate(ctx, cli, obj, func() error {
90+
return nil
91+
})
92+
return err
93+
})
94+
if err != nil {
95+
klog.Errorf("Failed to apply CRD %s: %v", obj.Name, err)
96+
aggregatedErrors = append(aggregatedErrors, err.Error())
97+
}
98+
}
99+
}
100+
101+
if len(aggregatedErrors) > 0 {
102+
return errors.New(strings.Join(aggregatedErrors, "; "))
103+
}
104+
105+
return nil
106+
}

pkg/crd/install_test.go

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
/*
2+
Copyright 2024 The Kubernetes Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package crd_test
18+
19+
import (
20+
"context"
21+
"os"
22+
"testing"
23+
24+
"github.com/stretchr/testify/mock"
25+
"github.com/stretchr/testify/require"
26+
"inference.networking.x-k8s.io/gateway-api-inference-extension/pkg/crd"
27+
"inference.networking.x-k8s.io/gateway-api-inference-extension/pkg/crd/mocks"
28+
)
29+
30+
func createTempFile(t *testing.T, dir, content, pattern string) {
31+
tempFile, err := os.CreateTemp(dir, pattern)
32+
require.NoError(t, err)
33+
34+
_, err = tempFile.WriteString(content)
35+
require.NoError(t, err)
36+
tempFile.Close()
37+
}
38+
39+
func TestInstallCRDs(t *testing.T) {
40+
ctx := context.TODO()
41+
mockClient := new(mocks.MockClient)
42+
43+
// Mock calls for valid CRDs
44+
mockClient.On("Create", mock.Anything, mock.Anything, mock.Anything).Return(nil)
45+
46+
// Valid CRD content
47+
validCRD := `apiVersion: apiextensions.k8s.io/v1
48+
kind: CustomResourceDefinition
49+
metadata:
50+
name: valid-crd.example.com
51+
spec:
52+
group: example.com
53+
names:
54+
kind: ValidCrd
55+
listKind: ValidCrdList
56+
plural: validcrds
57+
singular: validcrd
58+
scope: Namespaced
59+
versions:
60+
- name: v1
61+
served: true
62+
storage: true`
63+
64+
// Non-CRD YAML content
65+
nonCRDYAML := `apiVersion: v1
66+
kind: ConfigMap
67+
metadata:
68+
name: test-configmap`
69+
70+
// Invalid file content
71+
invalidContent := "invalid content"
72+
73+
tests := []struct {
74+
description string
75+
setup func(dir string)
76+
expectError bool
77+
}{
78+
{
79+
description: "Directory with valid CRD file",
80+
setup: func(dir string) {
81+
createTempFile(t, dir, validCRD, "valid-*.yaml")
82+
},
83+
expectError: false,
84+
},
85+
{
86+
description: "Directory with invalid content file",
87+
setup: func(dir string) {
88+
createTempFile(t, dir, invalidContent, "invalid-*.yaml")
89+
},
90+
expectError: true,
91+
},
92+
{
93+
description: "Directory with non-CRD YAML file",
94+
setup: func(dir string) {
95+
createTempFile(t, dir, nonCRDYAML, "non-crd-*.yaml")
96+
},
97+
expectError: false,
98+
},
99+
{
100+
description: "Directory with mixed valid and invalid files",
101+
setup: func(dir string) {
102+
createTempFile(t, dir, validCRD, "valid-*.yaml")
103+
createTempFile(t, dir, invalidContent, "invalid-*.yaml")
104+
},
105+
expectError: true,
106+
},
107+
{
108+
description: "Empty directory",
109+
setup: func(dir string) {}, // No files created
110+
expectError: false,
111+
},
112+
}
113+
114+
for _, test := range tests {
115+
t.Run(test.description, func(t *testing.T) {
116+
tempDir := t.TempDir()
117+
test.setup(tempDir)
118+
119+
err := crd.InstallCRDs(ctx, mockClient, tempDir)
120+
if test.expectError {
121+
require.Error(t, err, "Expected an error but got nil")
122+
} else {
123+
require.NoError(t, err, "Expected no error but got one")
124+
}
125+
})
126+
}
127+
128+
// Assert mockClient expectations
129+
mockClient.AssertExpectations(t)
130+
}

0 commit comments

Comments
 (0)