Skip to content

Commit ce9a2c8

Browse files
authored
Merge pull request kubernetes#76468 from julianvmodesto/ssa-cross-gv-test
Adds an integration test for cross group-version server-side apply
2 parents 4dc05dd + 5303f43 commit ce9a2c8

File tree

1 file changed

+276
-1
lines changed

1 file changed

+276
-1
lines changed

test/integration/apiserver/apply/apply_test.go

Lines changed: 276 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package apiserver
1818

1919
import (
2020
"encoding/json"
21+
"net/http"
2122
"net/http/httptest"
23+
"reflect"
2224
"testing"
2325
"time"
2426

@@ -217,7 +219,7 @@ func TestApplyUpdateApplyConflictForced(t *testing.T) {
217219
}
218220

219221
_, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
220-
AbsPath("/apis/extensions/v1beta1").
222+
AbsPath("/apis/apps/v1").
221223
Namespace("default").
222224
Resource("deployments").
223225
Name("deployment").
@@ -555,3 +557,276 @@ func TestApplyRemoveContainerPort(t *testing.T) {
555557
t.Fatalf("Expected no container ports but got: %v", deployment.Spec.Template.Spec.Containers[0].Ports)
556558
}
557559
}
560+
561+
// TestApplyFailsWithVersionMismatch ensures that a version mismatch between the
562+
// patch object and the live object will error
563+
func TestApplyFailsWithVersionMismatch(t *testing.T) {
564+
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
565+
566+
_, client, closeFn := setup(t)
567+
defer closeFn()
568+
569+
obj := []byte(`{
570+
"apiVersion": "apps/v1",
571+
"kind": "Deployment",
572+
"metadata": {
573+
"name": "deployment",
574+
"labels": {"app": "nginx"}
575+
},
576+
"spec": {
577+
"replicas": 3,
578+
"selector": {
579+
"matchLabels": {
580+
"app": "nginx"
581+
}
582+
},
583+
"template": {
584+
"metadata": {
585+
"labels": {
586+
"app": "nginx"
587+
}
588+
},
589+
"spec": {
590+
"containers": [{
591+
"name": "nginx",
592+
"image": "nginx:latest"
593+
}]
594+
}
595+
}
596+
}
597+
}`)
598+
599+
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
600+
AbsPath("/apis/apps/v1").
601+
Namespace("default").
602+
Resource("deployments").
603+
Name("deployment").
604+
Param("fieldManager", "apply_test").
605+
Body(obj).Do().Get()
606+
if err != nil {
607+
t.Fatalf("Failed to create object using Apply patch: %v", err)
608+
}
609+
610+
obj = []byte(`{
611+
"apiVersion": "extensions/v1beta",
612+
"kind": "Deployment",
613+
"metadata": {
614+
"name": "deployment",
615+
"labels": {"app": "nginx"}
616+
},
617+
"spec": {
618+
"replicas": 100,
619+
"selector": {
620+
"matchLabels": {
621+
"app": "nginx"
622+
}
623+
},
624+
"template": {
625+
"metadata": {
626+
"labels": {
627+
"app": "nginx"
628+
}
629+
},
630+
"spec": {
631+
"containers": [{
632+
"name": "nginx",
633+
"image": "nginx:latest"
634+
}]
635+
}
636+
}
637+
}
638+
}`)
639+
_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
640+
AbsPath("/apis/apps/v1").
641+
Namespace("default").
642+
Resource("deployments").
643+
Name("deployment").
644+
Param("fieldManager", "apply_test").
645+
Body([]byte(obj)).Do().Get()
646+
if err == nil {
647+
t.Fatalf("Expecting to get version mismatch when applying object")
648+
}
649+
status, ok := err.(*errors.StatusError)
650+
if !ok {
651+
t.Fatalf("Expecting to get version mismatch as API error")
652+
}
653+
if status.Status().Code != http.StatusBadRequest {
654+
t.Fatalf("expected status code to be %d but was %d", http.StatusBadRequest, status.Status().Code)
655+
}
656+
}
657+
658+
// TestApplyConvertsManagedFieldsVersion checks that the apply
659+
// converts the API group-version in the field manager
660+
func TestApplyConvertsManagedFieldsVersion(t *testing.T) {
661+
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
662+
663+
_, client, closeFn := setup(t)
664+
defer closeFn()
665+
666+
obj := []byte(`{
667+
"apiVersion": "apps/v1",
668+
"kind": "Deployment",
669+
"metadata": {
670+
"name": "deployment",
671+
"labels": {"app": "nginx"},
672+
"managedFields": [
673+
{
674+
"manager": "sidecar_controller",
675+
"operation": "Apply",
676+
"apiVersion": "extensions/v1beta1",
677+
"fields": {
678+
"f:metadata": {
679+
"f:labels": {
680+
"f:sidecar_version": {}
681+
}
682+
},
683+
"f:spec": {
684+
"f:template": {
685+
"f: spec": {
686+
"f:containers": {
687+
"k:{\"name\":\"sidecar\"}": {
688+
".": {},
689+
"f:image": {}
690+
}
691+
}
692+
}
693+
}
694+
}
695+
}
696+
}
697+
]
698+
},
699+
"spec": {
700+
"selector": {
701+
"matchLabels": {
702+
"app": "nginx"
703+
}
704+
},
705+
"template": {
706+
"metadata": {
707+
"labels": {
708+
"app": "nginx"
709+
}
710+
},
711+
"spec": {
712+
"containers": [{
713+
"name": "nginx",
714+
"image": "nginx:latest"
715+
}]
716+
}
717+
}
718+
}
719+
}`)
720+
721+
_, err := client.CoreV1().RESTClient().Post().
722+
AbsPath("/apis/apps/v1").
723+
Namespace("default").
724+
Resource("deployments").
725+
Body(obj).Do().Get()
726+
if err != nil {
727+
t.Fatalf("Failed to create object: %v", err)
728+
}
729+
730+
obj = []byte(`{
731+
"apiVersion": "apps/v1",
732+
"kind": "Deployment",
733+
"metadata": {
734+
"name": "deployment",
735+
"labels": {"sidecar_version": "release"}
736+
},
737+
"spec": {
738+
"template": {
739+
"spec": {
740+
"containers": [{
741+
"name": "sidecar",
742+
"image": "sidecar:latest"
743+
}]
744+
}
745+
}
746+
}
747+
}`)
748+
_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
749+
AbsPath("/apis/apps/v1").
750+
Namespace("default").
751+
Resource("deployments").
752+
Name("deployment").
753+
Param("fieldManager", "sidecar_controller").
754+
Body([]byte(obj)).Do().Get()
755+
if err != nil {
756+
t.Fatalf("Failed to apply object: %v", err)
757+
}
758+
759+
object, err := client.AppsV1().Deployments("default").Get("deployment", metav1.GetOptions{})
760+
if err != nil {
761+
t.Fatalf("Failed to retrieve object: %v", err)
762+
}
763+
764+
accessor, err := meta.Accessor(object)
765+
if err != nil {
766+
t.Fatalf("Failed to get meta accessor: %v", err)
767+
}
768+
769+
managed := accessor.GetManagedFields()
770+
if len(managed) != 2 {
771+
t.Fatalf("Expected 2 field managers, but got managed fields: %v", managed)
772+
}
773+
774+
var actual *metav1.ManagedFieldsEntry
775+
for i := range managed {
776+
entry := &managed[i]
777+
if entry.Manager == "sidecar_controller" && entry.APIVersion == "apps/v1" {
778+
actual = entry
779+
}
780+
}
781+
782+
if actual == nil {
783+
t.Fatalf("Expected managed fields to contain entry with manager '%v' with converted api version '%v', but got managed fields:\n%v", "sidecar_controller", "apps/v1", managed)
784+
}
785+
786+
expected := &metav1.ManagedFieldsEntry{
787+
Manager: "sidecar_controller",
788+
Operation: metav1.ManagedFieldsOperationApply,
789+
APIVersion: "apps/v1",
790+
Time: actual.Time,
791+
Fields: &metav1.Fields{
792+
Map: map[string]metav1.Fields{
793+
"f:metadata": {
794+
Map: map[string]metav1.Fields{
795+
"f:labels": {
796+
Map: map[string]metav1.Fields{
797+
"f:sidecar_version": {Map: map[string]metav1.Fields{}},
798+
},
799+
},
800+
},
801+
},
802+
"f:spec": {
803+
Map: map[string]metav1.Fields{
804+
"f:template": {
805+
Map: map[string]metav1.Fields{
806+
"f:spec": {
807+
Map: map[string]metav1.Fields{
808+
"f:containers": {
809+
Map: map[string]metav1.Fields{
810+
"k:{\"name\":\"sidecar\"}": {
811+
Map: map[string]metav1.Fields{
812+
".": {Map: map[string]metav1.Fields{}},
813+
"f:image": {Map: map[string]metav1.Fields{}},
814+
"f:name": {Map: map[string]metav1.Fields{}},
815+
},
816+
},
817+
},
818+
},
819+
},
820+
},
821+
},
822+
},
823+
},
824+
},
825+
},
826+
},
827+
}
828+
829+
if !reflect.DeepEqual(actual, expected) {
830+
t.Fatalf("expected:\n%v\nbut got:\n%v", expected, actual)
831+
}
832+
}

0 commit comments

Comments
 (0)