mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-09 12:07:47 +00:00
Add server side apply unsetting field tests
This commit is contained in:
parent
11c38a9c70
commit
ec136db9ce
@ -16,6 +16,7 @@ go_test(
|
||||
deps = [
|
||||
"//cmd/kube-apiserver/app/testing:go_default_library",
|
||||
"//pkg/master:go_default_library",
|
||||
"//staging/src/k8s.io/api/apps/v1:go_default_library",
|
||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library",
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
@ -1652,6 +1653,348 @@ func TestClearManagedFieldsWithUpdateEmptyList(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyUnsetExclusivelyOwnedFields verifies that when owned fields are omitted from an applied
|
||||
// configuration, and no other managers own the field, it is removed.
|
||||
func TestApplyUnsetExclusivelyOwnedFields(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||
|
||||
_, client, closeFn := setup(t)
|
||||
defer closeFn()
|
||||
|
||||
// spec.replicas is a optional, defaulted field
|
||||
// spec.template.spec.hostname is an optional, non-defaulted field
|
||||
apply := []byte(`{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "deployment-exclusive-unset",
|
||||
"labels": {"app": "nginx"}
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 3,
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"hostname": "test-hostname",
|
||||
"containers": [{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||
AbsPath("/apis/apps/v1").
|
||||
Namespace("default").
|
||||
Resource("deployments").
|
||||
Name("deployment-exclusive-unset").
|
||||
Param("fieldManager", "apply_test").
|
||||
Body(apply).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
|
||||
// unset spec.replicas and spec.template.spec.hostname
|
||||
apply = []byte(`{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "deployment-exclusive-unset",
|
||||
"labels": {"app": "nginx"}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||
AbsPath("/apis/apps/v1").
|
||||
Namespace("default").
|
||||
Resource("deployments").
|
||||
Name("deployment-exclusive-unset").
|
||||
Param("fieldManager", "apply_test").
|
||||
Body(apply).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
|
||||
deployment, ok := patched.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
t.Fatalf("Failed to convert response object to Deployment")
|
||||
}
|
||||
if *deployment.Spec.Replicas != 1 {
|
||||
t.Errorf("Expected deployment.spec.replicas to be 1 (default value), but got %d", deployment.Spec.Replicas)
|
||||
}
|
||||
if len(deployment.Spec.Template.Spec.Hostname) != 0 {
|
||||
t.Errorf("Expected deployment.spec.template.spec.hostname to be unset, but got %s", deployment.Spec.Template.Spec.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyUnsetSharedFields verifies that when owned fields are omitted from an applied
|
||||
// configuration, but other managers also own the field, is it not removed.
|
||||
func TestApplyUnsetSharedFields(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||
|
||||
_, client, closeFn := setup(t)
|
||||
defer closeFn()
|
||||
|
||||
// spec.replicas is a optional, defaulted field
|
||||
// spec.template.spec.hostname is an optional, non-defaulted field
|
||||
apply := []byte(`{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "deployment-shared-unset",
|
||||
"labels": {"app": "nginx"}
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 3,
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"hostname": "test-hostname",
|
||||
"containers": [{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
for _, fieldManager := range []string{"shared_owner_1", "shared_owner_2"} {
|
||||
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||
AbsPath("/apis/apps/v1").
|
||||
Namespace("default").
|
||||
Resource("deployments").
|
||||
Name("deployment-shared-unset").
|
||||
Param("fieldManager", fieldManager).
|
||||
Body(apply).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// unset spec.replicas and spec.template.spec.hostname
|
||||
apply = []byte(`{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "deployment-shared-unset",
|
||||
"labels": {"app": "nginx"}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||
AbsPath("/apis/apps/v1").
|
||||
Namespace("default").
|
||||
Resource("deployments").
|
||||
Name("deployment-shared-unset").
|
||||
Param("fieldManager", "shared_owner_1").
|
||||
Body(apply).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
|
||||
deployment, ok := patched.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
t.Fatalf("Failed to convert response object to Deployment")
|
||||
}
|
||||
if *deployment.Spec.Replicas != 3 {
|
||||
t.Errorf("Expected deployment.spec.replicas to be 3, but got %d", deployment.Spec.Replicas)
|
||||
}
|
||||
if deployment.Spec.Template.Spec.Hostname != "test-hostname" {
|
||||
t.Errorf("Expected deployment.spec.template.spec.hostname to be \"test-hostname\", but got %s", deployment.Spec.Template.Spec.Hostname)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyCanRemoveMapItemsContributedToByControllers verifies that when an applier creates an
|
||||
// object, a controller modifies the contents of the map item via update, and the applier
|
||||
// then omits the item from its applied configuration, that the item is removed.
|
||||
func TestApplyCanRemoveMapItemsContributedToByControllers(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
|
||||
|
||||
_, client, closeFn := setup(t)
|
||||
defer closeFn()
|
||||
|
||||
// Applier creates a deployment with a name=nginx container
|
||||
apply := []byte(`{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "deployment-shared-map-item-removal",
|
||||
"labels": {"app": "nginx"}
|
||||
},
|
||||
"spec": {
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"containers": [{
|
||||
"name": "nginx",
|
||||
"image": "nginx:latest",
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
appliedObj, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||
AbsPath("/apis/apps/v1").
|
||||
Namespace("default").
|
||||
Resource("deployments").
|
||||
Name("deployment-shared-map-item-removal").
|
||||
Param("fieldManager", "test_applier").
|
||||
Body(apply).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
|
||||
// a controller sets container.workingDir of the name=nginx container via an update
|
||||
applied, ok := appliedObj.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
t.Fatalf("Failed to convert response object to Deployment")
|
||||
}
|
||||
applied.Spec.Template.Spec.Containers[0].WorkingDir = "/home/replacement"
|
||||
_, err = client.AppsV1().Deployments("default").
|
||||
Update(context.TODO(), applied, metav1.UpdateOptions{FieldManager: "test_updater"})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
|
||||
// applier removes name=nginx the container
|
||||
apply = []byte(`{
|
||||
"apiVersion": "apps/v1",
|
||||
"kind": "Deployment",
|
||||
"metadata": {
|
||||
"name": "deployment-shared-map-item-removal",
|
||||
"labels": {"app": "nginx"}
|
||||
},
|
||||
"spec": {
|
||||
"replicas": 3,
|
||||
"selector": {
|
||||
"matchLabels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"hostname": "test-hostname",
|
||||
"containers": [{
|
||||
"name": "other-container",
|
||||
"image": "nginx:latest",
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
}`)
|
||||
|
||||
patched, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
|
||||
AbsPath("/apis/apps/v1").
|
||||
Namespace("default").
|
||||
Resource("deployments").
|
||||
Name("deployment-shared-map-item-removal").
|
||||
Param("fieldManager", "test_applier").
|
||||
Body(apply).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create object using Apply patch: %v", err)
|
||||
}
|
||||
|
||||
// ensure the container is deleted even though a controller updated a field of the container
|
||||
deployment, ok := patched.(*appsv1.Deployment)
|
||||
if !ok {
|
||||
t.Fatalf("Failed to convert response object to Deployment")
|
||||
}
|
||||
if len(deployment.Spec.Template.Spec.Containers) != 1 {
|
||||
t.Fatalf("Expected 1 container after apply, got %d", len(deployment.Spec.Template.Spec.Containers))
|
||||
}
|
||||
if deployment.Spec.Template.Spec.Containers[0].Name != "other-container" {
|
||||
t.Fatalf("Expected container to be named \"other-container\" but got %s", deployment.Spec.Template.Spec.Containers[0].Name)
|
||||
}
|
||||
}
|
||||
|
||||
var podBytes = []byte(`
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
|
Loading…
Reference in New Issue
Block a user