func AddHPAScaleRefEdges(g osgraph.Graph) { for _, node := range g.NodesByKind(kubegraph.HorizontalPodAutoscalerNodeKind) { hpaNode := node.(*kubegraph.HorizontalPodAutoscalerNode) syntheticMeta := kapi.ObjectMeta{ Name: hpaNode.HorizontalPodAutoscaler.Spec.ScaleRef.Name, Namespace: hpaNode.HorizontalPodAutoscaler.Namespace, } var groupVersionResource unversioned.GroupVersionResource resource := strings.ToLower(hpaNode.HorizontalPodAutoscaler.Spec.ScaleRef.Kind) if groupVersion, err := unversioned.ParseGroupVersion(hpaNode.HorizontalPodAutoscaler.Spec.ScaleRef.APIVersion); err == nil { groupVersionResource = groupVersion.WithResource(resource) } else { groupVersionResource = unversioned.GroupVersionResource{Resource: resource} } groupVersionResource, err := registered.RESTMapper().ResourceFor(groupVersionResource) if err != nil { continue } var syntheticNode graph.Node switch groupVersionResource.GroupResource() { case kapi.Resource("replicationcontrollers"): syntheticNode = kubegraph.FindOrCreateSyntheticReplicationControllerNode(g, &kapi.ReplicationController{ObjectMeta: syntheticMeta}) case deployapi.Resource("deploymentconfigs"): syntheticNode = deploygraph.FindOrCreateSyntheticDeploymentConfigNode(g, &deployapi.DeploymentConfig{ObjectMeta: syntheticMeta}) default: continue } g.AddEdge(hpaNode, syntheticNode, ScalingEdgeKind) } }
// ResourceSingularizer implements RESTMapper // It converts a resource name from plural to singular (e.g., from pods to pod) func (m *DefaultRESTMapper) ResourceSingularizer(resourceType string) (string, error) { partialResource := unversioned.GroupVersionResource{Resource: resourceType} resources, err := m.ResourcesFor(partialResource) if err != nil { return resourceType, err } singular := unversioned.GroupVersionResource{} for _, curr := range resources { currSingular, ok := m.pluralToSingular[curr] if !ok { continue } if singular.IsEmpty() { singular = currSingular continue } if currSingular.Resource != singular.Resource { return resourceType, fmt.Errorf("multiple possibilities found %v has been defined", resources) } } if singular.IsEmpty() { return resourceType, fmt.Errorf("no singular of resource %v has been defined", resources[0]) } return singular.Resource, nil }
func monitorFor(p *Propagator, clientPool dynamic.ClientPool, resource unversioned.GroupVersionResource) (monitor, error) { // TODO: consider store in one storage. glog.V(6).Infof("create storage for resource %s", resource) var monitor monitor client, err := clientPool.ClientForGroupVersion(resource.GroupVersion()) if err != nil { return monitor, err } monitor.store, monitor.controller = framework.NewInformer( // TODO: make special List and Watch function that removes fields other // than ObjectMeta. &cache.ListWatch{ ListFunc: func(options api.ListOptions) (runtime.Object, error) { // APIResource.Kind is not used by the dynamic client, so // leave it empty. We want to list this resource in all // namespaces if it's namespace scoped, so leave // APIResource.Namespaced as false is all right. apiResource := unversioned.APIResource{Name: resource.Resource} return client.Resource(&apiResource, api.NamespaceAll).List(&options) }, WatchFunc: func(options api.ListOptions) (watch.Interface, error) { // APIResource.Kind is not used by the dynamic client, so // leave it empty. We want to list this resource in all // namespaces if it's namespace scoped, so leave // APIResource.Namespaced as false is all right. apiResource := unversioned.APIResource{Name: resource.Resource} return client.Resource(&apiResource, api.NamespaceAll).Watch(&options) }, }, nil, ResourceResyncTime, framework.ResourceEventHandlerFuncs{ // add the event to the propagator's eventQueue. AddFunc: func(obj interface{}) { event := event{ eventType: addEvent, obj: obj, } p.eventQueue.Add(event) }, UpdateFunc: func(oldObj, newObj interface{}) { event := event{updateEvent, newObj, oldObj} p.eventQueue.Add(event) }, DeleteFunc: func(obj interface{}) { // delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok { obj = deletedFinalStateUnknown.Obj } event := event{ eventType: deleteEvent, obj: obj, } p.eventQueue.Add(event) }, }, ) return monitor, nil }
// coerceResourceForMatching makes the resource lower case and converts internal versions to unspecified (legacy behavior) func coerceResourceForMatching(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource { resource.Resource = strings.ToLower(resource.Resource) if resource.Version == runtime.APIVersionInternal { resource.Version = "" } return resource }
// deleteAllContentForGroupVersionResource will use the dynamic client to delete each resource identified in gvr. // It returns an estimate of the time remaining before the remaing resources are deleted. // If estimate > 0, not all resources are guaranteed to be gone. func deleteAllContentForGroupVersionResource( kubeClient clientset.Interface, clientPool dynamic.ClientPool, opCache operationNotSupportedCache, gvr unversioned.GroupVersionResource, namespace string, namespaceDeletedAt unversioned.Time, ) (int64, error) { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - namespace: %s, gvr: %v", namespace, gvr) // estimate how long it will take for the resource to be deleted (needed for objects that support graceful delete) estimate, err := estimateGracefulTermination(kubeClient, gvr, namespace, namespaceDeletedAt) if err != nil { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - unable to estimate - namespace: %s, gvr: %v, err: %v", namespace, gvr, err) return estimate, err } glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - estimate - namespace: %s, gvr: %v, estimate: %v", namespace, gvr, estimate) // get a client for this group version... dynamicClient, err := clientPool.ClientForGroupVersion(gvr.GroupVersion()) if err != nil { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - unable to get client - namespace: %s, gvr: %v, err: %v", namespace, gvr, err) return estimate, err } // first try to delete the entire collection deleteCollectionSupported, err := deleteCollection(dynamicClient, opCache, gvr, namespace) if err != nil { return estimate, err } // delete collection was not supported, so we list and delete each item... if !deleteCollectionSupported { err = deleteEachItem(dynamicClient, opCache, gvr, namespace) if err != nil { return estimate, err } } // verify there are no more remaining items // it is not an error condition for there to be remaining items if local estimate is non-zero glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - checking for no more items in namespace: %s, gvr: %v", namespace, gvr) unstructuredList, listSupported, err := listCollection(dynamicClient, opCache, gvr, namespace) if err != nil { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - error verifying no items in namespace: %s, gvr: %v, err: %v", namespace, gvr, err) return estimate, err } if !listSupported { return estimate, nil } glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - items remaining - namespace: %s, gvr: %v, items: %v", namespace, gvr, len(unstructuredList.Items)) if len(unstructuredList.Items) != 0 && estimate == int64(0) { return estimate, fmt.Errorf("unexpected items still remain in namespace: %s for gvr: %v", namespace, gvr) } return estimate, nil }
func (m *DefaultRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 if !hasResource { return nil, fmt.Errorf("a resource must be present, got: %v", resource) } ret := []unversioned.GroupVersionResource{} switch { // fully qualified. Find the exact match case hasGroup && hasVersion: for plural, singular := range m.pluralToSingular { if singular == resource { ret = append(ret, plural) break } if plural == resource { ret = append(ret, plural) break } } case hasGroup: requestedGroupResource := resource.GroupResource() for currResource := range m.pluralToSingular { if currResource.GroupResource() == requestedGroupResource { ret = append(ret, currResource) } } case hasVersion: for currResource := range m.pluralToSingular { if currResource.Version == resource.Version && currResource.Resource == resource.Resource { ret = append(ret, currResource) } } default: for currResource := range m.pluralToSingular { if currResource.Resource == resource.Resource { ret = append(ret, currResource) } } } if len(ret) == 0 { return nil, fmt.Errorf("no resource %v has been defined; known resources: %v", resource, m.pluralToSingular) } sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions}) return ret, nil }
func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { resource := input.GroupVersion().WithResource(strings.ToLower(input.Resource)) if resource.Version == runtime.APIVersionInternal { resource.Version = "" } hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 if !hasResource { return nil, fmt.Errorf("a resource must be present, got: %v", resource) } ret := []unversioned.GroupVersionKind{} switch { // fully qualified. Find the exact match case hasGroup && hasVersion: kind, exists := m.resourceToKind[resource] if exists { ret = append(ret, kind) } case hasGroup: requestedGroupResource := resource.GroupResource() for currResource, currKind := range m.resourceToKind { if currResource.GroupResource() == requestedGroupResource { ret = append(ret, currKind) } } case hasVersion: for currResource, currKind := range m.resourceToKind { if currResource.Version == resource.Version && currResource.Resource == resource.Resource { ret = append(ret, currKind) } } default: for currResource, currKind := range m.resourceToKind { if currResource.Resource == resource.Resource { ret = append(ret, currKind) } } } if len(ret) == 0 { return nil, &NoResourceMatchError{PartialResource: input} } sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) return ret, nil }
func (gc *GarbageCollector) monitorFor(resource unversioned.GroupVersionResource, kind unversioned.GroupVersionKind) (monitor, error) { // TODO: consider store in one storage. glog.V(6).Infof("create storage for resource %s", resource) var monitor monitor client, err := gc.metaOnlyClientPool.ClientForGroupVersion(resource.GroupVersion()) if err != nil { return monitor, err } gc.registeredRateLimiterForMonitors.registerIfNotPresent(resource.GroupVersion(), client, "garbage_collector_monitoring") setObjectTypeMeta := func(obj interface{}) { runtimeObject, ok := obj.(runtime.Object) if !ok { utilruntime.HandleError(fmt.Errorf("expected runtime.Object, got %#v", obj)) } runtimeObject.GetObjectKind().SetGroupVersionKind(kind) } monitor.store, monitor.controller = framework.NewInformer( gcListWatcher(client, resource), nil, ResourceResyncTime, framework.ResourceEventHandlerFuncs{ // add the event to the propagator's eventQueue. AddFunc: func(obj interface{}) { setObjectTypeMeta(obj) event := &event{ eventType: addEvent, obj: obj, } gc.propagator.eventQueue.Add(&workqueue.TimedWorkQueueItem{StartTime: gc.clock.Now(), Object: event}) }, UpdateFunc: func(oldObj, newObj interface{}) { setObjectTypeMeta(newObj) setObjectTypeMeta(oldObj) event := &event{updateEvent, newObj, oldObj} gc.propagator.eventQueue.Add(&workqueue.TimedWorkQueueItem{StartTime: gc.clock.Now(), Object: event}) }, DeleteFunc: func(obj interface{}) { // delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok { obj = deletedFinalStateUnknown.Obj } setObjectTypeMeta(obj) event := &event{ eventType: deleteEvent, obj: obj, } gc.propagator.eventQueue.Add(&workqueue.TimedWorkQueueItem{StartTime: gc.clock.Now(), Object: event}) }, }, ) return monitor, nil }
func monitorFor(p *Propagator, clientPool dynamic.ClientPool, resource unversioned.GroupVersionResource, kind unversioned.GroupVersionKind) (monitor, error) { // TODO: consider store in one storage. glog.V(6).Infof("create storage for resource %s", resource) var monitor monitor client, err := p.gc.metaOnlyClientPool.ClientForGroupVersion(resource.GroupVersion()) if err != nil { return monitor, err } setObjectTypeMeta := func(obj interface{}) { runtimeObject, ok := obj.(runtime.Object) if !ok { utilruntime.HandleError(fmt.Errorf("expected runtime.Object, got %#v", obj)) } runtimeObject.GetObjectKind().SetGroupVersionKind(kind) } monitor.store, monitor.controller = framework.NewInformer( gcListWatcher(client, resource), nil, ResourceResyncTime, framework.ResourceEventHandlerFuncs{ // add the event to the propagator's eventQueue. AddFunc: func(obj interface{}) { setObjectTypeMeta(obj) event := event{ eventType: addEvent, obj: obj, } p.eventQueue.Add(event) }, UpdateFunc: func(oldObj, newObj interface{}) { setObjectTypeMeta(newObj) setObjectTypeMeta(oldObj) event := event{updateEvent, newObj, oldObj} p.eventQueue.Add(event) }, DeleteFunc: func(obj interface{}) { // delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok { obj = deletedFinalStateUnknown.Obj } setObjectTypeMeta(obj) event := event{ eventType: deleteEvent, obj: obj, } p.eventQueue.Add(event) }, }, ) return monitor, nil }
func (m *DefaultRESTMapper) KindsFor(input unversioned.GroupVersionResource) ([]unversioned.GroupVersionKind, error) { resource := input.GroupVersion().WithResource(strings.ToLower(input.Resource)) hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 if !hasResource { return nil, fmt.Errorf("a resource must be present, got: %v", resource) } ret := []unversioned.GroupVersionKind{} switch { // fully qualified. Find the exact match case hasGroup && hasVersion: kind, exists := m.resourceToKind[resource] if exists { ret = append(ret, kind) } case hasGroup: requestedGroupResource := resource.GroupResource() for currResource, currKind := range m.resourceToKind { if currResource.GroupResource() == requestedGroupResource { ret = append(ret, currKind) } } case hasVersion: for currResource, currKind := range m.resourceToKind { if currResource.Version == resource.Version && currResource.Resource == resource.Resource { ret = append(ret, currKind) } } default: for currResource, currKind := range m.resourceToKind { if currResource.Resource == resource.Resource { ret = append(ret, currKind) } } } if len(ret) == 0 { return nil, fmt.Errorf("no kind %v has been defined; known resources: %v", resource, m.pluralToSingular) } sort.Sort(kindByPreferredGroupVersion{ret, m.defaultGroupVersions}) return ret, nil }
func resourceFor(mapper meta.RESTMapper, resourceArg string) unversioned.GroupVersionResource { fullySpecifiedGVR, groupResource := unversioned.ParseResourceArg(strings.ToLower(resourceArg)) gvr := unversioned.GroupVersionResource{} if fullySpecifiedGVR != nil { gvr, _ = mapper.ResourceFor(*fullySpecifiedGVR) } if gvr.IsEmpty() { var err error gvr, err = mapper.ResourceFor(groupResource.WithVersion("")) if err != nil { return unversioned.GroupVersionResource{Resource: resourceArg} } } return gvr }
// expandResourceShortcut will return the expanded version of resource // (something that a pkg/api/meta.RESTMapper can understand), if it is // indeed a shortcut. Otherwise, will return resource unmodified. func expandResourceShortcut(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource { if expanded, ok := shortForms[resource.Resource]; ok { // don't change the group or version that's already been specified resource.Resource = expanded } return resource }
// expandResourceShortcut will return the expanded version of resource // (something that a pkg/api/meta.RESTMapper can understand), if it is // indeed a shortcut. Otherwise, will return resource unmodified. func expandResourceShortcut(resource unversioned.GroupVersionResource) unversioned.GroupVersionResource { if expanded, ok := shortForms[resource.Resource]; ok { resource.Resource = expanded return resource } return resource }
func (o *ResourceConfig) ResourceEnabled(resource unversioned.GroupVersionResource) bool { versionOverride, versionExists := o.GroupVersionResourceConfigs[resource.GroupVersion()] if !versionExists { return false } if !versionOverride.Enable { return false } if versionOverride.DisabledResources.Has(resource.Resource) { return false } if len(versionOverride.EnabledResources) > 0 { return versionOverride.EnabledResources.Has(resource.Resource) } return true }
// estimateGrracefulTermination will estimate the graceful termination required for the specific entity in the namespace func estimateGracefulTermination(kubeClient clientset.Interface, groupVersionResource unversioned.GroupVersionResource, ns string, namespaceDeletedAt unversioned.Time) (int64, error) { groupResource := groupVersionResource.GroupResource() glog.V(5).Infof("namespace controller - estimateGracefulTermination - group %s, resource: %s", groupResource.Group, groupResource.Resource) estimate := int64(0) var err error switch groupResource { case unversioned.GroupResource{Group: "", Resource: "pods"}: estimate, err = estimateGracefulTerminationForPods(kubeClient, ns) } if err != nil { return estimate, err } // determine if the estimate is greater than the deletion timestamp duration := time.Since(namespaceDeletedAt.Time) allowedEstimate := time.Duration(estimate) * time.Second if duration >= allowedEstimate { estimate = int64(0) } return estimate, nil }
func monitorFor(p *Propagator, clientPool dynamic.ClientPool, resource unversioned.GroupVersionResource) (monitor, error) { // TODO: consider store in one storage. glog.V(6).Infof("create storage for resource %s", resource) var monitor monitor client, err := clientPool.ClientForGroupVersion(resource.GroupVersion()) if err != nil { return monitor, err } monitor.store, monitor.controller = framework.NewInformer( gcListWatcher(client, resource), nil, ResourceResyncTime, framework.ResourceEventHandlerFuncs{ // add the event to the propagator's eventQueue. AddFunc: func(obj interface{}) { event := event{ eventType: addEvent, obj: obj, } p.eventQueue.Add(event) }, UpdateFunc: func(oldObj, newObj interface{}) { event := event{updateEvent, newObj, oldObj} p.eventQueue.Add(event) }, DeleteFunc: func(obj interface{}) { // delta fifo may wrap the object in a cache.DeletedFinalStateUnknown, unwrap it if deletedFinalStateUnknown, ok := obj.(cache.DeletedFinalStateUnknown); ok { obj = deletedFinalStateUnknown.Obj } event := event{ eventType: deleteEvent, obj: obj, } p.eventQueue.Add(event) }, }, ) return monitor, nil }
// test the list and watch functions correctly converts the ListOptions func TestGCListWatcher(t *testing.T) { testHandler := &fakeActionHandler{} srv, clientConfig := testServerAndClientConfig(testHandler.ServeHTTP) defer srv.Close() clientPool := dynamic.NewClientPool(clientConfig, dynamic.LegacyAPIPathResolverFunc) podResource := unversioned.GroupVersionResource{Version: "v1", Resource: "pods"} client, err := clientPool.ClientForGroupVersion(podResource.GroupVersion()) if err != nil { t.Fatal(err) } lw := gcListWatcher(client, podResource) lw.Watch(api.ListOptions{ResourceVersion: "1"}) lw.List(api.ListOptions{ResourceVersion: "1"}) if e, a := 2, len(testHandler.actions); e != a { t.Errorf("expect %d requests, got %d", e, a) } if e, a := "resourceVersion=1", testHandler.actions[0].query; e != a { t.Errorf("expect %s, got %s", e, a) } if e, a := "resourceVersion=1", testHandler.actions[1].query; e != a { t.Errorf("expect %s, got %s", e, a) } }
// TODO turn this into reusable method checking available resources func contains(resourcesList map[string]*unversioned.APIResourceList, resource unversioned.GroupVersionResource) bool { if resourcesList == nil { return false } resourcesGroup, ok := resourcesList[resource.GroupVersion().String()] if !ok { return false } for _, item := range resourcesGroup.APIResources { if resource.Resource == item.Name { return true } } return false }
func (m *DefaultRESTMapper) ResourcesFor(resource unversioned.GroupVersionResource) ([]unversioned.GroupVersionResource, error) { resource.Resource = strings.ToLower(resource.Resource) if resource.Version == runtime.APIVersionInternal { resource.Version = "" } hasResource := len(resource.Resource) > 0 hasGroup := len(resource.Group) > 0 hasVersion := len(resource.Version) > 0 if !hasResource { return nil, fmt.Errorf("a resource must be present, got: %v", resource) } ret := []unversioned.GroupVersionResource{} switch { // fully qualified. Find the exact match case hasGroup && hasVersion: for plural, singular := range m.pluralToSingular { if singular == resource { ret = append(ret, plural) break } if plural == resource { ret = append(ret, plural) break } } case hasGroup: requestedGroupResource := resource.GroupResource() for plural, singular := range m.pluralToSingular { if singular.GroupResource() == requestedGroupResource { ret = append(ret, plural) } if plural.GroupResource() == requestedGroupResource { ret = append(ret, plural) } } case hasVersion: for plural, singular := range m.pluralToSingular { if singular.Version == resource.Version && singular.Resource == resource.Resource { ret = append(ret, plural) } if plural.Version == resource.Version && plural.Resource == resource.Resource { ret = append(ret, plural) } } default: for plural, singular := range m.pluralToSingular { if singular.Resource == resource.Resource { ret = append(ret, plural) } if plural.Resource == resource.Resource { ret = append(ret, plural) } } } if len(ret) == 0 { return nil, &NoResourceMatchError{PartialResource: resource} } sort.Sort(resourceByPreferredGroupVersion{ret, m.defaultGroupVersions}) return ret, nil }
// patchResource divides PatchResource for easier unit testing func patchResource( ctx api.Context, admit updateAdmissionFunc, timeout time.Duration, versionedObj runtime.Object, patcher rest.Patcher, name string, patchType api.PatchType, patchJS []byte, namer ScopeNamer, copier runtime.ObjectCopier, resource unversioned.GroupVersionResource, codec runtime.Codec, ) (runtime.Object, error) { namespace := api.NamespaceValue(ctx) var ( originalObjJS []byte originalPatchedObjJS []byte lastConflictErr error ) // applyPatch is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object as input. applyPatch := func(_ api.Context, _, currentObject runtime.Object) (runtime.Object, error) { // Make sure we actually have a persisted currentObject if hasUID, err := hasUID(currentObject); err != nil { return nil, err } else if !hasUID { return nil, errors.NewNotFound(resource.GroupResource(), name) } switch { case len(originalObjJS) == 0 || len(originalPatchedObjJS) == 0: // first time through, // 1. apply the patch // 2. save the originalJS and patchedJS to detect whether there were conflicting changes on retries if js, err := runtime.Encode(codec, currentObject); err != nil { return nil, err } else { originalObjJS = js } if js, err := getPatchedJS(patchType, originalObjJS, patchJS, versionedObj); err != nil { return nil, err } else { originalPatchedObjJS = js } objToUpdate := patcher.New() if err := runtime.DecodeInto(codec, originalPatchedObjJS, objToUpdate); err != nil { return nil, err } if err := checkName(objToUpdate, name, namespace, namer); err != nil { return nil, err } return objToUpdate, nil default: // on a conflict, // 1. build a strategic merge patch from originalJS and the patchedJS. Different patch types can // be specified, but a strategic merge patch should be expressive enough handle them. Build the // patch with this type to handle those cases. // 2. build a strategic merge patch from originalJS and the currentJS // 3. ensure no conflicts between the two patches // 4. apply the #1 patch to the currentJS object currentObjectJS, err := runtime.Encode(codec, currentObject) if err != nil { return nil, err } currentPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, currentObjectJS, versionedObj, strategicpatch.SMPatchVersionLatest) if err != nil { return nil, err } originalPatch, err := strategicpatch.CreateStrategicMergePatch(originalObjJS, originalPatchedObjJS, versionedObj, strategicpatch.SMPatchVersionLatest) if err != nil { return nil, err } diff1 := make(map[string]interface{}) if err := json.Unmarshal(originalPatch, &diff1); err != nil { return nil, err } diff2 := make(map[string]interface{}) if err := json.Unmarshal(currentPatch, &diff2); err != nil { return nil, err } hasConflicts, err := strategicpatch.HasConflicts(diff1, diff2) if err != nil { return nil, err } if hasConflicts { glog.V(4).Infof("patchResource failed for resource %s, because there is a meaningful conflict.\n diff1=%v\n, diff2=%v\n", name, diff1, diff2) // Return the last conflict error we got if we have one if lastConflictErr != nil { return nil, lastConflictErr } // Otherwise manufacture one of our own return nil, errors.NewConflict(resource.GroupResource(), name, nil) } newlyPatchedObjJS, err := getPatchedJS(api.StrategicMergePatchType, currentObjectJS, originalPatch, versionedObj) if err != nil { return nil, err } objToUpdate := patcher.New() if err := runtime.DecodeInto(codec, newlyPatchedObjJS, objToUpdate); err != nil { return nil, err } return objToUpdate, nil } } // applyAdmission is called every time GuaranteedUpdate asks for the updated object, // and is given the currently persisted object and the patched object as input. applyAdmission := func(ctx api.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) { return patchedObject, admit(patchedObject, currentObject) } updatedObjectInfo := rest.DefaultUpdatedObjectInfo(nil, copier, applyPatch, applyAdmission) return finishRequest(timeout, func() (runtime.Object, error) { updateObject, _, updateErr := patcher.Update(ctx, name, updatedObjectInfo) for i := 0; i < MaxPatchConflicts && (errors.IsConflict(updateErr)); i++ { lastConflictErr = updateErr updateObject, _, updateErr = patcher.Update(ctx, name, updatedObjectInfo) } return updateObject, updateErr }) }
// deleteAllContentForGroupVersionResource will use the dynamic client to delete each resource identified in gvr. // It returns an estimate of the time remaining before the remaining resources are deleted. // If estimate > 0, not all resources are guaranteed to be gone. func deleteAllContentForGroupVersionResource( kubeClient clientset.Interface, clientPool dynamic.ClientPool, opCache *operationNotSupportedCache, gvr unversioned.GroupVersionResource, namespace string, namespaceDeletedAt unversioned.Time, ) (int64, error) { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - namespace: %s, gvr: %v", namespace, gvr) // estimate how long it will take for the resource to be deleted (needed for objects that support graceful delete) estimate, err := estimateGracefulTermination(kubeClient, gvr, namespace, namespaceDeletedAt) if err != nil { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - unable to estimate - namespace: %s, gvr: %v, err: %v", namespace, gvr, err) return estimate, err } glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - estimate - namespace: %s, gvr: %v, estimate: %v", namespace, gvr, estimate) // get a client for this group version... dynamicClient, err := clientPool.ClientForGroupVersion(gvr.GroupVersion()) if err != nil { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - unable to get client - namespace: %s, gvr: %v, err: %v", namespace, gvr, err) return estimate, err } // first try to delete the entire collection deleteCollectionSupported, err := deleteCollection(dynamicClient, opCache, gvr, namespace) if err != nil { return estimate, err } // delete collection was not supported, so we list and delete each item... if !deleteCollectionSupported { err = deleteEachItem(dynamicClient, opCache, gvr, namespace) if err != nil { return estimate, err } } // verify there are no more remaining items // it is not an error condition for there to be remaining items if local estimate is non-zero glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - checking for no more items in namespace: %s, gvr: %v", namespace, gvr) unstructuredList, listSupported, err := listCollection(dynamicClient, opCache, gvr, namespace) if err != nil { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - error verifying no items in namespace: %s, gvr: %v, err: %v", namespace, gvr, err) return estimate, err } if !listSupported { return estimate, nil } glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - items remaining - namespace: %s, gvr: %v, items: %v", namespace, gvr, len(unstructuredList.Items)) if len(unstructuredList.Items) != 0 && estimate == int64(0) { // if any item has a finalizer, we treat that as a normal condition, and use a default estimation to allow for GC to complete. for _, item := range unstructuredList.Items { if len(item.GetFinalizers()) > 0 { glog.V(5).Infof("namespace controller - deleteAllContentForGroupVersionResource - items remaining with finalizers - namespace: %s, gvr: %v, finalizers: %v", namespace, gvr, item.GetFinalizers()) return finalizerEstimateSeconds, nil } } // nothing reported a finalizer, so something was unexpected as it should have been deleted. return estimate, fmt.Errorf("unexpected items still remain in namespace: %s for gvr: %v", namespace, gvr) } return estimate, nil }