github.com/kubewharf/katalyst-core@v0.5.3/pkg/controller/kcc/util/kcct.go (about)

     1  /*
     2  Copyright 2022 The Katalyst 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 util
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sort"
    23  	"time"
    24  
    25  	v1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/errors"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    29  	"k8s.io/apimachinery/pkg/labels"
    30  	"k8s.io/apimachinery/pkg/util/sets"
    31  	"k8s.io/client-go/util/retry"
    32  	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
    33  
    34  	apisv1alpha1 "github.com/kubewharf/katalyst-api/pkg/apis/config/v1alpha1"
    35  	"github.com/kubewharf/katalyst-api/pkg/client/listers/config/v1alpha1"
    36  	"github.com/kubewharf/katalyst-core/pkg/client/control"
    37  	kcctarget "github.com/kubewharf/katalyst-core/pkg/controller/kcc/target"
    38  	"github.com/kubewharf/katalyst-core/pkg/util"
    39  	"github.com/kubewharf/katalyst-core/pkg/util/native"
    40  )
    41  
    42  // GetRelatedCNCForTargetConfig retrieves the currently related CNCs for the configuration based on the following rules:
    43  // 1. Only configurations that have been updated or marked for deletion are considered. This is determined by checking if ObservedGeneration and Generation are equal, ensuring that the configuration has been validated by the KCC controller.
    44  // 2. Only valid configurations are considered, as invalid configurations may affect the associated CNCs and should maintain their current state.
    45  // 3. All CNCs are selected because modifying the target resource can involve more CNCs than those initially selected for the target resource.
    46  func GetRelatedCNCForTargetConfig(customNodeConfigLister v1alpha1.CustomNodeConfigLister, unstructured *unstructured.Unstructured) ([]*apisv1alpha1.CustomNodeConfig, error) {
    47  	targetResource := util.ToKCCTargetResource(unstructured)
    48  	if !targetResource.IsUpdated() && targetResource.GetDeletionTimestamp() == nil {
    49  		return nil, nil
    50  	}
    51  
    52  	if targetResource.CheckExpired(time.Now()) {
    53  		return nil, nil
    54  	}
    55  
    56  	return customNodeConfigLister.List(labels.Everything())
    57  }
    58  
    59  // ApplyKCCTargetConfigToCNC sets the hash value for the given configurations in CNC
    60  func ApplyKCCTargetConfigToCNC(cnc *apisv1alpha1.CustomNodeConfig,
    61  	gvr metav1.GroupVersionResource, kccTarget *unstructured.Unstructured,
    62  ) {
    63  	// only allow one kccTarget for same gvr of a cnc
    64  	if cnc == nil || kccTarget == nil {
    65  		return
    66  	}
    67  
    68  	targetResource := util.ToKCCTargetResource(kccTarget)
    69  	if targetResource.CheckExpired(time.Now()) {
    70  		return
    71  	}
    72  
    73  	idx := 0
    74  	katalystCustomConfigList := cnc.Status.KatalystCustomConfigList
    75  	// find target config
    76  	for ; idx < len(katalystCustomConfigList); idx++ {
    77  		if katalystCustomConfigList[idx].ConfigType == gvr {
    78  			break
    79  		}
    80  	}
    81  
    82  	// update target config if the gvr is already existed, otherwise append it and sort
    83  	if idx < len(katalystCustomConfigList) {
    84  		katalystCustomConfigList[idx] = apisv1alpha1.TargetConfig{
    85  			ConfigType:      gvr,
    86  			ConfigNamespace: targetResource.GetNamespace(),
    87  			ConfigName:      targetResource.GetName(),
    88  			Hash:            targetResource.GetHash(),
    89  		}
    90  	} else {
    91  		katalystCustomConfigList = append(katalystCustomConfigList, apisv1alpha1.TargetConfig{
    92  			ConfigType:      gvr,
    93  			ConfigNamespace: targetResource.GetNamespace(),
    94  			ConfigName:      targetResource.GetName(),
    95  			Hash:            targetResource.GetHash(),
    96  		})
    97  
    98  		cnc.Status.KatalystCustomConfigList = katalystCustomConfigList
    99  		sort.SliceStable(katalystCustomConfigList, func(i, j int) bool {
   100  			return katalystCustomConfigList[i].ConfigType.String() < katalystCustomConfigList[j].ConfigType.String()
   101  		})
   102  	}
   103  }
   104  
   105  // FindMatchedKCCTargetConfigForNode is to find this cnc needed config, if there are some configs are still not updated, we skip it.
   106  // The rule of cnc match to config is:
   107  // 1. if there is only one matched nodeNames config, and it is valid, return it
   108  // 2. if there is only one matched labelSelector config with the highest priority, and it is valid, return it
   109  // 3. if there is only one global config (either nodeNames or labelSelector is not existed), and it is valid, return it
   110  // 4. otherwise, return nil to keep current state no changed
   111  func FindMatchedKCCTargetConfigForNode(cnc *apisv1alpha1.CustomNodeConfig, targetAccessor kcctarget.KatalystCustomConfigTargetAccessor) (*unstructured.Unstructured, error) {
   112  	kccTargetList, err := targetAccessor.List(labels.Everything())
   113  	if err != nil {
   114  		return nil, err
   115  	}
   116  
   117  	kccTargetList, err = filterAvailableKCCTargetConfigs(kccTargetList)
   118  	if err != nil {
   119  		return nil, err
   120  	}
   121  
   122  	kccTargetList, err = findMatchedKCCTargetConfigForNode(cnc, kccTargetList)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	if len(kccTargetList) == 0 {
   128  		return nil, fmt.Errorf("matched kcc target config not found")
   129  	} else if len(kccTargetList) > 1 {
   130  		sort.SliceStable(kccTargetList, func(i, j int) bool {
   131  			return util.ToKCCTargetResource(kccTargetList[i]).GetPriority() >
   132  				util.ToKCCTargetResource(kccTargetList[j]).GetPriority()
   133  		})
   134  
   135  		if util.ToKCCTargetResource(kccTargetList[0]).GetPriority() ==
   136  			util.ToKCCTargetResource(kccTargetList[1]).GetPriority() {
   137  			return nil, fmt.Errorf("more than one kcc target config found with same priority")
   138  		}
   139  	}
   140  
   141  	if !util.ToKCCTargetResource(kccTargetList[0]).CheckValid() {
   142  		return nil, fmt.Errorf("one kcc target config found but invalid")
   143  	}
   144  
   145  	return kccTargetList[0], nil
   146  }
   147  
   148  // filterAvailableKCCTargetConfigs returns those available configurations from kcc target list
   149  func filterAvailableKCCTargetConfigs(kccTargetList []*unstructured.Unstructured) ([]*unstructured.Unstructured, error) {
   150  	availableKCCTargetList := make([]*unstructured.Unstructured, 0, len(kccTargetList))
   151  	// check whether all kccTarget has been updated
   152  	for _, kccTarget := range kccTargetList {
   153  		if kccTarget.GetDeletionTimestamp() != nil {
   154  			continue
   155  		}
   156  
   157  		if !util.ToKCCTargetResource(kccTarget).IsUpdated() {
   158  			return nil, fmt.Errorf("kccTarget %s is updating", native.GenerateUniqObjectNameKey(kccTarget))
   159  		}
   160  
   161  		availableKCCTargetList = append(availableKCCTargetList, kccTarget)
   162  	}
   163  
   164  	return availableKCCTargetList, nil
   165  }
   166  
   167  // findMatchedKCCTargetConfigForNode gets the matched configurations for CNC CR
   168  // if multiple configurations can match up with the given node, return nil to ignore all of them
   169  func findMatchedKCCTargetConfigForNode(cnc *apisv1alpha1.CustomNodeConfig, kccTargetList []*unstructured.Unstructured) ([]*unstructured.Unstructured, error) {
   170  	var matchedNodeNameConfigs, matchedLabelSelectorConfigs, matchedGlobalConfigs []*unstructured.Unstructured
   171  	for _, obj := range kccTargetList {
   172  		targetResource := util.ToKCCTargetResource(obj)
   173  		nodeNames := targetResource.GetNodeNames()
   174  		labelSelector := targetResource.GetLabelSelector()
   175  		if len(nodeNames) > 0 {
   176  			if sets.NewString(nodeNames...).Has(cnc.GetName()) {
   177  				matchedNodeNameConfigs = append(matchedNodeNameConfigs, obj)
   178  			}
   179  			continue
   180  		}
   181  
   182  		if labelSelector != "" {
   183  			selector, err := labels.Parse(labelSelector)
   184  			if err != nil {
   185  				// if some label selector config parse failed, we make all label selector config invalid
   186  				matchedLabelSelectorConfigs = append(matchedLabelSelectorConfigs, obj)
   187  				continue
   188  			}
   189  
   190  			cncLabels := cnc.GetLabels()
   191  			if selector.Matches(labels.Set(cncLabels)) {
   192  				matchedLabelSelectorConfigs = append(matchedLabelSelectorConfigs, obj)
   193  			}
   194  			continue
   195  		}
   196  
   197  		matchedGlobalConfigs = append(matchedGlobalConfigs, obj)
   198  	}
   199  
   200  	if len(matchedNodeNameConfigs) > 0 {
   201  		return matchedNodeNameConfigs, nil
   202  	} else if len(matchedLabelSelectorConfigs) > 0 {
   203  		return matchedLabelSelectorConfigs, nil
   204  	} else if len(matchedGlobalConfigs) > 0 {
   205  		return matchedGlobalConfigs, nil
   206  	}
   207  
   208  	return nil, nil
   209  }
   210  
   211  // UpdateKCCTGenericConditions is used to update conditions for kcc
   212  func UpdateKCCTGenericConditions(status *apisv1alpha1.GenericConfigStatus, conditionType apisv1alpha1.ConfigConditionType,
   213  	conditionStatus v1.ConditionStatus, reason, message string,
   214  ) bool {
   215  	var (
   216  		updated bool
   217  		found   bool
   218  	)
   219  
   220  	conditions := status.Conditions
   221  	for idx := range conditions {
   222  		if conditions[idx].Type == conditionType {
   223  			if conditions[idx].Status != conditionStatus {
   224  				conditions[idx].Status = conditionStatus
   225  				conditions[idx].LastTransitionTime = metav1.NewTime(time.Now())
   226  				updated = true
   227  			}
   228  			if conditions[idx].Reason != reason {
   229  				conditions[idx].Reason = reason
   230  				updated = true
   231  			}
   232  			if conditions[idx].Message != message {
   233  				conditions[idx].Message = message
   234  				updated = true
   235  			}
   236  			found = true
   237  			break
   238  		}
   239  	}
   240  
   241  	if !found {
   242  		conditions = append(conditions, apisv1alpha1.GenericConfigCondition{
   243  			Type:               conditionType,
   244  			Status:             conditionStatus,
   245  			LastTransitionTime: metav1.NewTime(time.Now()),
   246  			Reason:             reason,
   247  			Message:            message,
   248  		})
   249  		status.Conditions = conditions
   250  		updated = true
   251  	}
   252  
   253  	return updated
   254  }
   255  
   256  // EnsureKCCTargetFinalizer is used to add finalizer in kcc target
   257  // any component (that depends on kcc target) should add a specific finalizer in the target CR
   258  func EnsureKCCTargetFinalizer(ctx context.Context, unstructuredControl control.UnstructuredControl, finalizerName string,
   259  	gvr metav1.GroupVersionResource, target *unstructured.Unstructured,
   260  ) (*unstructured.Unstructured, error) {
   261  	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   262  		var err, getErr error
   263  		if controllerutil.ContainsFinalizer(target, finalizerName) {
   264  			return nil
   265  		}
   266  
   267  		controllerutil.AddFinalizer(target, finalizerName)
   268  		newTarget, err := unstructuredControl.UpdateUnstructured(ctx, gvr, target, metav1.UpdateOptions{})
   269  		if errors.IsConflict(err) {
   270  			newTarget, getErr = unstructuredControl.GetUnstructured(ctx, gvr, target.GetNamespace(), target.GetName(), metav1.GetOptions{ResourceVersion: "0"})
   271  			if getErr != nil {
   272  				return getErr
   273  			}
   274  		}
   275  
   276  		target = newTarget
   277  		return err
   278  	})
   279  	if err != nil {
   280  		return nil, err
   281  	}
   282  
   283  	return target, nil
   284  }
   285  
   286  // RemoveKCCTargetFinalizer is used to remove finalizer in kcc target
   287  // any component (that depends on kcc target) should make sure its dependency has been relieved before remove
   288  func RemoveKCCTargetFinalizer(ctx context.Context, unstructuredControl control.UnstructuredControl, finalizerName string,
   289  	gvr metav1.GroupVersionResource, target *unstructured.Unstructured,
   290  ) error {
   291  	err := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
   292  		var err, getErr error
   293  
   294  		if !controllerutil.ContainsFinalizer(target, finalizerName) {
   295  			return nil
   296  		}
   297  
   298  		controllerutil.RemoveFinalizer(target, finalizerName)
   299  		newTarget, err := unstructuredControl.UpdateUnstructured(ctx, gvr, target, metav1.UpdateOptions{})
   300  		if errors.IsConflict(err) {
   301  			newTarget, getErr = unstructuredControl.GetUnstructured(ctx, gvr, target.GetNamespace(), target.GetName(), metav1.GetOptions{ResourceVersion: "0"})
   302  			if getErr != nil {
   303  				return getErr
   304  			}
   305  		}
   306  
   307  		target = newTarget
   308  		return err
   309  	})
   310  
   311  	return err
   312  }