github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/caas/kubernetes/provider/resources/pod_test.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resources_test 5 6 import ( 7 "context" 8 "testing" 9 "time" 10 11 "github.com/juju/errors" 12 jc "github.com/juju/testing/checkers" 13 gc "gopkg.in/check.v1" 14 corev1 "k8s.io/api/core/v1" 15 k8serrors "k8s.io/apimachinery/pkg/api/errors" 16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 17 18 "github.com/juju/juju/caas/kubernetes/provider/resources" 19 "github.com/juju/juju/core/status" 20 ) 21 22 type podSuite struct { 23 resourceSuite 24 } 25 26 var _ = gc.Suite(&podSuite{}) 27 28 func (s *podSuite) TestApply(c *gc.C) { 29 ds := &corev1.Pod{ 30 ObjectMeta: metav1.ObjectMeta{ 31 Name: "ds1", 32 Namespace: "test", 33 }, 34 } 35 // Create. 36 dsResource := resources.NewPod("ds1", "test", ds) 37 c.Assert(dsResource.Apply(context.TODO(), s.client), jc.ErrorIsNil) 38 result, err := s.client.CoreV1().Pods("test").Get(context.TODO(), "ds1", metav1.GetOptions{}) 39 c.Assert(err, jc.ErrorIsNil) 40 c.Assert(len(result.GetAnnotations()), gc.Equals, 0) 41 42 // Update. 43 ds.SetAnnotations(map[string]string{"a": "b"}) 44 dsResource = resources.NewPod("ds1", "test", ds) 45 c.Assert(dsResource.Apply(context.TODO(), s.client), jc.ErrorIsNil) 46 47 result, err = s.client.CoreV1().Pods("test").Get(context.TODO(), "ds1", metav1.GetOptions{}) 48 c.Assert(err, jc.ErrorIsNil) 49 c.Assert(result.GetName(), gc.Equals, `ds1`) 50 c.Assert(result.GetNamespace(), gc.Equals, `test`) 51 c.Assert(result.GetAnnotations(), gc.DeepEquals, map[string]string{"a": "b"}) 52 } 53 54 func (s *podSuite) TestGet(c *gc.C) { 55 template := corev1.Pod{ 56 ObjectMeta: metav1.ObjectMeta{ 57 Name: "ds1", 58 Namespace: "test", 59 }, 60 } 61 ds1 := template 62 ds1.SetAnnotations(map[string]string{"a": "b"}) 63 _, err := s.client.CoreV1().Pods("test").Create(context.TODO(), &ds1, metav1.CreateOptions{}) 64 c.Assert(err, jc.ErrorIsNil) 65 66 dsResource := resources.NewPod("ds1", "test", &template) 67 c.Assert(len(dsResource.GetAnnotations()), gc.Equals, 0) 68 err = dsResource.Get(context.TODO(), s.client) 69 c.Assert(err, jc.ErrorIsNil) 70 c.Assert(dsResource.GetName(), gc.Equals, `ds1`) 71 c.Assert(dsResource.GetNamespace(), gc.Equals, `test`) 72 c.Assert(dsResource.GetAnnotations(), gc.DeepEquals, map[string]string{"a": "b"}) 73 } 74 75 func (s *podSuite) TestDelete(c *gc.C) { 76 ds := corev1.Pod{ 77 ObjectMeta: metav1.ObjectMeta{ 78 Name: "ds1", 79 Namespace: "test", 80 }, 81 } 82 _, err := s.client.CoreV1().Pods("test").Create(context.TODO(), &ds, metav1.CreateOptions{}) 83 c.Assert(err, jc.ErrorIsNil) 84 85 result, err := s.client.CoreV1().Pods("test").Get(context.TODO(), "ds1", metav1.GetOptions{}) 86 c.Assert(err, jc.ErrorIsNil) 87 c.Assert(result.GetName(), gc.Equals, `ds1`) 88 89 dsResource := resources.NewPod("ds1", "test", &ds) 90 err = dsResource.Delete(context.TODO(), s.client) 91 c.Assert(err, jc.ErrorIsNil) 92 93 err = dsResource.Get(context.TODO(), s.client) 94 c.Assert(err, jc.Satisfies, errors.IsNotFound) 95 96 _, err = s.client.CoreV1().Pods("test").Get(context.TODO(), "ds1", metav1.GetOptions{}) 97 c.Assert(err, jc.Satisfies, k8serrors.IsNotFound) 98 } 99 100 func TestTerminatedPodJujuStatus(t *testing.T) { 101 pod := corev1.Pod{ 102 ObjectMeta: metav1.ObjectMeta{ 103 DeletionTimestamp: &metav1.Time{Time: time.Now()}, 104 }, 105 Status: corev1.PodStatus{ 106 Message: "test", 107 }, 108 } 109 110 testTime := time.Now() 111 message, poStatus, now, err := resources.PodToJujuStatus( 112 pod, 113 testTime, 114 func() ([]corev1.Event, error) { 115 return []corev1.Event{}, nil 116 }, 117 ) 118 119 if err != nil { 120 t.Fatalf("unexpected error getting pod status: %v", err) 121 } 122 123 if message != pod.Status.Message { 124 t.Errorf("pod status messages not equal %q != %q", message, pod.Status.Message) 125 } 126 127 if poStatus != status.Terminated { 128 t.Errorf("expected status %q got %q", status.Terminated, poStatus) 129 } 130 131 if !testTime.Equal(now) { 132 t.Errorf("unexpected status time received, got %q wanted %q", now, testTime) 133 } 134 } 135 136 func TestPodConditionListJujuStatus(t *testing.T) { 137 tests := []struct { 138 Name string 139 Pod corev1.Pod 140 Status status.Status 141 Message string 142 }{ 143 { 144 // We are testing the juju status here when a pod is considered 145 // unschedulable. We want to see a juju status of blocked and 146 // the scheduling message echoed as this will provide the best 147 // reason. 148 Name: "pod scheduling status unschedulable", 149 Pod: corev1.Pod{ 150 Status: corev1.PodStatus{ 151 Conditions: []corev1.PodCondition{ 152 { 153 Type: corev1.PodScheduled, 154 Status: corev1.ConditionFalse, 155 Reason: corev1.PodReasonUnschedulable, 156 Message: "not enough resources", 157 }, 158 }, 159 }, 160 }, 161 Status: status.Blocked, 162 Message: "not enough resources", 163 }, 164 { 165 // We are testing the juju status here when pod scheduling is still 166 // occurring. Kubernetes is still organising where to put our pod 167 // so we expect a juju status of allocating and the pod scheduling 168 // message to be echoed. 169 Name: "pod scheduling status waiting", 170 Pod: corev1.Pod{ 171 Status: corev1.PodStatus{ 172 Conditions: []corev1.PodCondition{ 173 { 174 Type: corev1.PodScheduled, 175 Status: corev1.ConditionFalse, 176 Reason: "waiting", 177 Message: "waiting to be scheduled", 178 }, 179 }, 180 }, 181 }, 182 Status: status.Allocating, 183 Message: "waiting to be scheduled", 184 }, 185 { 186 // We expect that every pod has a pod scheduling condition. If it's 187 // missing our juju status should be Unknown as it's not safe for 188 // us to test anymore of the pod conditions. 189 Name: "pod scheduling status missing", 190 Pod: corev1.Pod{ 191 Status: corev1.PodStatus{ 192 Conditions: []corev1.PodCondition{}, 193 }, 194 }, 195 Status: status.Unknown, 196 Message: "", 197 }, 198 { 199 // We are testing here that when the pod is in it's init stage 200 // the correct juju status of maintenance is being reported. 201 Name: "pod init status waiting", 202 Pod: corev1.Pod{ 203 Status: corev1.PodStatus{ 204 Conditions: []corev1.PodCondition{ 205 { 206 Type: corev1.PodScheduled, 207 Status: corev1.ConditionTrue, 208 }, 209 { 210 Type: corev1.PodInitialized, 211 Status: corev1.ConditionFalse, 212 Reason: resources.PodReasonContainersNotInitialized, 213 Message: "initializing containers", 214 }, 215 }, 216 }, 217 }, 218 Status: status.Maintenance, 219 Message: "initializing containers", 220 }, 221 { 222 // We are testing here that when the pod is running the init steps 223 // the correct status of maintenance is being reported. 224 Name: "pod init status running", 225 Pod: corev1.Pod{ 226 Status: corev1.PodStatus{ 227 Conditions: []corev1.PodCondition{ 228 { 229 Type: corev1.PodScheduled, 230 Status: corev1.ConditionTrue, 231 }, 232 { 233 Type: corev1.PodInitialized, 234 Status: corev1.ConditionFalse, 235 Reason: resources.PodReasonInitializing, 236 Message: "initializing containers", 237 }, 238 }, 239 }, 240 }, 241 Status: status.Maintenance, 242 Message: "initializing containers", 243 }, 244 { 245 // We are testing here that when the pod is in it's init stage 246 // the correct juju status of error is displayed as one of the 247 // pods is in a crash loop backoff. 248 Name: "pod init status error", 249 Pod: corev1.Pod{ 250 Status: corev1.PodStatus{ 251 Conditions: []corev1.PodCondition{ 252 { 253 Type: corev1.PodScheduled, 254 Status: corev1.ConditionTrue, 255 }, 256 { 257 Type: corev1.PodInitialized, 258 Status: corev1.ConditionFalse, 259 Reason: resources.PodReasonContainersNotInitialized, 260 Message: "initializing containers", 261 }, 262 }, 263 InitContainerStatuses: []corev1.ContainerStatus{ 264 { 265 Name: "test-init-container", 266 State: corev1.ContainerState{ 267 Waiting: &corev1.ContainerStateWaiting{ 268 Reason: resources.PodReasonCrashLoopBackoff, 269 Message: "I am broken", 270 }, 271 }, 272 }, 273 }, 274 }, 275 }, 276 Status: status.Error, 277 Message: "crash loop backoff: I am broken", 278 }, 279 { 280 // We are testing here that while the main containers of the pod 281 // are still being spun up and the juju status of maintenance is 282 // still being reported 283 Name: "pod container status waiting", 284 Pod: corev1.Pod{ 285 Status: corev1.PodStatus{ 286 Conditions: []corev1.PodCondition{ 287 { 288 Type: corev1.PodScheduled, 289 Status: corev1.ConditionTrue, 290 }, 291 { 292 Type: corev1.ContainersReady, 293 Status: corev1.ConditionFalse, 294 Reason: resources.PodReasonContainersNotReady, 295 Message: "starting containers", 296 }, 297 }, 298 }, 299 }, 300 Status: status.Maintenance, 301 Message: "starting containers", 302 }, 303 { 304 // We want to test here that when a container in the pod goes into 305 // an error state like a crash loop backoff the subsequent juju 306 // status is error 307 Name: "pod container status error", 308 Pod: corev1.Pod{ 309 Status: corev1.PodStatus{ 310 Conditions: []corev1.PodCondition{ 311 { 312 Type: corev1.PodScheduled, 313 Status: corev1.ConditionTrue, 314 }, 315 { 316 Type: corev1.ContainersReady, 317 Status: corev1.ConditionFalse, 318 Reason: resources.PodReasonContainersNotReady, 319 Message: "starting containers", 320 }, 321 }, 322 ContainerStatuses: []corev1.ContainerStatus{ 323 { 324 Name: "test-container", 325 State: corev1.ContainerState{ 326 Waiting: &corev1.ContainerStateWaiting{ 327 Reason: resources.PodReasonCrashLoopBackoff, 328 Message: "I am broken", 329 }, 330 }, 331 }, 332 }, 333 }, 334 }, 335 Status: status.Error, 336 Message: "crash loop backoff: I am broken", 337 }, 338 { 339 // We want to test here the pod container creating message for init 340 // containers. This addresses lp-1914088 341 Name: "pod container status creating init", 342 Pod: corev1.Pod{ 343 Status: corev1.PodStatus{ 344 Conditions: []corev1.PodCondition{ 345 { 346 Type: corev1.PodScheduled, 347 Status: corev1.ConditionTrue, 348 }, 349 { 350 Type: corev1.PodInitialized, 351 Status: corev1.ConditionFalse, 352 Reason: resources.PodReasonContainersNotInitialized, 353 Message: "initializing containers", 354 }, 355 }, 356 InitContainerStatuses: []corev1.ContainerStatus{ 357 { 358 Name: "test-container", 359 State: corev1.ContainerState{ 360 Waiting: &corev1.ContainerStateWaiting{ 361 Reason: resources.PodReasonContainerCreating, 362 }, 363 }, 364 }, 365 }, 366 }, 367 }, 368 Status: status.Maintenance, 369 Message: "initializing containers", 370 }, 371 { 372 // We want to test here the pod container creating message on pod 373 // containers. This addresses lp-1914088 374 Name: "pod container status creating", 375 Pod: corev1.Pod{ 376 Status: corev1.PodStatus{ 377 Conditions: []corev1.PodCondition{ 378 { 379 Type: corev1.PodScheduled, 380 Status: corev1.ConditionTrue, 381 }, 382 { 383 Type: corev1.ContainersReady, 384 Status: corev1.ConditionFalse, 385 Reason: resources.PodReasonContainersNotReady, 386 Message: "creating containers", 387 }, 388 }, 389 ContainerStatuses: []corev1.ContainerStatus{ 390 { 391 Name: "test-container", 392 State: corev1.ContainerState{ 393 Waiting: &corev1.ContainerStateWaiting{ 394 Reason: resources.PodReasonContainerCreating, 395 }, 396 }, 397 }, 398 }, 399 }, 400 }, 401 Status: status.Maintenance, 402 Message: "creating containers", 403 }, 404 { 405 // We want to test that when the container reason is unknown we 406 // report Juju status of error and propagate the message 407 Name: "pod container unknown reason", 408 Pod: corev1.Pod{ 409 Status: corev1.PodStatus{ 410 Conditions: []corev1.PodCondition{ 411 { 412 Type: corev1.PodScheduled, 413 Status: corev1.ConditionTrue, 414 }, 415 { 416 Type: corev1.ContainersReady, 417 Status: corev1.ConditionFalse, 418 Reason: resources.PodReasonContainersNotReady, 419 Message: "starting containers", 420 }, 421 }, 422 ContainerStatuses: []corev1.ContainerStatus{ 423 { 424 Name: "test-container", 425 State: corev1.ContainerState{ 426 Waiting: &corev1.ContainerStateWaiting{ 427 Reason: "bad-reason", 428 Message: "I am broken", 429 }, 430 }, 431 }, 432 }, 433 }, 434 }, 435 Status: status.Error, 436 Message: "unknown container reason \"bad-reason\": I am broken", 437 }, 438 { 439 // We want to test here that when the pod ready condition the juju 440 // status is running 441 Name: "pod container status running", 442 Pod: corev1.Pod{ 443 Status: corev1.PodStatus{ 444 Conditions: []corev1.PodCondition{ 445 { 446 Type: corev1.PodScheduled, 447 Status: corev1.ConditionTrue, 448 }, 449 { 450 Type: corev1.ContainersReady, 451 Status: corev1.ConditionTrue, 452 }, 453 { 454 Type: corev1.PodReady, 455 Status: corev1.ConditionTrue, 456 }, 457 }, 458 }, 459 }, 460 Status: status.Running, 461 Message: "", 462 }, 463 } 464 465 for _, test := range tests { 466 t.Run(test.Name, func(t *testing.T) { 467 testTime := time.Now() 468 eventGetter := func() ([]corev1.Event, error) { 469 return []corev1.Event{}, nil 470 } 471 message, poStatus, _, err := resources.PodToJujuStatus( 472 test.Pod, testTime, eventGetter) 473 474 if err != nil { 475 t.Fatalf("unexpected error getting pod status: %v", err) 476 } 477 478 if message != test.Message { 479 t.Errorf("pod status messages not equal %q != %q", message, test.Message) 480 } 481 482 if poStatus != test.Status { 483 t.Errorf("expected status %q got %q", test.Status, poStatus) 484 } 485 }) 486 } 487 }