sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/cluster_test.go (about)

     1  /*
     2  Copyright 2020 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 internal
    18  
    19  import (
    20  	"context"
    21  	"crypto/rand"
    22  	"crypto/rsa"
    23  	"crypto/x509"
    24  	"crypto/x509/pkix"
    25  	"fmt"
    26  	"math/big"
    27  	"testing"
    28  	"time"
    29  
    30  	. "github.com/onsi/gomega"
    31  	appsv1 "k8s.io/api/apps/v1"
    32  	corev1 "k8s.io/api/core/v1"
    33  	rbacv1 "k8s.io/api/rbac/v1"
    34  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    35  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    36  	"k8s.io/apimachinery/pkg/runtime/schema"
    37  	"sigs.k8s.io/controller-runtime/pkg/client"
    38  	"sigs.k8s.io/controller-runtime/pkg/log"
    39  
    40  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    41  	"sigs.k8s.io/cluster-api/controllers/remote"
    42  	"sigs.k8s.io/cluster-api/util/certs"
    43  	"sigs.k8s.io/cluster-api/util/collections"
    44  	"sigs.k8s.io/cluster-api/util/kubeconfig"
    45  	"sigs.k8s.io/cluster-api/util/secret"
    46  )
    47  
    48  func TestGetMachinesForCluster(t *testing.T) {
    49  	g := NewWithT(t)
    50  
    51  	m := Management{Client: &fakeClient{
    52  		list: machineListForTestGetMachinesForCluster(),
    53  	}}
    54  	cluster := &clusterv1.Cluster{
    55  		ObjectMeta: metav1.ObjectMeta{
    56  			Namespace: metav1.NamespaceDefault,
    57  			Name:      "my-cluster",
    58  		},
    59  	}
    60  	machines, err := m.GetMachinesForCluster(ctx, cluster)
    61  	g.Expect(err).ToNot(HaveOccurred())
    62  	g.Expect(machines).To(HaveLen(3))
    63  
    64  	// Test the ControlPlaneMachines works
    65  	machines, err = m.GetMachinesForCluster(ctx, cluster, collections.ControlPlaneMachines("my-cluster"))
    66  	g.Expect(err).ToNot(HaveOccurred())
    67  	g.Expect(machines).To(HaveLen(1))
    68  
    69  	// Test that the filters use AND logic instead of OR logic
    70  	nameFilter := func(cluster *clusterv1.Machine) bool {
    71  		return cluster.Name == "first-machine"
    72  	}
    73  	machines, err = m.GetMachinesForCluster(ctx, cluster, collections.ControlPlaneMachines("my-cluster"), nameFilter)
    74  	g.Expect(err).ToNot(HaveOccurred())
    75  	g.Expect(machines).To(HaveLen(1))
    76  }
    77  
    78  func TestGetWorkloadCluster(t *testing.T) {
    79  	g := NewWithT(t)
    80  
    81  	ns, err := env.CreateNamespace(ctx, "workload-cluster2")
    82  	g.Expect(err).ToNot(HaveOccurred())
    83  	defer func() {
    84  		g.Expect(env.Cleanup(ctx, ns)).To(Succeed())
    85  	}()
    86  
    87  	// Create an etcd secret with valid certs
    88  	key, err := certs.NewPrivateKey()
    89  	g.Expect(err).ToNot(HaveOccurred())
    90  	cert, err := getTestCACert(key)
    91  	g.Expect(err).ToNot(HaveOccurred())
    92  	etcdSecret := &corev1.Secret{
    93  		ObjectMeta: metav1.ObjectMeta{
    94  			Name:      "my-cluster-etcd",
    95  			Namespace: ns.Name,
    96  			Labels: map[string]string{
    97  				clusterv1.ClusterNameLabel: "my-cluster",
    98  			},
    99  		},
   100  		Data: map[string][]byte{
   101  			secret.TLSCrtDataName: certs.EncodeCertPEM(cert),
   102  			secret.TLSKeyDataName: certs.EncodePrivateKeyPEM(key),
   103  		},
   104  	}
   105  	emptyCrtEtcdSecret := etcdSecret.DeepCopy()
   106  	delete(emptyCrtEtcdSecret.Data, secret.TLSCrtDataName)
   107  	emptyKeyEtcdSecret := etcdSecret.DeepCopy()
   108  	delete(emptyKeyEtcdSecret.Data, secret.TLSKeyDataName)
   109  	badCrtEtcdSecret := etcdSecret.DeepCopy()
   110  	badCrtEtcdSecret.Data[secret.TLSCrtDataName] = []byte("bad cert")
   111  
   112  	// Create kubeconfig secret
   113  	// Store the envtest config as the contents of the kubeconfig secret.
   114  	// This way we are using the envtest environment as both the
   115  	// management and the workload cluster.
   116  	testEnvKubeconfig := kubeconfig.FromEnvTestConfig(env.GetConfig(), &clusterv1.Cluster{
   117  		ObjectMeta: metav1.ObjectMeta{
   118  			Name:      "my-cluster",
   119  			Namespace: ns.Name,
   120  		},
   121  	})
   122  	kubeconfigSecret := &corev1.Secret{
   123  		ObjectMeta: metav1.ObjectMeta{
   124  			Name:      "my-cluster-kubeconfig",
   125  			Namespace: ns.Name,
   126  			Labels: map[string]string{
   127  				clusterv1.ClusterNameLabel: "my-cluster",
   128  			},
   129  		},
   130  		Data: map[string][]byte{
   131  			secret.KubeconfigDataName: testEnvKubeconfig,
   132  		},
   133  	}
   134  	clusterKey := client.ObjectKey{
   135  		Name:      "my-cluster",
   136  		Namespace: ns.Name,
   137  	}
   138  
   139  	tests := []struct {
   140  		name       string
   141  		clusterKey client.ObjectKey
   142  		objs       []client.Object
   143  		expectErr  bool
   144  	}{
   145  		{
   146  			name:       "returns a workload cluster",
   147  			clusterKey: clusterKey,
   148  			objs:       []client.Object{etcdSecret.DeepCopy(), kubeconfigSecret.DeepCopy()},
   149  			expectErr:  false,
   150  		},
   151  		{
   152  			name:       "returns error if cannot get rest.Config from kubeconfigSecret",
   153  			clusterKey: clusterKey,
   154  			objs:       []client.Object{etcdSecret.DeepCopy()},
   155  			expectErr:  true,
   156  		},
   157  		{
   158  			name:       "returns error if unable to find the etcd secret",
   159  			clusterKey: clusterKey,
   160  			objs:       []client.Object{kubeconfigSecret.DeepCopy()},
   161  			expectErr:  true,
   162  		},
   163  		{
   164  			name:       "returns error if unable to find the certificate in the etcd secret",
   165  			clusterKey: clusterKey,
   166  			objs:       []client.Object{emptyCrtEtcdSecret.DeepCopy(), kubeconfigSecret.DeepCopy()},
   167  			expectErr:  true,
   168  		},
   169  		{
   170  			name:       "returns error if unable to find the key in the etcd secret",
   171  			clusterKey: clusterKey,
   172  			objs:       []client.Object{emptyKeyEtcdSecret.DeepCopy(), kubeconfigSecret.DeepCopy()},
   173  			expectErr:  true,
   174  		},
   175  		{
   176  			name:       "returns error if unable to generate client cert",
   177  			clusterKey: clusterKey,
   178  			objs:       []client.Object{badCrtEtcdSecret.DeepCopy(), kubeconfigSecret.DeepCopy()},
   179  			expectErr:  true,
   180  		},
   181  	}
   182  
   183  	for _, tt := range tests {
   184  		t.Run(tt.name, func(t *testing.T) {
   185  			g := NewWithT(t)
   186  
   187  			for _, o := range tt.objs {
   188  				g.Expect(env.CreateAndWait(ctx, o)).To(Succeed())
   189  				defer func(do client.Object) {
   190  					g.Expect(env.CleanupAndWait(ctx, do)).To(Succeed())
   191  				}(o)
   192  			}
   193  
   194  			// We have to create a new ClusterCacheTracker for every test case otherwise
   195  			// it could still have a rest config from a previous run cached.
   196  			tracker, err := remote.NewClusterCacheTracker(
   197  				env.Manager,
   198  				remote.ClusterCacheTrackerOptions{
   199  					Log: &log.Log,
   200  				},
   201  			)
   202  			g.Expect(err).ToNot(HaveOccurred())
   203  
   204  			m := Management{
   205  				Client:              env.GetClient(),
   206  				SecretCachingClient: secretCachingClient,
   207  				Tracker:             tracker,
   208  			}
   209  
   210  			workloadCluster, err := m.GetWorkloadCluster(ctx, tt.clusterKey)
   211  			if tt.expectErr {
   212  				g.Expect(err).To(HaveOccurred())
   213  				g.Expect(workloadCluster).To(BeNil())
   214  				return
   215  			}
   216  			g.Expect(err).ToNot(HaveOccurred())
   217  			g.Expect(workloadCluster).ToNot(BeNil())
   218  		})
   219  	}
   220  }
   221  
   222  func getTestCACert(key *rsa.PrivateKey) (*x509.Certificate, error) {
   223  	cfg := certs.Config{
   224  		CommonName: "kubernetes",
   225  	}
   226  
   227  	now := time.Now().UTC()
   228  
   229  	tmpl := x509.Certificate{
   230  		SerialNumber: new(big.Int).SetInt64(0),
   231  		Subject: pkix.Name{
   232  			CommonName:   cfg.CommonName,
   233  			Organization: cfg.Organization,
   234  		},
   235  		NotBefore:             now.Add(time.Minute * -5),
   236  		NotAfter:              now.Add(time.Hour * 24), // 1 day
   237  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
   238  		MaxPathLenZero:        true,
   239  		BasicConstraintsValid: true,
   240  		MaxPathLen:            0,
   241  		IsCA:                  true,
   242  	}
   243  
   244  	b, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  
   249  	c, err := x509.ParseCertificate(b)
   250  	return c, err
   251  }
   252  
   253  func machineListForTestGetMachinesForCluster() *clusterv1.MachineList {
   254  	owned := true
   255  	ownedRef := []metav1.OwnerReference{
   256  		{
   257  			Kind:       "KubeadmControlPlane",
   258  			Name:       "my-control-plane",
   259  			Controller: &owned,
   260  		},
   261  	}
   262  	machine := func(name string) clusterv1.Machine {
   263  		return clusterv1.Machine{
   264  			TypeMeta: metav1.TypeMeta{},
   265  			ObjectMeta: metav1.ObjectMeta{
   266  				Name:      name,
   267  				Namespace: metav1.NamespaceDefault,
   268  				Labels: map[string]string{
   269  					clusterv1.ClusterNameLabel: "my-cluster",
   270  				},
   271  			},
   272  		}
   273  	}
   274  	controlPlaneMachine := machine("first-machine")
   275  	controlPlaneMachine.ObjectMeta.Labels[clusterv1.MachineControlPlaneLabel] = ""
   276  	controlPlaneMachine.OwnerReferences = ownedRef
   277  
   278  	return &clusterv1.MachineList{
   279  		Items: []clusterv1.Machine{
   280  			controlPlaneMachine,
   281  			machine("second-machine"),
   282  			machine("third-machine"),
   283  		},
   284  	}
   285  }
   286  
   287  type fakeClient struct {
   288  	client.Client
   289  	list interface{}
   290  
   291  	createErr    error
   292  	get          map[string]interface{}
   293  	getCalled    bool
   294  	updateCalled bool
   295  	getErr       error
   296  	patchErr     error
   297  	updateErr    error
   298  	listErr      error
   299  }
   300  
   301  func (f *fakeClient) Get(_ context.Context, key client.ObjectKey, obj client.Object, _ ...client.GetOption) error {
   302  	f.getCalled = true
   303  	if f.getErr != nil {
   304  		return f.getErr
   305  	}
   306  	item := f.get[key.String()]
   307  	switch l := item.(type) {
   308  	case *corev1.Pod:
   309  		l.DeepCopyInto(obj.(*corev1.Pod))
   310  	case *rbacv1.RoleBinding:
   311  		l.DeepCopyInto(obj.(*rbacv1.RoleBinding))
   312  	case *rbacv1.Role:
   313  		l.DeepCopyInto(obj.(*rbacv1.Role))
   314  	case *appsv1.DaemonSet:
   315  		l.DeepCopyInto(obj.(*appsv1.DaemonSet))
   316  	case *corev1.ConfigMap:
   317  		l.DeepCopyInto(obj.(*corev1.ConfigMap))
   318  	case *corev1.Secret:
   319  		l.DeepCopyInto(obj.(*corev1.Secret))
   320  	case nil:
   321  		return apierrors.NewNotFound(schema.GroupResource{}, key.Name)
   322  	default:
   323  		return fmt.Errorf("unknown type: %s", l)
   324  	}
   325  	return nil
   326  }
   327  
   328  func (f *fakeClient) List(_ context.Context, list client.ObjectList, _ ...client.ListOption) error {
   329  	if f.listErr != nil {
   330  		return f.listErr
   331  	}
   332  	switch l := f.list.(type) {
   333  	case *clusterv1.MachineList:
   334  		l.DeepCopyInto(list.(*clusterv1.MachineList))
   335  	case *corev1.NodeList:
   336  		l.DeepCopyInto(list.(*corev1.NodeList))
   337  	case *corev1.PodList:
   338  		l.DeepCopyInto(list.(*corev1.PodList))
   339  	default:
   340  		return fmt.Errorf("unknown type: %s", l)
   341  	}
   342  	return nil
   343  }
   344  
   345  func (f *fakeClient) Create(_ context.Context, _ client.Object, _ ...client.CreateOption) error {
   346  	if f.createErr != nil {
   347  		return f.createErr
   348  	}
   349  	return nil
   350  }
   351  
   352  func (f *fakeClient) Patch(_ context.Context, _ client.Object, _ client.Patch, _ ...client.PatchOption) error {
   353  	if f.patchErr != nil {
   354  		return f.patchErr
   355  	}
   356  	return nil
   357  }
   358  
   359  func (f *fakeClient) Update(_ context.Context, _ client.Object, _ ...client.UpdateOption) error {
   360  	f.updateCalled = true
   361  	if f.updateErr != nil {
   362  		return f.updateErr
   363  	}
   364  	return nil
   365  }