...

Source file src/k8s.io/apimachinery/pkg/util/managedfields/scalehandler.go

Documentation: k8s.io/apimachinery/pkg/util/managedfields

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package managedfields
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"k8s.io/apimachinery/pkg/api/meta"
    23  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    24  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    25  	"k8s.io/apimachinery/pkg/runtime/schema"
    26  	"k8s.io/apimachinery/pkg/util/managedfields/internal"
    27  	"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
    28  )
    29  
    30  var (
    31  	scaleGroupVersion   = schema.GroupVersion{Group: "autoscaling", Version: "v1"}
    32  	replicasPathInScale = fieldpath.MakePathOrDie("spec", "replicas")
    33  )
    34  
    35  // ResourcePathMappings maps a group/version to its replicas path. The
    36  // assumption is that all the paths correspond to leaf fields.
    37  type ResourcePathMappings map[string]fieldpath.Path
    38  
    39  // ScaleHandler manages the conversion of managed fields between a main
    40  // resource and the scale subresource
    41  type ScaleHandler struct {
    42  	parentEntries []metav1.ManagedFieldsEntry
    43  	groupVersion  schema.GroupVersion
    44  	mappings      ResourcePathMappings
    45  }
    46  
    47  // NewScaleHandler creates a new ScaleHandler
    48  func NewScaleHandler(parentEntries []metav1.ManagedFieldsEntry, groupVersion schema.GroupVersion, mappings ResourcePathMappings) *ScaleHandler {
    49  	return &ScaleHandler{
    50  		parentEntries: parentEntries,
    51  		groupVersion:  groupVersion,
    52  		mappings:      mappings,
    53  	}
    54  }
    55  
    56  // ToSubresource filter the managed fields of the main resource and convert
    57  // them so that they can be handled by scale.
    58  // For the managed fields that have a replicas path it performs two changes:
    59  //  1. APIVersion is changed to the APIVersion of the scale subresource
    60  //  2. Replicas path of the main resource is transformed to the replicas path of
    61  //     the scale subresource
    62  func (h *ScaleHandler) ToSubresource() ([]metav1.ManagedFieldsEntry, error) {
    63  	managed, err := internal.DecodeManagedFields(h.parentEntries)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	f := fieldpath.ManagedFields{}
    69  	t := map[string]*metav1.Time{}
    70  	for manager, versionedSet := range managed.Fields() {
    71  		path, ok := h.mappings[string(versionedSet.APIVersion())]
    72  		// Skip the entry if the APIVersion is unknown
    73  		if !ok || path == nil {
    74  			continue
    75  		}
    76  
    77  		if versionedSet.Set().Has(path) {
    78  			newVersionedSet := fieldpath.NewVersionedSet(
    79  				fieldpath.NewSet(replicasPathInScale),
    80  				fieldpath.APIVersion(scaleGroupVersion.String()),
    81  				versionedSet.Applied(),
    82  			)
    83  
    84  			f[manager] = newVersionedSet
    85  			t[manager] = managed.Times()[manager]
    86  		}
    87  	}
    88  
    89  	return managedFieldsEntries(internal.NewManaged(f, t))
    90  }
    91  
    92  // ToParent merges `scaleEntries` with the entries of the main resource and
    93  // transforms them accordingly
    94  func (h *ScaleHandler) ToParent(scaleEntries []metav1.ManagedFieldsEntry) ([]metav1.ManagedFieldsEntry, error) {
    95  	decodedParentEntries, err := internal.DecodeManagedFields(h.parentEntries)
    96  	if err != nil {
    97  		return nil, err
    98  	}
    99  	parentFields := decodedParentEntries.Fields()
   100  
   101  	decodedScaleEntries, err := internal.DecodeManagedFields(scaleEntries)
   102  	if err != nil {
   103  		return nil, err
   104  	}
   105  	scaleFields := decodedScaleEntries.Fields()
   106  
   107  	f := fieldpath.ManagedFields{}
   108  	t := map[string]*metav1.Time{}
   109  
   110  	for manager, versionedSet := range parentFields {
   111  		// Get the main resource "replicas" path
   112  		path, ok := h.mappings[string(versionedSet.APIVersion())]
   113  		// Drop the entry if the APIVersion is unknown.
   114  		if !ok {
   115  			continue
   116  		}
   117  
   118  		// If the parent entry does not have the replicas path or it is nil, just
   119  		// keep it as it is. The path is nil for Custom Resources without scale
   120  		// subresource.
   121  		if path == nil || !versionedSet.Set().Has(path) {
   122  			f[manager] = versionedSet
   123  			t[manager] = decodedParentEntries.Times()[manager]
   124  			continue
   125  		}
   126  
   127  		if _, ok := scaleFields[manager]; !ok {
   128  			// "Steal" the replicas path from the main resource entry
   129  			newSet := versionedSet.Set().Difference(fieldpath.NewSet(path))
   130  
   131  			if !newSet.Empty() {
   132  				newVersionedSet := fieldpath.NewVersionedSet(
   133  					newSet,
   134  					versionedSet.APIVersion(),
   135  					versionedSet.Applied(),
   136  				)
   137  				f[manager] = newVersionedSet
   138  				t[manager] = decodedParentEntries.Times()[manager]
   139  			}
   140  		} else {
   141  			// Field wasn't stolen, let's keep the entry as it is.
   142  			f[manager] = versionedSet
   143  			t[manager] = decodedParentEntries.Times()[manager]
   144  			delete(scaleFields, manager)
   145  		}
   146  	}
   147  
   148  	for manager, versionedSet := range scaleFields {
   149  		if !versionedSet.Set().Has(replicasPathInScale) {
   150  			continue
   151  		}
   152  		newVersionedSet := fieldpath.NewVersionedSet(
   153  			fieldpath.NewSet(h.mappings[h.groupVersion.String()]),
   154  			fieldpath.APIVersion(h.groupVersion.String()),
   155  			versionedSet.Applied(),
   156  		)
   157  		f[manager] = newVersionedSet
   158  		t[manager] = decodedParentEntries.Times()[manager]
   159  	}
   160  
   161  	return managedFieldsEntries(internal.NewManaged(f, t))
   162  }
   163  
   164  func managedFieldsEntries(entries internal.ManagedInterface) ([]metav1.ManagedFieldsEntry, error) {
   165  	obj := &unstructured.Unstructured{Object: map[string]interface{}{}}
   166  	if err := internal.EncodeObjectManagedFields(obj, entries); err != nil {
   167  		return nil, err
   168  	}
   169  	accessor, err := meta.Accessor(obj)
   170  	if err != nil {
   171  		panic(fmt.Sprintf("couldn't get accessor: %v", err))
   172  	}
   173  	return accessor.GetManagedFields(), nil
   174  }
   175  

View as plain text