github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/kubernetes/debugging/transform_test.go (about)

     1  /*
     2  Copyright 2021 The Skaffold 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 debugging
    18  
    19  import (
    20  	"reflect"
    21  	"testing"
    22  	"time"
    23  
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	batchv1 "k8s.io/api/batch/v1"
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/util/intstr"
    30  
    31  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    32  	"github.com/GoogleContainerTools/skaffold/testutil"
    33  )
    34  
    35  func TestAllocatePort(t *testing.T) {
    36  	// helper function to create a container
    37  	containerWithPorts := func(ports ...int32) v1.Container {
    38  		var created []v1.ContainerPort
    39  		for _, port := range ports {
    40  			created = append(created, v1.ContainerPort{ContainerPort: port})
    41  		}
    42  		return v1.Container{Ports: created}
    43  	}
    44  
    45  	tests := []struct {
    46  		description string
    47  		pod         v1.PodSpec
    48  		desiredPort int32
    49  		result      int32
    50  	}{
    51  		{
    52  			description: "simple",
    53  			pod:         v1.PodSpec{},
    54  			desiredPort: 5005,
    55  			result:      5005,
    56  		},
    57  		{
    58  			description: "finds next available port",
    59  			pod: v1.PodSpec{Containers: []v1.Container{
    60  				containerWithPorts(5005, 5007),
    61  				containerWithPorts(5008, 5006),
    62  			}},
    63  			desiredPort: 5005,
    64  			result:      5009,
    65  		},
    66  		{
    67  			description: "skips reserved",
    68  			pod:         v1.PodSpec{},
    69  			desiredPort: 1,
    70  			result:      1024,
    71  		},
    72  		{
    73  			description: "skips 0",
    74  			pod:         v1.PodSpec{},
    75  			desiredPort: 0,
    76  			result:      1024,
    77  		},
    78  		{
    79  			description: "skips negative",
    80  			pod:         v1.PodSpec{},
    81  			desiredPort: -1,
    82  			result:      1024,
    83  		},
    84  		{
    85  			description: "wraps at maxPort",
    86  			pod:         v1.PodSpec{},
    87  			desiredPort: 65536,
    88  			result:      1024,
    89  		},
    90  		{
    91  			description: "wraps beyond maxPort",
    92  			pod:         v1.PodSpec{},
    93  			desiredPort: 65537,
    94  			result:      1024,
    95  		},
    96  		{
    97  			description: "looks backwards at 65535",
    98  			pod: v1.PodSpec{Containers: []v1.Container{
    99  				containerWithPorts(65535),
   100  			}},
   101  			desiredPort: 65535,
   102  			result:      65534,
   103  		},
   104  	}
   105  	for _, test := range tests {
   106  		testutil.Run(t, test.description, func(t *testutil.T) {
   107  			portAvailable := func(port int32) bool {
   108  				return isPortAvailable(&test.pod, port)
   109  			}
   110  			result := util.AllocatePort(portAvailable, test.desiredPort)
   111  
   112  			t.CheckDeepEqual(test.result, result)
   113  		})
   114  	}
   115  }
   116  
   117  func TestDescribe(t *testing.T) {
   118  	tests := []struct {
   119  		in     runtime.Object
   120  		result string
   121  	}{
   122  		{&v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "Pod"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "pod/name"},
   123  		{&v1.ReplicationController{TypeMeta: metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.String(), Kind: "ReplicationController"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "replicationcontroller/name"},
   124  		{&appsv1.Deployment{TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "Deployment"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "deployment.apps/name"},
   125  		{&appsv1.ReplicaSet{TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "ReplicaSet"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "replicaset.apps/name"},
   126  		{&appsv1.StatefulSet{TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "StatefulSet"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "statefulset.apps/name"},
   127  		{&appsv1.DaemonSet{TypeMeta: metav1.TypeMeta{APIVersion: appsv1.SchemeGroupVersion.String(), Kind: "DaemonSet"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "daemonset.apps/name"},
   128  		{&batchv1.Job{TypeMeta: metav1.TypeMeta{APIVersion: batchv1.SchemeGroupVersion.String(), Kind: "Job"}, ObjectMeta: metav1.ObjectMeta{Name: "name"}}, "job.batch/name"},
   129  	}
   130  	for _, test := range tests {
   131  		testutil.Run(t, reflect.TypeOf(test.in).Name(), func(t *testutil.T) {
   132  			gvk := test.in.GetObjectKind().GroupVersionKind()
   133  			group, version, kind, description := Describe(test.in)
   134  
   135  			t.CheckDeepEqual(gvk.Group, group)
   136  			t.CheckDeepEqual(gvk.Kind, kind)
   137  			t.CheckDeepEqual(gvk.Version, version)
   138  			t.CheckDeepEqual(test.result, description)
   139  		})
   140  	}
   141  }
   142  
   143  func TestRewriteHTTPGetProbe(t *testing.T) {
   144  	tests := []struct {
   145  		description string
   146  		input       v1.Probe
   147  		minTimeout  time.Duration
   148  		changed     bool
   149  		expected    v1.Probe
   150  	}{
   151  		{
   152  			description: "non-http probe should be skipped",
   153  			input:       v1.Probe{ProbeHandler: v1.ProbeHandler{Exec: &v1.ExecAction{Command: []string{"echo"}}}, TimeoutSeconds: 10},
   154  			minTimeout:  20 * time.Second,
   155  			changed:     false,
   156  		},
   157  		{
   158  			description: "http probe with big timeout should be skipped",
   159  			input:       v1.Probe{ProbeHandler: v1.ProbeHandler{Exec: &v1.ExecAction{Command: []string{"echo"}}}, TimeoutSeconds: 100 * 60},
   160  			minTimeout:  20 * time.Second,
   161  			changed:     false,
   162  		},
   163  		{
   164  			description: "http probe with no timeout",
   165  			input:       v1.Probe{ProbeHandler: v1.ProbeHandler{Exec: &v1.ExecAction{Command: []string{"echo"}}}},
   166  			minTimeout:  20 * time.Second,
   167  			changed:     true,
   168  			expected:    v1.Probe{ProbeHandler: v1.ProbeHandler{Exec: &v1.ExecAction{Command: []string{"echo"}}}, TimeoutSeconds: 20},
   169  		},
   170  		{
   171  			description: "http probe with small timeout",
   172  			input:       v1.Probe{ProbeHandler: v1.ProbeHandler{Exec: &v1.ExecAction{Command: []string{"echo"}}}, TimeoutSeconds: 60},
   173  			minTimeout:  100 * time.Second,
   174  			changed:     true,
   175  			expected:    v1.Probe{ProbeHandler: v1.ProbeHandler{Exec: &v1.ExecAction{Command: []string{"echo"}}}, TimeoutSeconds: 100},
   176  		},
   177  	}
   178  	for _, test := range tests {
   179  		testutil.Run(t, test.description, func(t *testutil.T) {
   180  			p := test.input
   181  			if rewriteHTTPGetProbe(&p, test.minTimeout) {
   182  				t.CheckDeepEqual(test.expected, p)
   183  			} else {
   184  				t.CheckDeepEqual(test.input, p) // should not have changed
   185  			}
   186  		})
   187  	}
   188  }
   189  
   190  // TestRewriteProbes verifies that rewriteProbes skips podspecs that have a
   191  // `debug.cloud.google.com/config` annotation.
   192  func TestRewriteProbes(t *testing.T) {
   193  	tests := []struct {
   194  		name    string
   195  		input   v1.Pod
   196  		changed bool
   197  		result  v1.Pod
   198  	}{
   199  		{
   200  			name: "skips pod missing debug annotation",
   201  			input: v1.Pod{
   202  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   203  				ObjectMeta: metav1.ObjectMeta{Name: "podname"},
   204  				Spec: v1.PodSpec{Containers: []v1.Container{{
   205  					Name:          "name1",
   206  					Image:         "image1",
   207  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 1}}}}},
   208  			changed: false,
   209  		},
   210  		{
   211  			name: "processes pod with debug annotation and uses default timeout",
   212  			input: v1.Pod{
   213  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   214  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/config": `{"name1":{"runtime":"test"}}`}},
   215  				Spec: v1.PodSpec{Containers: []v1.Container{{
   216  					Name:          "name1",
   217  					Image:         "image1",
   218  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 1}}}}},
   219  			changed: true,
   220  			result: v1.Pod{
   221  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   222  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/config": `{"name1":{"runtime":"test"}}`}},
   223  				Spec: v1.PodSpec{Containers: []v1.Container{{
   224  					Name:          "name1",
   225  					Image:         "image1",
   226  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 600}}}}},
   227  		},
   228  		{
   229  			name: "skips pod with skip-probes annotation",
   230  			input: v1.Pod{
   231  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   232  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/probe/timeouts": `skip`}},
   233  				Spec: v1.PodSpec{Containers: []v1.Container{{
   234  					Name:          "name1",
   235  					Image:         "image1",
   236  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 1}}}}},
   237  			changed: false,
   238  		},
   239  		{
   240  			name: "processes pod with probes annotation with explicit timeout",
   241  			input: v1.Pod{
   242  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   243  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/probe/timeouts": `1m`}},
   244  				Spec: v1.PodSpec{Containers: []v1.Container{{
   245  					Name:          "name1",
   246  					Image:         "image1",
   247  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 1}}}}},
   248  			changed: false,
   249  			result: v1.Pod{
   250  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   251  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/probe/timeouts": `1m`}},
   252  				Spec: v1.PodSpec{Containers: []v1.Container{{
   253  					Name:          "name1",
   254  					Image:         "image1",
   255  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 60}}}}},
   256  		},
   257  		{
   258  			name: "processes pod with probes annotation with invalid timeout",
   259  			input: v1.Pod{
   260  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   261  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/probe/timeouts": `on`}},
   262  				Spec: v1.PodSpec{Containers: []v1.Container{{
   263  					Name:          "name1",
   264  					Image:         "image1",
   265  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 1}}}}},
   266  			changed: false,
   267  			result: v1.Pod{
   268  				TypeMeta:   metav1.TypeMeta{APIVersion: v1.SchemeGroupVersion.Version, Kind: "Pod"},
   269  				ObjectMeta: metav1.ObjectMeta{Name: "podname", Annotations: map[string]string{"debug.cloud.google.com/probe/timeouts": `on`}},
   270  				Spec: v1.PodSpec{Containers: []v1.Container{{
   271  					Name:          "name1",
   272  					Image:         "image1",
   273  					LivenessProbe: &v1.Probe{ProbeHandler: v1.ProbeHandler{HTTPGet: &v1.HTTPGetAction{Path: "/", Port: intstr.FromInt(8080)}}, TimeoutSeconds: 600}}}}},
   274  		},
   275  	}
   276  	for _, test := range tests {
   277  		testutil.Run(t, test.name, func(t *testutil.T) {
   278  			pod := test.input
   279  			result := rewriteProbes(&pod.ObjectMeta, &pod.Spec)
   280  			t.CheckDeepEqual(test.changed, result)
   281  			if test.changed {
   282  				t.CheckDeepEqual(test.result, pod)
   283  			} else {
   284  				t.CheckDeepEqual(test.input, pod)
   285  			}
   286  		})
   287  	}
   288  }