Skip to content

Commit

Permalink
add regression test of formerly atomic claimRef
Browse files Browse the repository at this point in the history
  • Loading branch information
alexzielenski committed Aug 3, 2022
1 parent 84f795d commit bd648f3
Showing 1 changed file with 264 additions and 0 deletions.
264 changes: 264 additions & 0 deletions test/integration/apiserver/apply/apply_test.go
Expand Up @@ -27,6 +27,7 @@ import (
"testing"
"time"

"github.com/google/go-cmp/cmp"
"sigs.k8s.io/yaml"

appsv1 "k8s.io/api/apps/v1"
Expand Down Expand Up @@ -3588,3 +3589,266 @@ func TestSubresourceField(t *testing.T) {
t.Fatalf(`Unexpected entry, got: %v`, managedFields[1])
}
}

// K8s has a bug introduced in vX.XX.X which changed the treatment of
// ObjectReferences from granular to atomic. This means that only one manager
// may own all fields of the ObjectReference. This resulted in a regression
// for the common use case of user-specified GVK, and machine-populated UID fields.
//
// This is a test to show that clusters affected by this bug before it was fixed
// do not experience any friction when updating to a version of k8s which marks
// the fields' management again as granular.
func TestApplyFormerlyAtomicFields(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()

// Start server with our populated ObjectReference. Since it is atomic its
// ownership changed when XX popualted the UID after the user specified the
// GVKN.

// 1. Create PersistentVolume with its claimRef owned by
// kube-controller-manager as in v1.22 - 1.24
// 2. Attempt to re-apply the original PersistentVolume which does not
// include uid.
// 3. Check that:
// a.) The operaiton was successfu;
// b.) The uid is unchanged

client, closeFn := setup(t)
defer closeFn()

// old PersistentVolume from last version of k8s with its claimRef owned
// atomically
oldPersistentVolume := []byte(`
{
"apiVersion": "v1",
"kind": "PersistentVolume",
"metadata": {
"creationTimestamp": "2022-06-08T23:46:32Z",
"finalizers": [
"kubernetes.io/pv-protection"
],
"labels": {
"type": "local"
},
"name": "pv-storage",
"uid": "112b18f7-fde6-4e48-aa61-f5168bd576b8"
},
"spec": {
"accessModes": [
"ReadWriteOnce"
],
"capacity": {
"storage": "16Mi"
},
"claimRef": {
"apiVersion": "v1",
"kind": "PersistentVolumeClaim",
"name": "pvc-storage",
"namespace": "default",
"resourceVersion": "15499",
"uid": "2018e302-7b12-406c-9fa2-e52535d29e48"
},
"hostPath": {
"path": "“/tmp/mydata",
"type": ""
},
"persistentVolumeReclaimPolicy": "Retain",
"volumeMode": "Filesystem"
},
"status": {
"phase": "Bound"
}
}`)

managedFieldsUpdate := []byte(`{
"apiVersion": "v1",
"kind": "PersistentVolume",
"metadata": {
"name": "pv-storage",
"managedFields": [
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:metadata": {
"f:labels": {
"f:type": {}
}
},
"f:spec": {
"f:accessModes": {},
"f:capacity": {
"f:storage": {}
},
"f:hostPath": {
"f:path": {}
},
"f:storageClassName": {}
}
},
"manager": "apply_test",
"operation": "Apply",
"time": "2022-06-08T23:46:32Z"
},
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:status": {
"f:phase": {}
}
},
"manager": "kube-controller-manager",
"operation": "Update",
"subresource": "status",
"time": "2022-06-08T23:46:32Z"
},
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {
"f:spec": {
"f:claimRef": {}
}
},
"manager": "kube-controller-manager",
"operation": "Update",
"time": "2022-06-08T23:46:37Z"
}
]
}
}`)

// Re-applies name and namespace
originalPV := []byte(`{
"kind": "PersistentVolume",
"apiVersion": "v1",
"metadata": {
"labels": {
"type": "local"
},
"name": "pv-storage",
},
"spec": {
"storageClassName": "",
"capacity": {
"storage": "16Mi"
},
"accessModes": [
"ReadWriteOnce"
],
"hostPath": {
"path": "“/tmp/mydata"
},
"claimRef": {
"name": "pvc-storage",
"namespace": "default"
}
}
}`)

// Create PV
originalObj, err := client.CoreV1().RESTClient().
Post().
Param("fieldManager", "apply_test").
Resource("persistentvolumes").
Body(oldPersistentVolume).
Do(context.TODO()).
Get()

if err != nil {
t.Fatalf("Failed to apply object: %v", err)
} else if _, ok := originalObj.(*v1.PersistentVolume); !ok {
t.Fatalf("returned object is incorrect type: %t", originalObj)
}

// Directly set managed fields to object
newObj, err := client.CoreV1().RESTClient().
Patch(types.StrategicMergePatchType).
Name("pv-storage").
Param("fieldManager", "apply_test").
Resource("persistentvolumes").
Body(managedFieldsUpdate).
Do(context.TODO()).
Get()

if err != nil {
t.Fatalf("Failed to apply object: %v", err)
} else if _, ok := newObj.(*v1.PersistentVolume); !ok {
t.Fatalf("returned object is incorrect type: %t", newObj)
}

// Is initialized, attempt to write to fields underneath
// claimRef ObjectReference.
newObj, err = client.CoreV1().RESTClient().
Patch(types.ApplyPatchType).
Name("pv-storage").
Param("fieldManager", "apply_test").
Resource("persistentvolumes").
Body(originalPV).
Do(context.TODO()).
Get()

if err != nil {
t.Fatalf("Failed to apply object: %v", err)
} else if _, ok := newObj.(*v1.PersistentVolume); !ok {
t.Fatalf("returned object is incorrect type: %t", newObj)
}

// Test that bug is fixed by showing no error and that uid is not cleared.
if !reflect.DeepEqual(originalObj.(*v1.PersistentVolume).Spec.ClaimRef, newObj.(*v1.PersistentVolume).Spec.ClaimRef) {
t.Fatalf("claimRef changed unexpectedly")
}

// Expect that we know own name/namespace fields
// All other fields unowned
// Make sure apply_test now owns claimRef.UID and that kube-controller-manager owns
// claimRef (but its ownership is not respected due to new granular structType)
managedFields := newObj.(*v1.PersistentVolume).ManagedFields
var expectedManagedFields []metav1.ManagedFieldsEntry
expectedManagedFieldsString := []byte(`[
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {"f:metadata":{"f:labels":{"f:type":{}}},"f:spec":{"f:accessModes":{},"f:capacity":{"f:storage":{}},"f:claimRef":{"f:name":{},"f:namespace":{}},"f:hostPath":{"f:path":{}},"f:storageClassName":{}}},
"manager": "apply_test",
"operation": "Apply",
"time": "2022-06-08T23:46:32Z"
},
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {"f:status":{"f:phase":{}}},
"manager": "kube-controller-manager",
"operation": "Update",
"subresource": "status",
"time": "2022-06-08T23:46:32Z"
},
{
"apiVersion": "v1",
"fieldsType": "FieldsV1",
"fieldsV1": {"f:spec":{"f:claimRef":{}}},
"manager": "kube-controller-manager",
"operation": "Update",
"time": "2022-06-08T23:46:37Z"
}
]`)

err = json.Unmarshal(expectedManagedFieldsString, &expectedManagedFields)
if err != nil {
t.Fatalf("unexpectly failed to decode expected managed fields")
}

// Wipe timestamps before comparison
for i := range expectedManagedFields {
expectedManagedFields[i].Time = nil
}

for i := range managedFields {
managedFields[i].Time = nil
}

if !reflect.DeepEqual(expectedManagedFields, managedFields) {
t.Fatalf("unexpected managed fields: %v", cmp.Diff(expectedManagedFields, managedFields))
}
}

0 comments on commit bd648f3

Please sign in to comment.