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  }