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  }