sigs.k8s.io/cluster-api@v1.7.1/exp/ipam/internal/webhooks/ipaddress_test.go (about) 1 /* 2 Copyright 2022 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 webhooks 18 19 import ( 20 "context" 21 "testing" 22 23 . "github.com/onsi/gomega" 24 corev1 "k8s.io/api/core/v1" 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime" 27 "k8s.io/utils/ptr" 28 "sigs.k8s.io/controller-runtime/pkg/client" 29 "sigs.k8s.io/controller-runtime/pkg/client/fake" 30 31 ipamv1 "sigs.k8s.io/cluster-api/exp/ipam/api/v1beta1" 32 ) 33 34 func TestIPAddressValidateCreate(t *testing.T) { 35 claim := &ipamv1.IPAddressClaim{ 36 TypeMeta: metav1.TypeMeta{ 37 APIVersion: ipamv1.GroupVersion.String(), 38 Kind: "IPAddressClaim", 39 }, 40 ObjectMeta: metav1.ObjectMeta{ 41 Name: "claim", 42 Namespace: "default", 43 }, 44 Spec: ipamv1.IPAddressClaimSpec{ 45 PoolRef: corev1.TypedLocalObjectReference{ 46 Kind: "TestPool", 47 Name: "pool", 48 APIGroup: ptr.To("ipam.cluster.x-k8s.io"), 49 }, 50 }, 51 } 52 53 getAddress := func(v6 bool, fn func(addr *ipamv1.IPAddress)) ipamv1.IPAddress { 54 addr := ipamv1.IPAddress{ 55 ObjectMeta: metav1.ObjectMeta{ 56 Namespace: "default", 57 }, 58 Spec: ipamv1.IPAddressSpec{ 59 ClaimRef: corev1.LocalObjectReference{Name: claim.Name}, 60 PoolRef: claim.Spec.PoolRef, 61 Address: "10.0.0.1", 62 Prefix: 24, 63 Gateway: "10.0.0.254", 64 }, 65 } 66 if v6 { 67 addr.Spec.Address = "42::1" 68 addr.Spec.Prefix = 64 69 addr.Spec.Gateway = "42::ffff" 70 } 71 fn(&addr) 72 return addr 73 } 74 75 tests := []struct { 76 name string 77 ip ipamv1.IPAddress 78 extraObjs []client.Object 79 expectErr bool 80 }{ 81 { 82 name: "a valid IPv4 Address should be accepted", 83 ip: getAddress(false, func(*ipamv1.IPAddress) {}), 84 extraObjs: []client.Object{claim}, 85 expectErr: false, 86 }, 87 { 88 name: "a valid IPv6 Address should be accepted", 89 ip: getAddress(true, func(*ipamv1.IPAddress) {}), 90 extraObjs: []client.Object{claim}, 91 expectErr: false, 92 }, 93 { 94 name: "a prefix that is negative should be rejected", 95 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 96 addr.Spec.Prefix = -1 97 }), 98 extraObjs: []client.Object{claim}, 99 expectErr: true, 100 }, 101 { 102 name: "a prefix that is too large for v4 should be rejected", 103 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 104 addr.Spec.Prefix = 64 105 }), 106 extraObjs: []client.Object{claim}, 107 expectErr: true, 108 }, 109 { 110 name: "a prefix that is too large for v6 should be rejected", 111 ip: getAddress(true, func(addr *ipamv1.IPAddress) { 112 addr.Spec.Prefix = 256 113 }), 114 extraObjs: []client.Object{claim}, 115 expectErr: true, 116 }, 117 { 118 name: "an invalid address should be rejected", 119 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 120 addr.Spec.Address = "42" 121 }), 122 extraObjs: []client.Object{claim}, 123 expectErr: true, 124 }, 125 { 126 name: "an invalid gateway should be rejected", 127 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 128 addr.Spec.Gateway = "42" 129 }), 130 extraObjs: []client.Object{claim}, 131 expectErr: true, 132 }, 133 { 134 name: "an empty gateway should be allowed", 135 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 136 addr.Spec.Gateway = "" 137 }), 138 extraObjs: []client.Object{claim}, 139 expectErr: false, 140 }, 141 { 142 name: "a pool reference that does not match the claim should be rejected", 143 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 144 addr.Spec.PoolRef.Name = "nothing" 145 }), 146 extraObjs: []client.Object{claim}, 147 expectErr: true, 148 }, 149 { 150 name: "a pool reference that does not contain a group should be rejected", 151 ip: getAddress(false, func(addr *ipamv1.IPAddress) { 152 addr.Spec.PoolRef.APIGroup = nil 153 }), 154 extraObjs: []client.Object{claim}, 155 expectErr: true, 156 }, 157 } 158 159 for i := range tests { 160 tt := tests[i] 161 t.Run(tt.name, func(t *testing.T) { 162 g := NewWithT(t) 163 scheme := runtime.NewScheme() 164 g.Expect(ipamv1.AddToScheme(scheme)).To(Succeed()) 165 wh := IPAddress{ 166 Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.extraObjs...).Build(), 167 } 168 if tt.expectErr { 169 g.Expect(wh.validate(context.Background(), &tt.ip)).NotTo(Succeed()) 170 } else { 171 g.Expect(wh.validate(context.Background(), &tt.ip)).To(Succeed()) 172 } 173 }) 174 } 175 } 176 177 func TestIPAddressValidateUpdate(t *testing.T) { 178 getAddress := func(fn func(addr *ipamv1.IPAddress)) ipamv1.IPAddress { 179 addr := ipamv1.IPAddress{ 180 ObjectMeta: metav1.ObjectMeta{ 181 Namespace: "default", 182 }, 183 Spec: ipamv1.IPAddressSpec{ 184 ClaimRef: corev1.LocalObjectReference{}, 185 PoolRef: corev1.TypedLocalObjectReference{}, 186 Address: "10.0.0.1", 187 Prefix: 24, 188 Gateway: "10.0.0.254", 189 }, 190 } 191 fn(&addr) 192 return addr 193 } 194 195 tests := []struct { 196 name string 197 oldIP ipamv1.IPAddress 198 newIP ipamv1.IPAddress 199 extraObjs []client.Object 200 expectErr bool 201 }{ 202 { 203 name: "should accept objects with identical spec", 204 oldIP: getAddress(func(*ipamv1.IPAddress) {}), 205 newIP: getAddress(func(*ipamv1.IPAddress) {}), 206 expectErr: false, 207 }, 208 { 209 name: "should reject objects with different spec", 210 oldIP: getAddress(func(*ipamv1.IPAddress) {}), 211 newIP: getAddress(func(addr *ipamv1.IPAddress) { 212 addr.Spec.Address = "10.0.0.2" 213 }), 214 expectErr: true, 215 }, 216 } 217 218 for i := range tests { 219 tt := tests[i] 220 t.Run(tt.name, func(t *testing.T) { 221 g := NewWithT(t) 222 scheme := runtime.NewScheme() 223 g.Expect(ipamv1.AddToScheme(scheme)).To(Succeed()) 224 wh := IPAddress{ 225 Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(tt.extraObjs...).Build(), 226 } 227 warnings, err := wh.ValidateUpdate(context.Background(), &tt.oldIP, &tt.newIP) 228 if tt.expectErr { 229 g.Expect(err).To(HaveOccurred()) 230 } else { 231 g.Expect(err).ToNot(HaveOccurred()) 232 } 233 g.Expect(warnings).To(BeEmpty()) 234 }) 235 } 236 }