...

Source file src/k8s.io/kubectl/pkg/polymorphichelpers/history_test.go

Documentation: k8s.io/kubectl/pkg/polymorphichelpers

     1  /*
     2  Copyright 2017 The Kubernetes 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 polymorphichelpers
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"reflect"
    23  	"testing"
    24  
    25  	appsv1 "k8s.io/api/apps/v1"
    26  	corev1 "k8s.io/api/core/v1"
    27  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  	"k8s.io/apimachinery/pkg/types"
    32  	"k8s.io/apimachinery/pkg/util/json"
    33  	"k8s.io/client-go/kubernetes/fake"
    34  )
    35  
    36  var historytests = map[schema.GroupKind]reflect.Type{
    37  	{Group: "apps", Kind: "DaemonSet"}:   reflect.TypeOf(&DaemonSetHistoryViewer{}),
    38  	{Group: "apps", Kind: "StatefulSet"}: reflect.TypeOf(&StatefulSetHistoryViewer{}),
    39  	{Group: "apps", Kind: "Deployment"}:  reflect.TypeOf(&DeploymentHistoryViewer{}),
    40  }
    41  
    42  func TestHistoryViewerFor(t *testing.T) {
    43  	fakeClientset := &fake.Clientset{}
    44  
    45  	for kind, expectedType := range historytests {
    46  		result, err := HistoryViewerFor(kind, fakeClientset)
    47  		if err != nil {
    48  			t.Fatalf("error getting HistoryViewer for a %v: %v", kind.String(), err)
    49  		}
    50  
    51  		if reflect.TypeOf(result) != expectedType {
    52  			t.Fatalf("unexpected output type (%v was expected but got %v)", expectedType, reflect.TypeOf(result))
    53  		}
    54  	}
    55  }
    56  
    57  func TestViewDeploymentHistory(t *testing.T) {
    58  	trueVar := true
    59  	replicas := int32(1)
    60  
    61  	deployment := &appsv1.Deployment{
    62  		ObjectMeta: metav1.ObjectMeta{
    63  			Name:      "moons",
    64  			Namespace: "default",
    65  			UID:       "fc7e66ad-eacc-4413-8277-e22276eacce6",
    66  			Labels:    map[string]string{"foo": "bar"},
    67  		},
    68  		Spec: appsv1.DeploymentSpec{
    69  			Selector: &metav1.LabelSelector{
    70  				MatchLabels: map[string]string{"foo": "bar"},
    71  			},
    72  			Replicas: &replicas,
    73  			Template: corev1.PodTemplateSpec{
    74  				ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
    75  				Spec: corev1.PodSpec{
    76  					Containers: []corev1.Container{{
    77  						Name:  "test",
    78  						Image: fmt.Sprintf("foo:1"),
    79  					}}},
    80  			},
    81  		},
    82  	}
    83  
    84  	fakeClientSet := fake.NewSimpleClientset(deployment)
    85  
    86  	replicaSets := map[int64]*appsv1.ReplicaSet{}
    87  	var i int64
    88  	for i = 1; i < 5; i++ {
    89  		rs := &appsv1.ReplicaSet{
    90  			ObjectMeta: metav1.ObjectMeta{
    91  				Name:            fmt.Sprintf("moons-%d", i),
    92  				Namespace:       "default",
    93  				UID:             types.UID(fmt.Sprintf("00000000-0000-0000-0000-00000000000%d", i)),
    94  				Labels:          map[string]string{"foo": "bar"},
    95  				OwnerReferences: []metav1.OwnerReference{{APIVersion: "apps/v1", Kind: "Deployment", Name: deployment.Name, UID: deployment.UID, Controller: &trueVar}},
    96  				Annotations: map[string]string{
    97  					"deployment.kubernetes.io/revision": fmt.Sprintf("%d", i),
    98  				},
    99  			},
   100  			Spec: appsv1.ReplicaSetSpec{
   101  				Selector: &metav1.LabelSelector{
   102  					MatchLabels: map[string]string{"foo": "bar"},
   103  				},
   104  				Replicas: &replicas,
   105  				Template: corev1.PodTemplateSpec{
   106  					ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
   107  					Spec: corev1.PodSpec{
   108  						Containers: []corev1.Container{{
   109  							Name:  "test",
   110  							Image: fmt.Sprintf("foo:%d", i),
   111  						}}},
   112  				},
   113  			},
   114  		}
   115  
   116  		if i == 3 {
   117  			rs.ObjectMeta.Annotations[ChangeCauseAnnotation] = "foo change cause"
   118  		} else if i == 4 {
   119  			rs.ObjectMeta.Annotations[ChangeCauseAnnotation] = "bar change cause"
   120  		}
   121  
   122  		fakeClientSet.AppsV1().ReplicaSets("default").Create(context.TODO(), rs, metav1.CreateOptions{})
   123  		replicaSets[i] = rs
   124  	}
   125  
   126  	viewer := DeploymentHistoryViewer{fakeClientSet}
   127  
   128  	t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
   129  		result, err := viewer.ViewHistory("default", "moons", 0)
   130  		if err != nil {
   131  			t.Fatalf("error getting history for Deployment moons: %v", err)
   132  		}
   133  
   134  		expected := `REVISION  CHANGE-CAUSE
   135  1         <none>
   136  2         <none>
   137  3         foo change cause
   138  4         bar change cause
   139  `
   140  		if result != expected {
   141  			t.Fatalf("unexpected output  (%v was expected but got %v)", expected, result)
   142  		}
   143  	})
   144  
   145  	t.Run("should describe a single revision", func(t *testing.T) {
   146  		result, err := viewer.ViewHistory("default", "moons", 3)
   147  		if err != nil {
   148  			t.Fatalf("error getting history for Deployment moons: %v", err)
   149  		}
   150  
   151  		expected := `Pod Template:
   152    Labels:	foo=bar
   153    Annotations:	kubernetes.io/change-cause: foo change cause
   154    Containers:
   155     test:
   156      Image:	foo:3
   157      Port:	<none>
   158      Host Port:	<none>
   159      Environment:	<none>
   160      Mounts:	<none>
   161    Volumes:	<none>
   162    Node-Selectors:	<none>
   163    Tolerations:	<none>
   164  `
   165  		if result != expected {
   166  			t.Fatalf("unexpected output  (%v was expected but got %v)", expected, result)
   167  		}
   168  	})
   169  
   170  	t.Run("should get history", func(t *testing.T) {
   171  		result, err := viewer.GetHistory("default", "moons")
   172  		if err != nil {
   173  			t.Fatalf("error getting history for Deployment moons: %v", err)
   174  		}
   175  
   176  		if len(result) != 4 {
   177  			t.Fatalf("unexpected history length (expected 4, got %d", len(result))
   178  		}
   179  
   180  		for i = 1; i < 4; i++ {
   181  			actual, found := result[i]
   182  			if !found {
   183  				t.Fatalf("revision %d not found in history", i)
   184  			}
   185  			expected := replicaSets[i]
   186  			if !reflect.DeepEqual(expected, actual) {
   187  				t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
   188  			}
   189  		}
   190  	})
   191  }
   192  
   193  func TestViewHistory(t *testing.T) {
   194  
   195  	t.Run("for statefulSet", func(t *testing.T) {
   196  		var (
   197  			trueVar  = true
   198  			replicas = int32(1)
   199  
   200  			podStub = corev1.PodTemplateSpec{
   201  				ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
   202  				Spec:       corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}},
   203  			}
   204  
   205  			ssStub = &appsv1.StatefulSet{
   206  				ObjectMeta: metav1.ObjectMeta{
   207  					Name:      "moons",
   208  					Namespace: "default",
   209  					UID:       "1993",
   210  					Labels:    map[string]string{"foo": "bar"},
   211  				},
   212  				Spec: appsv1.StatefulSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Replicas: &replicas, Template: podStub},
   213  			}
   214  		)
   215  		stsRawData, err := json.Marshal(ssStub)
   216  		if err != nil {
   217  			t.Fatalf("error creating sts raw data: %v", err)
   218  		}
   219  		ssStub1 := &appsv1.ControllerRevision{
   220  			ObjectMeta: metav1.ObjectMeta{
   221  				Name:            "moons",
   222  				Namespace:       "default",
   223  				Labels:          map[string]string{"foo": "bar"},
   224  				OwnerReferences: []metav1.OwnerReference{{APIVersion: "apps/v1", Kind: "StatefulSet", Name: "moons", UID: "1993", Controller: &trueVar}},
   225  			},
   226  			Data:     runtime.RawExtension{Raw: stsRawData},
   227  			TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"},
   228  			Revision: 1,
   229  		}
   230  
   231  		fakeClientSet := fake.NewSimpleClientset(ssStub)
   232  		_, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), ssStub1, metav1.CreateOptions{})
   233  		if err != nil {
   234  			t.Fatalf("create controllerRevisions error %v occurred ", err)
   235  		}
   236  
   237  		var sts = &StatefulSetHistoryViewer{
   238  			fakeClientSet,
   239  		}
   240  
   241  		t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
   242  			result, err := sts.ViewHistory("default", "moons", 0)
   243  			if err != nil {
   244  				t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
   245  			}
   246  
   247  			expected := `REVISION  CHANGE-CAUSE
   248  1         <none>
   249  `
   250  
   251  			if result != expected {
   252  				t.Fatalf("unexpected output  (%v was expected but got %v)", expected, result)
   253  			}
   254  		})
   255  
   256  		t.Run("should describe the revision if revision is specified", func(t *testing.T) {
   257  			result, err := sts.ViewHistory("default", "moons", 1)
   258  			if err != nil {
   259  				t.Fatalf("error getting ViewHistory for a StatefulSets moons: %v", err)
   260  			}
   261  
   262  			expected := `Pod Template:
   263    Labels:	foo=bar
   264    Containers:
   265     test:
   266      Image:	nginx
   267      Port:	<none>
   268      Host Port:	<none>
   269      Environment:	<none>
   270      Mounts:	<none>
   271    Volumes:	<none>
   272    Node-Selectors:	<none>
   273    Tolerations:	<none>
   274  `
   275  
   276  			if result != expected {
   277  				t.Fatalf("unexpected output  (%v was expected but got %v)", expected, result)
   278  			}
   279  		})
   280  
   281  		t.Run("should get history", func(t *testing.T) {
   282  			result, err := sts.GetHistory("default", "moons")
   283  			if err != nil {
   284  				t.Fatalf("error getting history for StatefulSet moons: %v", err)
   285  			}
   286  
   287  			if len(result) != 1 {
   288  				t.Fatalf("unexpected history length (expected 1, got %d", len(result))
   289  			}
   290  
   291  			actual, found := result[1]
   292  			if !found {
   293  				t.Fatalf("revision 1 not found in history")
   294  			}
   295  			expected := ssStub
   296  			if !reflect.DeepEqual(expected, actual) {
   297  				t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
   298  			}
   299  		})
   300  	})
   301  
   302  	t.Run("for daemonSet", func(t *testing.T) {
   303  		var (
   304  			trueVar = true
   305  			podStub = corev1.PodTemplateSpec{
   306  				ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}},
   307  				Spec:       corev1.PodSpec{Containers: []corev1.Container{{Name: "test", Image: "nginx"}}},
   308  			}
   309  
   310  			daemonSetStub = &appsv1.DaemonSet{
   311  				ObjectMeta: metav1.ObjectMeta{
   312  					Name:      "moons",
   313  					Namespace: "default",
   314  					UID:       "1993",
   315  					Labels:    map[string]string{"foo": "bar"},
   316  				},
   317  				Spec: appsv1.DaemonSetSpec{Selector: &metav1.LabelSelector{MatchLabels: podStub.ObjectMeta.Labels}, Template: podStub},
   318  			}
   319  		)
   320  
   321  		daemonSetRaw, err := json.Marshal(daemonSetStub)
   322  		if err != nil {
   323  			t.Fatalf("error creating sts raw data: %v", err)
   324  		}
   325  		daemonSetControllerRevision := &appsv1.ControllerRevision{
   326  			ObjectMeta: metav1.ObjectMeta{
   327  				Name:            "moons",
   328  				Namespace:       "default",
   329  				Labels:          map[string]string{"foo": "bar"},
   330  				OwnerReferences: []metav1.OwnerReference{{APIVersion: "apps/v1", Kind: "DaemonSet", Name: "moons", UID: "1993", Controller: &trueVar}},
   331  			},
   332  			Data:     runtime.RawExtension{Raw: daemonSetRaw},
   333  			TypeMeta: metav1.TypeMeta{Kind: "StatefulSet", APIVersion: "apps/v1"},
   334  			Revision: 1,
   335  		}
   336  
   337  		fakeClientSet := fake.NewSimpleClientset(daemonSetStub)
   338  		_, err = fakeClientSet.AppsV1().ControllerRevisions("default").Create(context.TODO(), daemonSetControllerRevision, metav1.CreateOptions{})
   339  		if err != nil {
   340  			t.Fatalf("create controllerRevisions error %v occurred ", err)
   341  		}
   342  
   343  		var daemonSetHistoryViewer = &DaemonSetHistoryViewer{
   344  			fakeClientSet,
   345  		}
   346  
   347  		t.Run("should show revisions list if the revision is not specified", func(t *testing.T) {
   348  			result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 0)
   349  			if err != nil {
   350  				t.Fatalf("error getting ViewHistory for DaemonSet moons: %v", err)
   351  			}
   352  
   353  			expected := `REVISION  CHANGE-CAUSE
   354  1         <none>
   355  `
   356  
   357  			if result != expected {
   358  				t.Fatalf("unexpected output  (%v was expected but got %v)", expected, result)
   359  			}
   360  		})
   361  
   362  		t.Run("should describe the revision if revision is specified", func(t *testing.T) {
   363  			result, err := daemonSetHistoryViewer.ViewHistory("default", "moons", 1)
   364  			if err != nil {
   365  				t.Fatalf("error getting ViewHistory for DaemonSet moons: %v", err)
   366  			}
   367  
   368  			expected := `Pod Template:
   369    Labels:	foo=bar
   370    Containers:
   371     test:
   372      Image:	nginx
   373      Port:	<none>
   374      Host Port:	<none>
   375      Environment:	<none>
   376      Mounts:	<none>
   377    Volumes:	<none>
   378    Node-Selectors:	<none>
   379    Tolerations:	<none>
   380  `
   381  
   382  			if result != expected {
   383  				t.Fatalf("unexpected output  (%v was expected but got %v)", expected, result)
   384  			}
   385  		})
   386  
   387  		t.Run("should get history", func(t *testing.T) {
   388  			result, err := daemonSetHistoryViewer.GetHistory("default", "moons")
   389  			if err != nil {
   390  				t.Fatalf("error getting history for DaemonSet moons: %v", err)
   391  			}
   392  
   393  			if len(result) != 1 {
   394  				t.Fatalf("unexpected history length (expected 1, got %d", len(result))
   395  			}
   396  
   397  			actual, found := result[1]
   398  			if !found {
   399  				t.Fatalf("revision 1 not found in history")
   400  			}
   401  			expected := daemonSetStub
   402  			if !reflect.DeepEqual(expected, actual) {
   403  				t.Errorf("history does not match. expected %+v, got %+v", expected, actual)
   404  			}
   405  		})
   406  	})
   407  }
   408  
   409  func TestApplyDaemonSetHistory(t *testing.T) {
   410  	tests := []struct {
   411  		name     string
   412  		source   *appsv1.DaemonSet
   413  		expected *appsv1.DaemonSet
   414  	}{
   415  		{
   416  			"test_less",
   417  			&appsv1.DaemonSet{
   418  				Spec: appsv1.DaemonSetSpec{
   419  					Template: corev1.PodTemplateSpec{
   420  						ObjectMeta: metav1.ObjectMeta{
   421  							Annotations: map[string]string{"version": "v3"},
   422  						},
   423  						Spec: corev1.PodSpec{
   424  							InitContainers:   []corev1.Container{{Name: "i0"}},
   425  							Containers:       []corev1.Container{{Name: "c0"}},
   426  							Volumes:          []corev1.Volume{{Name: "v0"}},
   427  							NodeSelector:     map[string]string{"1dsa": "n0"},
   428  							ImagePullSecrets: []corev1.LocalObjectReference{{Name: "ips0"}},
   429  							Tolerations:      []corev1.Toleration{{Key: "t0"}},
   430  							HostAliases:      []corev1.HostAlias{{IP: "h0"}},
   431  							ReadinessGates:   []corev1.PodReadinessGate{{ConditionType: corev1.PodScheduled}},
   432  						},
   433  					},
   434  				},
   435  			},
   436  			&appsv1.DaemonSet{
   437  				Spec: appsv1.DaemonSetSpec{
   438  					Template: corev1.PodTemplateSpec{
   439  						Spec: corev1.PodSpec{
   440  							Containers: []corev1.Container{{Name: "c1"}},
   441  							// keep diversity field, eg: nil or empty slice
   442  							InitContainers: []corev1.Container{},
   443  						},
   444  					},
   445  				},
   446  			},
   447  		},
   448  		{
   449  			"test_more",
   450  			&appsv1.DaemonSet{
   451  				Spec: appsv1.DaemonSetSpec{
   452  					Template: corev1.PodTemplateSpec{
   453  						Spec: corev1.PodSpec{
   454  							InitContainers: []corev1.Container{{Name: "i0"}},
   455  						},
   456  					},
   457  				},
   458  			},
   459  			&appsv1.DaemonSet{
   460  				Spec: appsv1.DaemonSetSpec{
   461  					Template: corev1.PodTemplateSpec{
   462  						ObjectMeta: metav1.ObjectMeta{
   463  							Annotations: map[string]string{"version": "v3"},
   464  						},
   465  						Spec: corev1.PodSpec{
   466  							InitContainers: []corev1.Container{{Name: "i1"}},
   467  							Containers:     []corev1.Container{{Name: "c1"}},
   468  							Volumes:        []corev1.Volume{{Name: "v1"}},
   469  						},
   470  					},
   471  				},
   472  			},
   473  		},
   474  		{
   475  			"test_equal",
   476  			&appsv1.DaemonSet{
   477  				Spec: appsv1.DaemonSetSpec{
   478  					Template: corev1.PodTemplateSpec{
   479  						ObjectMeta: metav1.ObjectMeta{
   480  							Annotations: map[string]string{"version": "v0"},
   481  						},
   482  						Spec: corev1.PodSpec{
   483  							Containers:     []corev1.Container{{Name: "c1"}},
   484  							InitContainers: []corev1.Container{{Name: "i0"}},
   485  							Volumes:        []corev1.Volume{{Name: "v0"}},
   486  						},
   487  					},
   488  				},
   489  			},
   490  			&appsv1.DaemonSet{
   491  				Spec: appsv1.DaemonSetSpec{
   492  					Template: corev1.PodTemplateSpec{
   493  						ObjectMeta: metav1.ObjectMeta{
   494  							Annotations: map[string]string{"version": "v1"},
   495  						},
   496  						Spec: corev1.PodSpec{
   497  							InitContainers: []corev1.Container{{Name: "i1"}},
   498  							Containers:     []corev1.Container{{Name: "c1"}},
   499  							Volumes:        []corev1.Volume{{Name: "v1"}},
   500  						},
   501  					},
   502  				},
   503  			},
   504  		},
   505  	}
   506  
   507  	for _, tt := range tests {
   508  		t.Run(tt.name, func(t *testing.T) {
   509  			patch, err := getDaemonSetPatch(tt.expected)
   510  			if err != nil {
   511  				t.Errorf("getDaemonSetPatch failed : %v", err)
   512  			}
   513  			cr := &appsv1.ControllerRevision{
   514  				Data: runtime.RawExtension{
   515  					Raw: patch,
   516  				},
   517  			}
   518  			tt.source, err = applyDaemonSetHistory(tt.source, cr)
   519  			if err != nil {
   520  				t.Errorf("applyDaemonSetHistory failed : %v", err)
   521  			}
   522  			if !apiequality.Semantic.DeepEqual(tt.source, tt.expected) {
   523  				t.Errorf("expected out [%v]  but get [%v]", tt.expected, tt.source)
   524  			}
   525  		})
   526  	}
   527  }
   528  

View as plain text