sigs.k8s.io/cluster-api-provider-azure@v1.14.3/test/e2e/kubernetes/deployment/deployment.go (about)

     1  //go:build e2e
     2  // +build e2e
     3  
     4  /*
     5  Copyright 2020 The Kubernetes Authors.
     6  
     7  Licensed under the Apache License, Version 2.0 (the "License");
     8  you may not use this file except in compliance with the License.
     9  You may obtain a copy of the License at
    10  
    11      http://www.apache.org/licenses/LICENSE-2.0
    12  
    13  Unless required by applicable law or agreed to in writing, software
    14  distributed under the License is distributed on an "AS IS" BASIS,
    15  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    16  See the License for the specific language governing permissions and
    17  limitations under the License.
    18  */
    19  
    20  package deployment
    21  
    22  import (
    23  	"context"
    24  	"fmt"
    25  	"log"
    26  	"time"
    27  
    28  	. "github.com/onsi/gomega"
    29  	appsv1 "k8s.io/api/apps/v1"
    30  	corev1 "k8s.io/api/core/v1"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/labels"
    34  	"k8s.io/client-go/kubernetes"
    35  	typedappsv1 "k8s.io/client-go/kubernetes/typed/apps/v1"
    36  	"k8s.io/utils/ptr"
    37  	clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
    38  )
    39  
    40  const (
    41  	ExternalLoadbalancer                   = LoadbalancerType("external")
    42  	InternalLoadbalancer                   = LoadbalancerType("internal")
    43  	deploymentOperationTimeout             = 30 * time.Second
    44  	deploymentOperationSleepBetweenRetries = 3 * time.Second
    45  )
    46  
    47  type (
    48  	// Builder provides a helper interface for building Kubernetes deployments within this test suite
    49  	Builder struct {
    50  		deployment *appsv1.Deployment
    51  	}
    52  
    53  	LoadbalancerType string
    54  )
    55  
    56  // Create will create a deployment for a given image with a name in a namespace
    57  func Create(image, name, namespace string) *Builder {
    58  	e2eDeployment := &Builder{
    59  		deployment: &appsv1.Deployment{
    60  			ObjectMeta: metav1.ObjectMeta{
    61  				Name:      name,
    62  				Namespace: namespace,
    63  				Labels:    map[string]string{},
    64  			},
    65  			Spec: appsv1.DeploymentSpec{
    66  				Replicas: ptr.To[int32](1),
    67  				Selector: &metav1.LabelSelector{
    68  					MatchLabels: map[string]string{
    69  						"app": name,
    70  					},
    71  				},
    72  				Template: corev1.PodTemplateSpec{
    73  					ObjectMeta: metav1.ObjectMeta{
    74  						Labels: map[string]string{
    75  							"app": name,
    76  						},
    77  					},
    78  					Spec: corev1.PodSpec{
    79  						Containers: []corev1.Container{
    80  							{
    81  								Name:  name,
    82  								Image: image,
    83  								Resources: corev1.ResourceRequirements{
    84  									Requests: corev1.ResourceList{
    85  										corev1.ResourceCPU:    resource.MustParse("10m"),
    86  										corev1.ResourceMemory: resource.MustParse("10M"),
    87  									},
    88  								},
    89  							},
    90  						},
    91  						NodeSelector: map[string]string{
    92  							"kubernetes.io/os": "linux",
    93  						},
    94  					},
    95  				},
    96  			},
    97  		},
    98  	}
    99  
   100  	return e2eDeployment
   101  }
   102  
   103  func (d *Builder) AddLabels(labels map[string]string) {
   104  	for k, v := range labels {
   105  		d.deployment.ObjectMeta.Labels[k] = v
   106  		d.deployment.Spec.Selector.MatchLabels[k] = v
   107  		d.deployment.Spec.Template.ObjectMeta.Labels[k] = v
   108  	}
   109  }
   110  
   111  func (d *Builder) GetName() string {
   112  	return d.deployment.Name
   113  }
   114  
   115  func (d *Builder) SetReplicas(replicas int32) *Builder {
   116  	d.deployment.Spec.Replicas = &replicas
   117  	return d
   118  }
   119  
   120  func (d *Builder) AddContainerPort(name, portName string, portNumber int32, protocol corev1.Protocol) {
   121  	for _, c := range d.deployment.Spec.Template.Spec.Containers {
   122  		if c.Name == name {
   123  			c.Ports = []corev1.ContainerPort{
   124  				{
   125  					Name:          portName,
   126  					ContainerPort: portNumber,
   127  					Protocol:      protocol,
   128  				},
   129  			}
   130  		}
   131  	}
   132  }
   133  
   134  func (d *Builder) AddPVC(pvcName string) *Builder {
   135  	volumes := []corev1.Volume{
   136  		{
   137  			Name: "managed",
   138  			VolumeSource: corev1.VolumeSource{
   139  				PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
   140  					ClaimName: pvcName,
   141  				},
   142  			},
   143  		},
   144  	}
   145  	d.deployment.Spec.Template.Spec.Volumes = volumes
   146  	return d
   147  }
   148  
   149  func (d *Builder) Deploy(ctx context.Context, clientset *kubernetes.Clientset) (*appsv1.Deployment, error) {
   150  	var deployment *appsv1.Deployment
   151  	Eventually(func(g Gomega) {
   152  		var err error
   153  		deployment, err = d.Client(clientset).Create(ctx, d.deployment, metav1.CreateOptions{})
   154  		if err != nil {
   155  			log.Printf("Error trying to deploy %s in namespace %s:%s\n", d.deployment.Name, d.deployment.ObjectMeta.Namespace, err.Error())
   156  		}
   157  		g.Expect(err).NotTo(HaveOccurred())
   158  	}, deploymentOperationTimeout, deploymentOperationSleepBetweenRetries).Should(Succeed())
   159  
   160  	return deployment, nil
   161  }
   162  
   163  func (d *Builder) Client(clientset *kubernetes.Clientset) typedappsv1.DeploymentInterface {
   164  	return clientset.AppsV1().Deployments(d.deployment.ObjectMeta.Namespace)
   165  }
   166  
   167  func (d *Builder) GetPodsFromDeployment(ctx context.Context, clientset *kubernetes.Clientset) ([]corev1.Pod, error) {
   168  	opts := metav1.ListOptions{
   169  		LabelSelector: labels.Set(d.deployment.Labels).String(),
   170  		Limit:         100,
   171  	}
   172  	var pods *corev1.PodList
   173  	Eventually(func(g Gomega) {
   174  		var err error
   175  		pods, err = clientset.CoreV1().Pods(d.deployment.GetNamespace()).List(ctx, opts)
   176  		if err != nil {
   177  			log.Printf("Error trying to get the pods from deployment %s:%s\n", d.deployment.GetName(), err.Error())
   178  		}
   179  		g.Expect(err).NotTo(HaveOccurred())
   180  	}, deploymentOperationTimeout, deploymentOperationSleepBetweenRetries).Should(Succeed())
   181  	return pods.Items, nil
   182  }
   183  
   184  func (d *Builder) CreateServiceResourceSpec(ports []corev1.ServicePort, lbtype LoadbalancerType, ipFamilies []corev1.IPFamily) *corev1.Service {
   185  	suffix := "elb"
   186  	annotations := map[string]string{}
   187  	if lbtype == InternalLoadbalancer {
   188  		suffix = "ilb"
   189  		annotations["service.beta.kubernetes.io/azure-load-balancer-internal"] = "true"
   190  	}
   191  
   192  	return &corev1.Service{
   193  		ObjectMeta: metav1.ObjectMeta{
   194  			Name:        fmt.Sprintf("%s-%s", d.deployment.Name, suffix),
   195  			Namespace:   d.deployment.Namespace,
   196  			Annotations: annotations,
   197  		},
   198  		Spec: corev1.ServiceSpec{
   199  			Type:  corev1.ServiceTypeLoadBalancer,
   200  			Ports: ports,
   201  			Selector: map[string]string{
   202  				"app": d.deployment.Name,
   203  			},
   204  			IPFamilies: ipFamilies,
   205  		},
   206  	}
   207  }
   208  
   209  func (d *Builder) SetImage(name, image string) {
   210  	for i, c := range d.deployment.Spec.Template.Spec.Containers {
   211  		if c.Name == name {
   212  			c.Image = image
   213  			d.deployment.Spec.Template.Spec.Containers[i] = c
   214  		}
   215  	}
   216  }
   217  
   218  func (d *Builder) AddWindowsSelectors() *Builder {
   219  	if d.deployment.Spec.Template.Spec.NodeSelector == nil {
   220  		d.deployment.Spec.Template.Spec.NodeSelector = map[string]string{}
   221  	}
   222  
   223  	d.deployment.Spec.Template.Spec.NodeSelector["kubernetes.io/os"] = "windows"
   224  	return d
   225  }
   226  
   227  // AddMachinePoolSelectors will add node selectors which will ensure the workload runs on a specific MachinePool
   228  func (d *Builder) AddMachinePoolSelectors(machinePoolName string) *Builder {
   229  	if d.deployment.Spec.Template.Spec.NodeSelector == nil {
   230  		d.deployment.Spec.Template.Spec.NodeSelector = map[string]string{}
   231  	}
   232  
   233  	d.deployment.Spec.Template.Spec.NodeSelector[clusterv1.OwnerKindAnnotation] = "MachinePool"
   234  	d.deployment.Spec.Template.Spec.NodeSelector[clusterv1.OwnerNameAnnotation] = machinePoolName
   235  	return d
   236  }
   237  
   238  func (d *Builder) AddPodAntiAffinity(affinity corev1.PodAntiAffinity) *Builder {
   239  	if d.deployment.Spec.Template.Spec.Affinity == nil {
   240  		d.deployment.Spec.Template.Spec.Affinity = &corev1.Affinity{}
   241  	}
   242  
   243  	d.deployment.Spec.Template.Spec.Affinity.PodAntiAffinity = &affinity
   244  	return d
   245  }