ingress-nginx-helm/test/e2e/framework/deployment.go

640 lines
16 KiB
Go
Raw Normal View History

2017-11-10 02:00:38 +00:00
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package framework
import (
"context"
"errors"
"fmt"
"os"
"time"
"github.com/onsi/ginkgo/v2"
2020-02-19 03:08:14 +00:00
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
2017-11-10 02:00:38 +00:00
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/intstr"
)
// EchoService name of the deployment for the echo app
const EchoService = "echo"
// SlowEchoService name of the deployment for the echo app
const SlowEchoService = "slow-echo"
// HTTPBunService name of the deployment for the httpbun app
const HTTPBunService = "httpbun"
// NipService name of external service using nip.io
const NIPService = "external-nip"
// HTTPBunImage is the default image that is used to deploy HTTPBun with the framwork
var HTTPBunImage = os.Getenv("HTTPBUN_IMAGE")
// EchoImage is the default image to be used by the echo service
const EchoImage = "registry.k8s.io/ingress-nginx/e2e-test-echo@sha256:4938d1d91a2b7d19454460a8c1b010b89f6ff92d2987fd889ac3e8fc3b70d91a"
// TODO: change all Deployment functions to use these options
// in order to reduce complexity and have a unified API across the
// framework
type deploymentOptions struct {
name string
namespace string
image string
replicas int
svcAnnotations map[string]string
}
// WithDeploymentNamespace allows configuring the deployment's namespace
func WithDeploymentNamespace(n string) func(*deploymentOptions) {
return func(o *deploymentOptions) {
o.namespace = n
}
}
// WithSvcTopologyAnnotations create svc with topology mode sets to auto
func WithSvcTopologyAnnotations() func(*deploymentOptions) {
return func(o *deploymentOptions) {
o.svcAnnotations = map[string]string{
corev1.AnnotationTopologyMode: "auto",
}
}
}
// WithDeploymentName allows configuring the deployment's names
func WithDeploymentName(n string) func(*deploymentOptions) {
return func(o *deploymentOptions) {
o.name = n
}
}
// WithDeploymentReplicas allows configuring the deployment's replicas count
func WithDeploymentReplicas(r int) func(*deploymentOptions) {
return func(o *deploymentOptions) {
o.replicas = r
}
}
func WithName(n string) func(*deploymentOptions) {
return func(o *deploymentOptions) {
o.name = n
}
}
// WithImage allows configuring the image for the deployments
func WithImage(i string) func(*deploymentOptions) {
return func(o *deploymentOptions) {
o.image = i
}
}
// NewEchoDeployment creates a new single replica deployment of the echo server image in a particular namespace
func (f *Framework) NewEchoDeployment(opts ...func(*deploymentOptions)) {
options := &deploymentOptions{
namespace: f.Namespace,
name: EchoService,
replicas: 1,
image: EchoImage,
}
for _, o := range opts {
o(options)
}
f.EnsureDeployment(newDeployment(
options.name,
options.namespace,
options.image,
80,
int32(options.replicas),
2022-12-28 20:59:27 +00:00
nil, nil, nil,
[]corev1.VolumeMount{},
[]corev1.Volume{},
2022-12-28 20:59:27 +00:00
true,
))
f.EnsureService(&corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: options.name,
Namespace: options.namespace,
Annotations: options.svcAnnotations,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
TargetPort: intstr.FromInt(80),
Protocol: corev1.ProtocolTCP,
},
},
Selector: map[string]string{
"app": options.name,
},
},
})
err := WaitForEndpoints(
f.KubeClientSet,
DefaultTimeout,
options.name,
options.namespace,
options.replicas,
)
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
2018-07-19 13:37:28 +00:00
}
// BuildNipHost used to generate a nip host for DNS resolving
func BuildNIPHost(ip string) string {
return fmt.Sprintf("%s.nip.io", ip)
}
// GetNipHost used to generate a nip host for external DNS resolving
// for the instance deployed by the framework
func (f *Framework) GetNIPHost() string {
return BuildNIPHost(f.HTTPBunIP)
}
// BuildNIPExternalNameService used to generate a service pointing to nip.io to
// help resolve to an IP address
func BuildNIPExternalNameService(f *Framework, ip, portName string) *corev1.Service {
return &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: NIPService,
Namespace: f.Namespace,
},
Spec: corev1.ServiceSpec{
ExternalName: BuildNIPHost(ip),
Type: corev1.ServiceTypeExternalName,
Ports: []corev1.ServicePort{
{
Name: portName,
Port: 80,
TargetPort: intstr.FromInt(80),
Protocol: "TCP",
},
},
},
}
}
// NewHttpbunDeployment creates a new single replica deployment of the httpbun
// server image in a particular namespace we return the ip for testing purposes
func (f *Framework) NewHttpbunDeployment(opts ...func(*deploymentOptions)) string {
options := &deploymentOptions{
namespace: f.Namespace,
name: HTTPBunService,
replicas: 1,
image: HTTPBunImage,
}
for _, o := range opts {
o(options)
}
// Create the HTTPBun Deployment
f.EnsureDeployment(newDeployment(
options.name,
options.namespace,
options.image,
80,
int32(options.replicas),
nil, nil,
// Required to get hostname information
[]corev1.EnvVar{
{
Name: "HTTPBUN_INFO_ENABLED",
Value: "1",
},
},
[]corev1.VolumeMount{},
[]corev1.Volume{},
true,
))
// Create a service pointing to deployment
f.EnsureService(&corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: options.name,
Namespace: options.namespace,
Annotations: options.svcAnnotations,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
TargetPort: intstr.FromInt(80),
Protocol: corev1.ProtocolTCP,
},
},
Selector: map[string]string{
"app": options.name,
},
},
})
// Wait for deployment to become available
err := WaitForEndpoints(
f.KubeClientSet,
DefaultTimeout,
options.name,
options.namespace,
options.replicas,
)
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
// Get cluster ip for HTTPBun to be used in tests
e, err := f.KubeClientSet.
CoreV1().
Endpoints(f.Namespace).
Get(context.TODO(), HTTPBunService, metav1.GetOptions{})
assert.Nil(ginkgo.GinkgoT(), err, "failed to get httpbun endpoint")
return e.Subsets[0].Addresses[0].IP
}
// NewSlowEchoDeployment creates a new deployment of the slow echo server image in a particular namespace.
func (f *Framework) NewSlowEchoDeployment() {
2020-11-12 22:36:48 +00:00
cfg := `#
2020-05-31 03:25:56 +00:00
events {
worker_connections 1024;
multi_accept on;
}
2020-05-31 03:25:56 +00:00
http {
default_type 'text/plain';
client_max_body_size 0;
2020-05-31 03:25:56 +00:00
server {
access_log on;
access_log /dev/stdout;
2020-05-31 03:25:56 +00:00
listen 80;
location / {
content_by_lua_block {
ngx.print("ok")
}
}
2020-05-31 03:25:56 +00:00
location ~ ^/sleep/(?<sleepTime>[0-9]+)$ {
content_by_lua_block {
ngx.sleep(ngx.var.sleepTime)
ngx.print("ok after " .. ngx.var.sleepTime .. " seconds")
}
}
}
}
`
2020-11-12 22:36:48 +00:00
f.NGINXWithConfigDeployment(SlowEchoService, cfg)
}
func (f *Framework) GetNginxBaseImage() string {
nginxBaseImage := os.Getenv("NGINX_BASE_IMAGE")
if nginxBaseImage == "" {
assert.NotEmpty(ginkgo.GinkgoT(), errors.New("NGINX_BASE_IMAGE not defined"), "NGINX_BASE_IMAGE not defined")
}
return nginxBaseImage
}
// NGINXDeployment creates a new simple NGINX Deployment using NGINX base image
// and passing the desired configuration
func (f *Framework) NGINXDeployment(name, cfg string, waitendpoint bool) {
2020-11-12 22:36:48 +00:00
cfgMap := map[string]string{
"nginx.conf": cfg,
}
_, err := f.KubeClientSet.
CoreV1().
ConfigMaps(f.Namespace).
Create(context.TODO(), &corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: f.Namespace,
},
Data: cfgMap,
}, metav1.CreateOptions{})
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "creating configmap")
deployment := newDeployment(name, f.Namespace, f.GetNginxBaseImage(), 80, 1,
2022-12-28 20:59:27 +00:00
nil, nil, nil,
[]corev1.VolumeMount{
{
2020-11-12 22:36:48 +00:00
Name: name,
2020-05-31 03:25:56 +00:00
MountPath: "/etc/nginx/nginx.conf",
SubPath: "nginx.conf",
ReadOnly: true,
},
},
[]corev1.Volume{
{
2020-11-12 22:36:48 +00:00
Name: name,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
2020-11-12 22:36:48 +00:00
Name: name,
},
},
},
},
2022-12-28 20:59:27 +00:00
}, true,
)
2020-02-14 00:19:07 +00:00
f.EnsureDeployment(deployment)
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
2020-11-12 22:36:48 +00:00
Name: name,
Namespace: f.Namespace,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
TargetPort: intstr.FromInt(80),
Protocol: corev1.ProtocolTCP,
},
},
Selector: map[string]string{
2020-11-12 22:36:48 +00:00
"app": name,
},
},
}
2020-02-14 00:19:07 +00:00
f.EnsureService(service)
if waitendpoint {
err = WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, 1)
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
}
}
// NGINXWithConfigDeployment creates an NGINX deployment using a configmap containing the nginx.conf configuration
func (f *Framework) NGINXWithConfigDeployment(name, cfg string) {
f.NGINXDeployment(name, cfg, true)
2018-07-19 13:37:28 +00:00
}
2020-02-13 14:49:00 +00:00
// NewGRPCBinDeployment creates a new deployment of the
// moul/grpcbin image for GRPC tests
func (f *Framework) NewGRPCBinDeployment() {
name := "grpcbin"
probe := &corev1.Probe{
2020-02-19 03:08:14 +00:00
InitialDelaySeconds: 1,
PeriodSeconds: 1,
2020-02-14 00:19:07 +00:00
SuccessThreshold: 1,
2020-02-13 14:49:00 +00:00
TimeoutSeconds: 1,
ProbeHandler: corev1.ProbeHandler{
2020-02-13 14:49:00 +00:00
TCPSocket: &corev1.TCPSocketAction{
Port: intstr.FromInt(9000),
},
},
}
2020-02-14 00:19:07 +00:00
sel := map[string]string{
"app": name,
}
f.EnsureDeployment(&appsv1.Deployment{
2020-02-13 14:49:00 +00:00
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: f.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: NewInt32(1),
Selector: &metav1.LabelSelector{
2020-02-14 00:19:07 +00:00
MatchLabels: sel,
2020-02-13 14:49:00 +00:00
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
2020-02-14 00:19:07 +00:00
Labels: sel,
2020-02-13 14:49:00 +00:00
},
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: NewInt64(0),
Containers: []corev1.Container{
{
Name: name,
Image: "moul/grpcbin",
2020-02-14 00:19:07 +00:00
Env: []corev1.EnvVar{},
2020-02-13 14:49:00 +00:00
Ports: []corev1.ContainerPort{
{
Name: "insecure",
ContainerPort: 9000,
2020-02-13 19:39:20 +00:00
Protocol: corev1.ProtocolTCP,
2020-02-13 14:49:00 +00:00
},
{
Name: "secure",
ContainerPort: 9001,
2020-02-13 19:39:20 +00:00
Protocol: corev1.ProtocolTCP,
2020-02-13 14:49:00 +00:00
},
},
ReadinessProbe: probe,
LivenessProbe: probe,
},
},
},
},
},
2020-02-14 00:19:07 +00:00
})
2020-02-13 14:49:00 +00:00
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: f.Namespace,
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "insecure",
Port: 9000,
TargetPort: intstr.FromInt(9000),
Protocol: corev1.ProtocolTCP,
},
{
Name: "secure",
Port: 9001,
TargetPort: intstr.FromInt(9001),
2020-02-13 14:49:00 +00:00
Protocol: corev1.ProtocolTCP,
},
},
2020-02-14 00:19:07 +00:00
Selector: sel,
2020-02-13 14:49:00 +00:00
},
}
2020-02-14 00:19:07 +00:00
f.EnsureService(service)
2020-02-13 14:49:00 +00:00
err := WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, 1)
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
2020-02-13 14:49:00 +00:00
}
2022-12-28 20:59:27 +00:00
func newDeployment(name, namespace, image string, port int32, replicas int32, command []string, args []string, env []corev1.EnvVar,
volumeMounts []corev1.VolumeMount, volumes []corev1.Volume, setProbe bool,
) *appsv1.Deployment {
probe := &corev1.Probe{
2020-09-30 14:24:51 +00:00
InitialDelaySeconds: 2,
2020-02-19 03:08:14 +00:00
PeriodSeconds: 1,
SuccessThreshold: 1,
2020-09-30 14:24:51 +00:00
TimeoutSeconds: 2,
FailureThreshold: 6,
ProbeHandler: corev1.ProbeHandler{
HTTPGet: &corev1.HTTPGetAction{
Port: intstr.FromString("http"),
Path: "/",
},
},
}
d := &appsv1.Deployment{
2017-11-10 02:00:38 +00:00
ObjectMeta: metav1.ObjectMeta{
2018-07-19 13:37:28 +00:00
Name: name,
Namespace: namespace,
2017-11-10 02:00:38 +00:00
},
Spec: appsv1.DeploymentSpec{
Replicas: NewInt32(replicas),
2017-11-10 02:00:38 +00:00
Selector: &metav1.LabelSelector{
MatchLabels: map[string]string{
2018-07-19 13:37:28 +00:00
"app": name,
2017-11-10 02:00:38 +00:00
},
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
2018-07-19 13:37:28 +00:00
"app": name,
2017-11-10 02:00:38 +00:00
},
},
Spec: corev1.PodSpec{
TerminationGracePeriodSeconds: NewInt64(0),
2017-11-10 02:00:38 +00:00
Containers: []corev1.Container{
{
2018-07-19 13:37:28 +00:00
Name: name,
Image: image,
2017-11-10 02:00:38 +00:00
Env: []corev1.EnvVar{},
Ports: []corev1.ContainerPort{
{
Name: "http",
2018-07-19 13:37:28 +00:00
ContainerPort: port,
2017-11-10 02:00:38 +00:00
},
},
2022-12-28 20:59:27 +00:00
VolumeMounts: volumeMounts,
2017-11-10 02:00:38 +00:00
},
},
Volumes: volumes,
2017-11-10 02:00:38 +00:00
},
},
},
}
2022-12-28 20:59:27 +00:00
if setProbe {
d.Spec.Template.Spec.Containers[0].ReadinessProbe = probe
d.Spec.Template.Spec.Containers[0].LivenessProbe = probe
}
if len(command) > 0 {
d.Spec.Template.Spec.Containers[0].Command = command
}
2022-12-28 20:59:27 +00:00
if len(args) > 0 {
d.Spec.Template.Spec.Containers[0].Args = args
}
if len(env) > 0 {
d.Spec.Template.Spec.Containers[0].Env = env
}
return d
}
func (f *Framework) NewDeployment(name, image string, port, replicas int32) {
2022-12-28 20:59:27 +00:00
f.NewDeploymentWithOpts(name, image, port, replicas, nil, nil, nil, nil, nil, true)
}
// NewDeployment creates a new deployment in a particular namespace.
func (f *Framework) NewDeploymentWithOpts(name, image string, port, replicas int32, command, args []string, env []corev1.EnvVar, volumeMounts []corev1.VolumeMount, volumes []corev1.Volume, setProbe bool) {
2022-12-28 20:59:27 +00:00
deployment := newDeployment(name, f.Namespace, image, port, replicas, command, args, env, volumeMounts, volumes, setProbe)
2020-02-14 00:19:07 +00:00
f.EnsureDeployment(deployment)
2017-11-10 02:00:38 +00:00
service := &corev1.Service{
ObjectMeta: metav1.ObjectMeta{
2018-07-19 13:37:28 +00:00
Name: name,
Namespace: f.Namespace,
2017-11-10 02:00:38 +00:00
},
Spec: corev1.ServiceSpec{
Ports: []corev1.ServicePort{
{
Name: "http",
Port: 80,
2018-07-19 13:37:28 +00:00
TargetPort: intstr.FromInt(int(port)),
Protocol: corev1.ProtocolTCP,
2017-11-10 02:00:38 +00:00
},
},
Selector: map[string]string{
2018-07-19 13:37:28 +00:00
"app": name,
2017-11-10 02:00:38 +00:00
},
},
}
2020-02-14 00:19:07 +00:00
f.EnsureService(service)
2019-12-13 02:07:50 +00:00
err := WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, int(replicas))
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "waiting for endpoints to become ready")
2017-11-10 02:00:38 +00:00
}
// DeleteDeployment deletes a deployment with a particular name and waits for the pods to be deleted
func (f *Framework) DeleteDeployment(name string) error {
d, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Get(context.TODO(), name, metav1.GetOptions{})
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "getting deployment")
grace := int64(0)
err = f.KubeClientSet.AppsV1().Deployments(f.Namespace).Delete(context.TODO(), name, metav1.DeleteOptions{
GracePeriodSeconds: &grace,
})
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "deleting deployment")
return waitForPodsDeleted(f.KubeClientSet, 2*time.Minute, f.Namespace, &metav1.ListOptions{
LabelSelector: labelSelectorToString(d.Spec.Selector.MatchLabels),
})
}
// ScaleDeploymentToZero scales a deployment with a particular name and waits for the pods to be deleted
func (f *Framework) ScaleDeploymentToZero(name string) {
d, err := f.KubeClientSet.AppsV1().Deployments(f.Namespace).Get(context.TODO(), name, metav1.GetOptions{})
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "getting deployment")
assert.NotNil(ginkgo.GinkgoT(), d, "expected a deployment but none returned")
d.Spec.Replicas = NewInt32(0)
2020-02-14 00:19:07 +00:00
d, err = f.KubeClientSet.AppsV1().Deployments(f.Namespace).Update(context.TODO(), d, metav1.UpdateOptions{})
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "getting deployment")
assert.NotNil(ginkgo.GinkgoT(), d, "expected a deployment but none returned")
2020-01-05 14:15:38 +00:00
err = WaitForEndpoints(f.KubeClientSet, DefaultTimeout, name, f.Namespace, 0)
2020-02-19 03:08:14 +00:00
assert.Nil(ginkgo.GinkgoT(), err, "waiting for no endpoints")
}
2020-09-26 23:27:19 +00:00
// UpdateIngressControllerDeployment updates the ingress-nginx deployment
func (f *Framework) UpdateIngressControllerDeployment(fn func(deployment *appsv1.Deployment) error) error {
err := UpdateDeployment(f.KubeClientSet, f.Namespace, "nginx-ingress-controller", 1, fn)
if err != nil {
return err
}
return f.updateIngressNGINXPod()
}