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 v1beta1
18+
19+ import (
20+ "context"
21+ "path/filepath"
22+ "testing"
23+
24+ . "github.com/onsi/gomega"
25+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26+ "k8s.io/client-go/kubernetes/scheme"
27+ "sigs.k8s.io/controller-runtime/pkg/client"
28+ "sigs.k8s.io/controller-runtime/pkg/envtest"
29+ )
30+
31+ func TestGCPMachine_AliasIPRanges_Validation (t * testing.T ) {
32+ g := NewWithT (t )
33+ ctx := context .Background ()
34+
35+ // Setup the test environment with CRDs
36+ testEnv := & envtest.Environment {
37+ CRDDirectoryPaths : []string {
38+ filepath .Join (".." , ".." , "config" , "crd" , "bases" ),
39+ },
40+ ErrorIfCRDPathMissing : true ,
41+ }
42+
43+ cfg , err := testEnv .Start ()
44+ g .Expect (err ).NotTo (HaveOccurred ())
45+ g .Expect (cfg ).NotTo (BeNil ())
46+
47+ defer func () {
48+ err := testEnv .Stop ()
49+ g .Expect (err ).NotTo (HaveOccurred ())
50+ }()
51+
52+ err = AddToScheme (scheme .Scheme )
53+ g .Expect (err ).NotTo (HaveOccurred ())
54+
55+ k8sClient , err := client .New (cfg , client.Options {Scheme : scheme .Scheme })
56+ g .Expect (err ).NotTo (HaveOccurred ())
57+ g .Expect (k8sClient ).NotTo (BeNil ())
58+
59+ namespace := "default"
60+
61+ tests := []struct {
62+ name string
63+ aliasIPRanges []AliasIPRange
64+ wantErr bool
65+ errorContains string
66+ }{
67+ // Valid cases - these should be accepted
68+ {
69+ name : "valid CIDR notation" ,
70+ aliasIPRanges : []AliasIPRange {
71+ {
72+ IPCidrRange : "127.0.0.1/24" ,
73+ SubnetworkRangeName : "subnet-name" ,
74+ },
75+ },
76+ wantErr : false ,
77+ },
78+ {
79+ name : "valid IP address only" ,
80+ aliasIPRanges : []AliasIPRange {
81+ {
82+ IPCidrRange : "127.0.0.1" ,
83+ SubnetworkRangeName : "subnet-name" ,
84+ },
85+ },
86+ wantErr : false ,
87+ },
88+ {
89+ name : "valid netmask only" ,
90+ aliasIPRanges : []AliasIPRange {
91+ {
92+ IPCidrRange : "/24" ,
93+ SubnetworkRangeName : "subnet-name" ,
94+ },
95+ },
96+ wantErr : false ,
97+ },
98+ {
99+ name : "valid without subnetwork range name" ,
100+ aliasIPRanges : []AliasIPRange {
101+ {
102+ IPCidrRange : "/24" ,
103+ },
104+ },
105+ wantErr : false ,
106+ },
107+ {
108+ name : "valid multiple ranges" ,
109+ aliasIPRanges : []AliasIPRange {
110+ {
111+ IPCidrRange : "10.0.0.0/24" ,
112+ SubnetworkRangeName : "pods" ,
113+ },
114+ {
115+ IPCidrRange : "10.1.0.0/24" ,
116+ SubnetworkRangeName : "services" ,
117+ },
118+ },
119+ wantErr : false ,
120+ },
121+ {
122+ name : "valid empty alias IP ranges" ,
123+ aliasIPRanges : []AliasIPRange {},
124+ wantErr : false ,
125+ },
126+ {
127+ name : "valid nil alias IP ranges" ,
128+ aliasIPRanges : nil ,
129+ wantErr : false ,
130+ },
131+ // Invalid cases - these should be rejected by CRD validation
132+ {
133+ name : "invalid netmask too large" ,
134+ aliasIPRanges : []AliasIPRange {
135+ {
136+ IPCidrRange : "/33" ,
137+ SubnetworkRangeName : "subnet-name" ,
138+ },
139+ },
140+ wantErr : true ,
141+ errorContains : "should match" ,
142+ },
143+ {
144+ name : "invalid empty ipCidrRange" ,
145+ aliasIPRanges : []AliasIPRange {
146+ {
147+ IPCidrRange : "" ,
148+ SubnetworkRangeName : "subnet-name" ,
149+ },
150+ },
151+ wantErr : true ,
152+ errorContains : "should match" ,
153+ },
154+ {
155+ name : "invalid IP address out of range" ,
156+ aliasIPRanges : []AliasIPRange {
157+ {
158+ IPCidrRange : "1270.0.0.1/24" ,
159+ SubnetworkRangeName : "subnet-name" ,
160+ },
161+ },
162+ wantErr : true ,
163+ errorContains : "should match" ,
164+ },
165+ {
166+ name : "invalid IP address with letters" ,
167+ aliasIPRanges : []AliasIPRange {
168+ {
169+ IPCidrRange : "127.0.0.1a" ,
170+ SubnetworkRangeName : "subnet-name" ,
171+ },
172+ },
173+ wantErr : true ,
174+ errorContains : "should match" ,
175+ },
176+ {
177+ name : "invalid CIDR with letters" ,
178+ aliasIPRanges : []AliasIPRange {
179+ {
180+ IPCidrRange : "127.0.0.1a/24" ,
181+ SubnetworkRangeName : "subnet-name" ,
182+ },
183+ },
184+ wantErr : true ,
185+ errorContains : "should match" ,
186+ },
187+ {
188+ name : "invalid format with extra slash" ,
189+ aliasIPRanges : []AliasIPRange {
190+ {
191+ IPCidrRange : "10.0.0.0//24" ,
192+ SubnetworkRangeName : "subnet-name" ,
193+ },
194+ },
195+ wantErr : true ,
196+ errorContains : "should match" ,
197+ },
198+ {
199+ name : "invalid format with space" ,
200+ aliasIPRanges : []AliasIPRange {
201+ {
202+ IPCidrRange : "10.0.0.0 /24" ,
203+ SubnetworkRangeName : "subnet-name" ,
204+ },
205+ },
206+ wantErr : true ,
207+ errorContains : "should match" ,
208+ },
209+ }
210+
211+ for _ , tt := range tests {
212+ t .Run (tt .name , func (t * testing.T ) {
213+ g := NewWithT (t )
214+
215+ // Create a GCPMachine with the test aliasIPRanges
216+ machine := & GCPMachine {
217+ ObjectMeta : metav1.ObjectMeta {
218+ Name : "test-machine-" + randString (6 ),
219+ Namespace : namespace ,
220+ },
221+ Spec : GCPMachineSpec {
222+ InstanceType : "n1-standard-2" ,
223+ AliasIPRanges : tt .aliasIPRanges ,
224+ },
225+ }
226+
227+ // Attempt to create the machine
228+ err := k8sClient .Create (ctx , machine )
229+
230+ if tt .wantErr {
231+ g .Expect (err ).To (HaveOccurred ())
232+ if tt .errorContains != "" {
233+ g .Expect (err .Error ()).To (ContainSubstring (tt .errorContains ))
234+ }
235+ } else {
236+ g .Expect (err ).NotTo (HaveOccurred ())
237+ // Clean up successfully created resources
238+ _ = k8sClient .Delete (ctx , machine )
239+ }
240+ })
241+ }
242+ }
243+
244+ // randString generates a random string of specified length
245+ func randString (n int ) string {
246+ const letterBytes = "abcdefghijklmnopqrstuvwxyz0123456789"
247+ b := make ([]byte , n )
248+ for i := range b {
249+ b [i ] = letterBytes [i % len (letterBytes )]
250+ }
251+ return string (b )
252+ }
0 commit comments