comments fixes
This commit is contained in:
@@ -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
|
||||||
|
Reference in New Issue
Block a user