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 }