diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/gvkparser.go b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/gvkparser.go index b86a533ac60..1c750638615 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/gvkparser.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/handlers/fieldmanager/gvkparser.go @@ -69,7 +69,6 @@ func NewGVKParser(models proto.Models, preserveUnknownFields bool) (*GvkParser, if len(gvk.Kind) > 0 { _, ok := parser.gvks[gvk] if ok { - // TODO: double check why this is failing in real_cluster_script.go return nil, fmt.Errorf("duplicate entry for %v", gvk) } parser.gvks[gvk] = modelName diff --git a/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go b/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go index 67f95026dd1..222772ad720 100644 --- a/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go +++ b/staging/src/k8s.io/client-go/applyconfigurations/meta/v1/unstructured.go @@ -19,22 +19,22 @@ type UnstructuredExtractor interface { ExtractUnstructuredStatus(object *unstructured.Unstructured, fieldManager string) (*unstructured.Unstructured, error) } -// objectTypeCache is a cache of typed.ParseableTypes -type objectTypeCache interface { - objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) -} +//// objectTypeCache is a cache of typed.ParseableTypes +//type objectTypeCache interface { +// objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) +//} -// nonCachingObjectTypeCache is a objectTypeCache that does no caching +// objectTypeCache is a objectTypeCache that does no caching // (i.e. it downloads the OpenAPISchema every time) // Useful during the proof-of-concept stage until we agree on a caching solution. -type nonCachingObjectTypeCache struct { +type objectTypeCache struct { // TODO: lock this? discoveryClient discovery.DiscoveryInterface gvkParser *fieldmanager.GvkParser } // objectTypeForGVK retrieves the typed.ParseableType for a given gvk from the cache -func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) { +func (c *objectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind) (*typed.ParseableType, error) { if !c.discoveryClient.HasOpenAPISchemaChanged() && c.gvkParser != nil { // cache hit @@ -67,14 +67,14 @@ func (c *nonCachingObjectTypeCache) objectTypeForGVK(gvk schema.GroupVersionKind } type extractor struct { - cache objectTypeCache + cache *objectTypeCache } // NewUnstructuredExtractor creates the extractor with which you can extract the applied configuration // for a given manager from an unstructured object. func NewUnstructuredExtractor(dc discovery.DiscoveryInterface) UnstructuredExtractor { return &extractor{ - cache: &nonCachingObjectTypeCache{ + cache: &objectTypeCache{ discoveryClient: dc, }, } diff --git a/staging/src/k8s.io/client-go/discovery/cached/memory/memcache.go b/staging/src/k8s.io/client-go/discovery/cached/memory/memcache.go index 9de389fa7e4..5d54092f771 100644 --- a/staging/src/k8s.io/client-go/discovery/cached/memory/memcache.go +++ b/staging/src/k8s.io/client-go/discovery/cached/memory/memcache.go @@ -149,6 +149,10 @@ func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) { return d.delegate.OpenAPISchema() } +func (d *memCacheClient) HasOpenAPISchemaChanged() bool { + return d.delegate.HasOpenAPISchemaChanged() +} + func (d *memCacheClient) Fresh() bool { d.lock.RLock() defer d.lock.RUnlock() diff --git a/staging/src/k8s.io/client-go/discovery/fake/discovery.go b/staging/src/k8s.io/client-go/discovery/fake/discovery.go index f0cc2dbf4f3..fc89bed0f80 100644 --- a/staging/src/k8s.io/client-go/discovery/fake/discovery.go +++ b/staging/src/k8s.io/client-go/discovery/fake/discovery.go @@ -153,6 +153,10 @@ func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) { return &openapi_v2.Document{}, nil } +func (c *FakeDiscovery) HasOpenAPISchemaChanged() bool { + return true +} + // RESTClient returns a RESTClient that is used to communicate with API server // by this client implementation. func (c *FakeDiscovery) RESTClient() restclient.Interface { diff --git a/test/integration/client/dynamic_client_test.go b/test/integration/client/dynamic_client_test.go index 193f0c345fd..813000bf3a8 100644 --- a/test/integration/client/dynamic_client_test.go +++ b/test/integration/client/dynamic_client_test.go @@ -302,6 +302,39 @@ func TestUnstructuredExtract(t *testing.T) { } fmt.Printf("gotModified = %+v\n", gotModified) + // extract again to test hitting the object type cache + extracted2, err := extractor.ExtractUnstructured(gotModified, mgr) + if err != nil { + t.Fatalf("unexpected error when extracting for the second time") + } + + // modify the object and apply the modified object again + modified2 := extracted2 + modified2.SetLabels(map[string]string{"label2": "value2"}) + modifiedData2, err := json.Marshal(modified2) + if err != nil { + t.Fatalf("failed to marshal modified pod into bytes: %v", err) + } + + actualModified2, err := dynamicClient.Resource(resource).Namespace("default").Patch( + context.TODO(), + name, + types.ApplyPatchType, + modifiedData2, + metav1.PatchOptions{FieldManager: mgr}) + if err != nil { + t.Fatalf("unexpected error when applying modified pod: %v", err) + } + + gotModified2, err := dynamicClient.Resource(resource).Namespace("default").Get(context.TODO(), name, metav1.GetOptions{}) + if err != nil { + t.Fatalf("unexpected error when getting pod %q: %v", name, err) + } + + if !reflect.DeepEqual(actualModified2, gotModified2) { + t.Fatalf("unexpected pod in list. wanted %#v, got %#v", actualModified, gotModified) + } + // delete the object dynamically err = dynamicClient.Resource(resource).Namespace("default").Delete(context.TODO(), name, metav1.DeleteOptions{}) if err != nil {