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 }