comments fixes
All checks were successful
Lint / Run on Ubuntu (push) Successful in 26s
Tests / Run on Ubuntu (push) Successful in 27s

This commit is contained in:
2025-04-30 01:04:13 +05:00
parent 010bd5fbe0
commit fc5f580243

View File

@@ -221,23 +221,23 @@ func (r *NodeTainterConfigReconciler) SetupWithManager(mgr ctrl.Manager) error {
// --- UTILS FUNCTIONS --- // --- UTILS FUNCTIONS ---
// TaintToString конвертирует тейнт в строку для статуса/логов // Converts Taint to string for status/logs
func TaintToString(taint *corev1.Taint) string { func TaintToString(taint *corev1.Taint) string {
return fmt.Sprintf("%s=%s:%s", taint.Key, taint.Value, taint.Effect) return fmt.Sprintf("%s=%s:%s", taint.Key, taint.Value, taint.Effect)
} }
// TaintsToString конвертирует слайс тейнтов в слайс строк // Converts Taints slice to string slice
func TaintsToStrings(taints []corev1.Taint) []string { func TaintsToStrings(taints []corev1.Taint) []string {
res := make([]string, len(taints)) res := make([]string, len(taints))
for i, t := range taints { for i, t := range taints {
res[i] = TaintToString(&t) res[i] = TaintToString(&t)
} }
sort.Strings(res) // Сортируем для консистентности статуса sort.Strings(res)
return res return res
} }
// parseLabelRulesFromSpec парсит правила из CRD Spec // Parses rules from CRD Spec
// Возвращает map["labelKey=labelValue"]corev1.Taint и ошибки // returns map["labelKey=labelValue"]corev1.Taint and errors
func parseLabelRulesFromSpec(specLabelRules map[string]string) (map[string]corev1.Taint, []error) { func parseLabelRulesFromSpec(specLabelRules map[string]string) (map[string]corev1.Taint, []error) {
parsed := make(map[string]corev1.Taint) parsed := make(map[string]corev1.Taint)
var errs []error var errs []error
@@ -251,25 +251,24 @@ func parseLabelRulesFromSpec(specLabelRules map[string]string) (map[string]corev
continue continue
} }
// Парсим селектор "key=value"
partsSelector := strings.SplitN(ruleSelector, "=", 2) partsSelector := strings.SplitN(ruleSelector, "=", 2)
if len(partsSelector) != 2 { // Должен быть знак = if len(partsSelector) != 2 {
errs = append(errs, fmt.Errorf("invalid rule selector format '%s': missing '='", ruleSelector)) errs = append(errs, fmt.Errorf("invalid rule selector format '%s': missing '='", ruleSelector))
continue continue
} }
labelKey := strings.TrimSpace(partsSelector[0]) labelKey := strings.TrimSpace(partsSelector[0])
labelValue := strings.TrimSpace(partsSelector[1]) // Может быть пустым! labelValue := strings.TrimSpace(partsSelector[1])
if labelKey == "" { if labelKey == "" {
errs = append(errs, fmt.Errorf("invalid rule selector format '%s': empty label key", ruleSelector)) errs = append(errs, fmt.Errorf("invalid rule selector format '%s': empty label key", ruleSelector))
continue continue
} }
// Валидируем ключ лейбла
if msgs := apivalidation.IsQualifiedName(labelKey); len(msgs) > 0 { if msgs := apivalidation.IsQualifiedName(labelKey); len(msgs) > 0 {
errs = append(errs, fmt.Errorf("invalid label key in selector '%s': %v", ruleSelector, msgs)) errs = append(errs, fmt.Errorf("invalid label key in selector '%s': %v", ruleSelector, msgs))
continue continue
} }
// Валидируем значение лейбла (если не пустое)
if labelValue != "" { if labelValue != "" {
if msgs := apivalidation.IsValidLabelValue(labelValue); len(msgs) > 0 { if msgs := apivalidation.IsValidLabelValue(labelValue); len(msgs) > 0 {
errs = append(errs, fmt.Errorf("invalid label value in selector '%s': %v", ruleSelector, msgs)) errs = append(errs, fmt.Errorf("invalid label value in selector '%s': %v", ruleSelector, msgs))
@@ -277,7 +276,6 @@ func parseLabelRulesFromSpec(specLabelRules map[string]string) (map[string]corev
} }
} }
// Парсим строку тейнта "key=value:Effect" (используем улучшенную логику из прошлого ответа)
partsEffect := strings.SplitN(taintString, ":", 2) partsEffect := strings.SplitN(taintString, ":", 2)
if len(partsEffect) != 2 || partsEffect[1] == "" { if len(partsEffect) != 2 || partsEffect[1] == "" {
errs = append(errs, fmt.Errorf("invalid taint format for rule '%s': '%s' (missing effect)", ruleSelector, taintString)) errs = append(errs, fmt.Errorf("invalid taint format for rule '%s': '%s' (missing effect)", ruleSelector, taintString))
@@ -311,35 +309,31 @@ func parseLabelRulesFromSpec(specLabelRules map[string]string) (map[string]corev
continue continue
} }
// Все ок
taint := corev1.Taint{Key: taintKey, Value: taintValue, Effect: effect} taint := corev1.Taint{Key: taintKey, Value: taintValue, Effect: effect}
parsed[ruleSelector] = taint // Ключ = "labelKey=labelValue" parsed[ruleSelector] = taint // Key = "labelKey=labelValue"
} }
return parsed, errs return parsed, errs
} }
// calculateDesiredTaints определяет тейнты на основе лейблов ноды и правил // Defines Taints depending on node labels and rules
func calculateDesiredTaints(nodeLabels map[string]string, parsedLabelRules map[string]corev1.Taint) []corev1.Taint { func calculateDesiredTaints(nodeLabels map[string]string, parsedLabelRules map[string]corev1.Taint) []corev1.Taint {
desired := []corev1.Taint{} desired := []corev1.Taint{}
foundTaints := make(map[string]bool) // Для уникальности Key:Effect foundTaints := make(map[string]bool)
if nodeLabels == nil { if nodeLabels == nil {
nodeLabels = make(map[string]string) // Безопасность nodeLabels = make(map[string]string)
} }
for ruleSelector, taint := range parsedLabelRules { for ruleSelector, taint := range parsedLabelRules {
parts := strings.SplitN(ruleSelector, "=", 2) parts := strings.SplitN(ruleSelector, "=", 2)
if len(parts) != 2 { if len(parts) != 2 {
continue continue
} // Уже должно быть отва лидировано }
ruleKey := parts[0] ruleKey := parts[0]
ruleValue := parts[1] // Может быть пустой ruleValue := parts[1]
actualValue, exists := nodeLabels[ruleKey] actualValue, exists := nodeLabels[ruleKey]
// Логика сравнения:
// 1. Ключ лейбла должен существовать на ноде.
// 2. Значение лейбла на ноде должно ТОЧНО совпадать со значением в правиле (включая пустую строку).
if exists && actualValue == ruleValue { if exists && actualValue == ruleValue {
taintKeyEffect := fmt.Sprintf("%s:%s", taint.Key, taint.Effect) taintKeyEffect := fmt.Sprintf("%s:%s", taint.Key, taint.Effect)
if !foundTaints[taintKeyEffect] { if !foundTaints[taintKeyEffect] {
@@ -351,22 +345,19 @@ func calculateDesiredTaints(nodeLabels map[string]string, parsedLabelRules map[s
return desired return desired
} }
// TaintKeyEffect создает уникальную строку для тейнта (Key:Effect) // Creates unique string for Taint (Key:Effect)
func TaintKeyEffect(taint *corev1.Taint) string { func TaintKeyEffect(taint *corev1.Taint) string {
return fmt.Sprintf("%s:%s", taint.Key, taint.Effect) return fmt.Sprintf("%s:%s", taint.Key, taint.Effect)
} }
// mergeAndCheckTaints сравнивает текущие и желаемые тейнты, управляемые оператором. // Compares current and desired controlled Taints
// parsedLabelRules: map["labelKey=labelValue"]corev1.Taint - содержит ВСЕ валидные правила из конфига.
func mergeAndCheckTaints(currentTaints []corev1.Taint, desiredTaints []corev1.Taint, parsedLabelRules map[string]corev1.Taint) (bool, []corev1.Taint) { func mergeAndCheckTaints(currentTaints []corev1.Taint, desiredTaints []corev1.Taint, parsedLabelRules map[string]corev1.Taint) (bool, []corev1.Taint) {
// 1. Определяем, какие типы тейнтов (Key:Effect) управляются нами по всем правилам
managedTaintTypes := sets.NewString() managedTaintTypes := sets.NewString()
for _, ruleTaint := range parsedLabelRules { // Итерируем по значениям (Taint объектам) for _, ruleTaint := range parsedLabelRules {
managedTaintTypes.Insert(TaintKeyEffect(&ruleTaint)) managedTaintTypes.Insert(TaintKeyEffect(&ruleTaint))
} }
// 2. Разделяем текущие тейнты на управляемые и неуправляемые currentManagedTaints := make(map[string]corev1.Taint)
currentManagedTaints := make(map[string]corev1.Taint) // key:Effect -> Taint
unmanagedTaints := []corev1.Taint{} unmanagedTaints := []corev1.Taint{}
for _, taint := range currentTaints { for _, taint := range currentTaints {
ke := TaintKeyEffect(&taint) ke := TaintKeyEffect(&taint)
@@ -377,31 +368,27 @@ func mergeAndCheckTaints(currentTaints []corev1.Taint, desiredTaints []corev1.Ta
} }
} }
// 3. Создаем map желаемых тейнтов для быстрого поиска
desiredTaintsMap := make(map[string]corev1.Taint) // key:Effect -> Taint desiredTaintsMap := make(map[string]corev1.Taint) // key:Effect -> Taint
for _, taint := range desiredTaints { for _, taint := range desiredTaints {
// Проверка, что желаемый тейнт действительно определен в правилах (на всякий случай)
ke := TaintKeyEffect(&taint) ke := TaintKeyEffect(&taint)
if managedTaintTypes.Has(ke) { if managedTaintTypes.Has(ke) {
desiredTaintsMap[ke] = taint desiredTaintsMap[ke] = taint
} }
} }
// 4. Сравниваем управляемые текущие и желаемые
needsUpdate := false needsUpdate := false
if len(currentManagedTaints) != len(desiredTaintsMap) { if len(currentManagedTaints) != len(desiredTaintsMap) {
needsUpdate = true needsUpdate = true
} else { } else {
for ke, desiredTaint := range desiredTaintsMap { for ke, desiredTaint := range desiredTaintsMap {
currentTaint, exists := currentManagedTaints[ke] currentTaint, exists := currentManagedTaints[ke]
if !exists || currentTaint.Value != desiredTaint.Value { // Сравниваем и значения if !exists || currentTaint.Value != desiredTaint.Value {
needsUpdate = true needsUpdate = true
break break
} }
} }
} }
// 5. Собираем новый список тейнтов, если нужно обновление
if needsUpdate { if needsUpdate {
newTaints := make([]corev1.Taint, 0, len(unmanagedTaints)+len(desiredTaintsMap)) newTaints := make([]corev1.Taint, 0, len(unmanagedTaints)+len(desiredTaintsMap))
newTaints = append(newTaints, unmanagedTaints...) newTaints = append(newTaints, unmanagedTaints...)
@@ -409,7 +396,7 @@ func mergeAndCheckTaints(currentTaints []corev1.Taint, desiredTaints []corev1.Ta
for ke := range desiredTaintsMap { for ke := range desiredTaintsMap {
desiredKeys = append(desiredKeys, ke) desiredKeys = append(desiredKeys, ke)
} }
sort.Strings(desiredKeys) // Сортируем для консистентности sort.Strings(desiredKeys)
for _, ke := range desiredKeys { for _, ke := range desiredKeys {
newTaints = append(newTaints, desiredTaintsMap[ke]) newTaints = append(newTaints, desiredTaintsMap[ke])
} }
@@ -419,16 +406,14 @@ func mergeAndCheckTaints(currentTaints []corev1.Taint, desiredTaints []corev1.Ta
return false, currentTaints return false, currentTaints
} }
// updateCRDStatus обновляет статус ресурса NodeTainterConfig // Updates NodeTainterConfig status
// TODO: Вызывать эту функцию при изменении CRD или при старте/ошибках контроллера. // TODO: Call this function on CRD updates or controller start/errors
func (r *NodeTainterConfigReconciler) updateCRDStatus(ctx context.Context, config *configv1alpha1.NodeTainterConfig, status metav1.ConditionStatus, reason, message string) error { func (r *NodeTainterConfigReconciler) updateCRDStatus(ctx context.Context, config *configv1alpha1.NodeTainterConfig, status metav1.ConditionStatus, reason, message string) error {
log := log.FromContext(ctx).WithValues("config", config.Name) log := log.FromContext(ctx).WithValues("config", config.Name)
configCopy := config.DeepCopy() configCopy := config.DeepCopy()
// Устанавливаем observedGeneration
configCopy.Status.ObservedGeneration = config.Generation configCopy.Status.ObservedGeneration = config.Generation
// Обновляем Condition
newCondition := metav1.Condition{ newCondition := metav1.Condition{
Type: ConditionTypeReady, Type: ConditionTypeReady,
Status: status, Status: status,
@@ -436,16 +421,12 @@ func (r *NodeTainterConfigReconciler) updateCRDStatus(ctx context.Context, confi
Message: message, Message: message,
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
} }
// TODO: Использовать 'meta.SetStatusCondition' из 'k8s.io/apimachinery/pkg/api/meta' для правильного обновления conditions // TODO: Use 'meta.SetStatusCondition' from 'k8s.io/apimachinery/pkg/api/meta' for correct conditions updates
// Примерно так:
// meta.SetStatusCondition(&configCopy.Status.Conditions, newCondition)
// Пока просто заменяем для простоты
configCopy.Status.Conditions = []metav1.Condition{newCondition} configCopy.Status.Conditions = []metav1.Condition{newCondition}
// TODO: Обновить NodeTaintStatus на основе данных со всех нод (может быть сложно и затратно) // TODO: Update NodeTaintStatus based on data from all nodes
// configCopy.Status.NodeTaintStatus = ... // configCopy.Status.NodeTaintStatus = ...
// Используем Patch для обновления статуса
if err := r.Status().Patch(ctx, configCopy, client.MergeFrom(config)); err != nil { if err := r.Status().Patch(ctx, configCopy, client.MergeFrom(config)); err != nil {
log.Error(err, "Failed to patch NodeTainterConfig status") log.Error(err, "Failed to patch NodeTainterConfig status")
return err return err
@@ -454,26 +435,19 @@ func (r *NodeTainterConfigReconciler) updateCRDStatus(ctx context.Context, confi
return nil return nil
} }
// updateNodeTaintStatus обновляет информацию о тейнтах для конкретной ноды в статусе CRD // Updates info about Taints for correct Node in CRD status
// TODO: Эта функция в текущем виде будет вызывать конфликты, т.к. каждый Reconcile ноды
// будет пытаться перезаписать весь Status.NodeTaintStatus.
// Правильный подход: читать текущий статус CRD, обновлять только запись для текущей ноды, патчить.
// Это усложняет код, пока оставим так для демонстрации, но ЭТО НУЖНО ИСПРАВИТЬ для production.
func (r *NodeTainterConfigReconciler) updateNodeTaintStatus(ctx context.Context, node *corev1.Node, appliedTaints []corev1.Taint, errorMsg string) error { func (r *NodeTainterConfigReconciler) updateNodeTaintStatus(ctx context.Context, node *corev1.Node, appliedTaints []corev1.Taint, errorMsg string) error {
log := log.FromContext(ctx).WithValues("node", node.Name) log := log.FromContext(ctx).WithValues("node", node.Name)
var config configv1alpha1.NodeTainterConfig var config configv1alpha1.NodeTainterConfig
configKey := types.NamespacedName{Name: GlobalTaintConfigName} configKey := types.NamespacedName{Name: GlobalTaintConfigName}
// Получаем CRD еще раз, чтобы обновить его статус
if err := r.Get(ctx, configKey, &config); err != nil { if err := r.Get(ctx, configKey, &config); err != nil {
log.Error(err, "Failed to get NodeTainterConfig for status update", "configName", GlobalTaintConfigName) log.Error(err, "Failed to get NodeTainterConfig for status update", "configName", GlobalTaintConfigName)
// Не можем обновить статус, если не получили CRD
return fmt.Errorf("failed to get config %s for status update: %w", GlobalTaintConfigName, err) return fmt.Errorf("failed to get config %s for status update: %w", GlobalTaintConfigName, err)
} }
configCopy := config.DeepCopy() configCopy := config.DeepCopy()
// Ищем статус для текущей ноды
found := false found := false
nodeStatus := configv1alpha1.NodeTaintInfo{ nodeStatus := configv1alpha1.NodeTaintInfo{
NodeName: node.Name, NodeName: node.Name,
@@ -492,12 +466,10 @@ func (r *NodeTainterConfigReconciler) updateNodeTaintStatus(ctx context.Context,
configCopy.Status.NodeTaintStatus = append(configCopy.Status.NodeTaintStatus, nodeStatus) configCopy.Status.NodeTaintStatus = append(configCopy.Status.NodeTaintStatus, nodeStatus)
} }
// Сортируем для консистентности
sort.Slice(configCopy.Status.NodeTaintStatus, func(i, j int) bool { sort.Slice(configCopy.Status.NodeTaintStatus, func(i, j int) bool {
return configCopy.Status.NodeTaintStatus[i].NodeName < configCopy.Status.NodeTaintStatus[j].NodeName return configCopy.Status.NodeTaintStatus[i].NodeName < configCopy.Status.NodeTaintStatus[j].NodeName
}) })
// Патчим статус
if err := r.Status().Patch(ctx, configCopy, client.MergeFrom(&config)); err != nil { if err := r.Status().Patch(ctx, configCopy, client.MergeFrom(&config)); err != nil {
log.Error(err, "Failed to patch NodeTainterConfig status with node info", "node", node.Name) log.Error(err, "Failed to patch NodeTainterConfig status with node info", "node", node.Name)
return err return err