| /* |
| Copyright 2016 The Kubernetes Authors. |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| // package taints implements utilities for working with taints |
| package taints |
| |
| import ( |
| "fmt" |
| "strings" |
| |
| "k8s.io/api/core/v1" |
| utilerrors "k8s.io/apimachinery/pkg/util/errors" |
| "k8s.io/apimachinery/pkg/util/sets" |
| "k8s.io/apimachinery/pkg/util/validation" |
| api "k8s.io/kubernetes/pkg/apis/core" |
| "k8s.io/kubernetes/pkg/apis/core/helper" |
| ) |
| |
| const ( |
| MODIFIED = "modified" |
| TAINTED = "tainted" |
| UNTAINTED = "untainted" |
| ) |
| |
| // parseTaint parses a taint from a string, whose form must be either |
| // '<key>=<value>:<effect>', '<key>:<effect>', or '<key>'. |
| func parseTaint(st string) (v1.Taint, error) { |
| var taint v1.Taint |
| |
| var key string |
| var value string |
| var effect v1.TaintEffect |
| |
| parts := strings.Split(st, ":") |
| switch len(parts) { |
| case 1: |
| key = parts[0] |
| case 2: |
| effect = v1.TaintEffect(parts[1]) |
| if err := validateTaintEffect(effect); err != nil { |
| return taint, err |
| } |
| |
| partsKV := strings.Split(parts[0], "=") |
| if len(partsKV) > 2 { |
| return taint, fmt.Errorf("invalid taint spec: %v", st) |
| } |
| key = partsKV[0] |
| if len(partsKV) == 2 { |
| value = partsKV[1] |
| if errs := validation.IsValidLabelValue(value); len(errs) > 0 { |
| return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; ")) |
| } |
| } |
| default: |
| return taint, fmt.Errorf("invalid taint spec: %v", st) |
| } |
| |
| if errs := validation.IsQualifiedName(key); len(errs) > 0 { |
| return taint, fmt.Errorf("invalid taint spec: %v, %s", st, strings.Join(errs, "; ")) |
| } |
| |
| taint.Key = key |
| taint.Value = value |
| taint.Effect = effect |
| |
| return taint, nil |
| } |
| |
| func validateTaintEffect(effect v1.TaintEffect) error { |
| if effect != v1.TaintEffectNoSchedule && effect != v1.TaintEffectPreferNoSchedule && effect != v1.TaintEffectNoExecute { |
| return fmt.Errorf("invalid taint effect: %v, unsupported taint effect", effect) |
| } |
| |
| return nil |
| } |
| |
| // NewTaintsVar wraps []api.Taint in a struct that implements flag.Value to allow taints to be |
| // bound to command line flags. |
| func NewTaintsVar(ptr *[]api.Taint) taintsVar { |
| return taintsVar{ |
| ptr: ptr, |
| } |
| } |
| |
| type taintsVar struct { |
| ptr *[]api.Taint |
| } |
| |
| func (t taintsVar) Set(s string) error { |
| if len(s) == 0 { |
| *t.ptr = nil |
| return nil |
| } |
| sts := strings.Split(s, ",") |
| var taints []api.Taint |
| for _, st := range sts { |
| taint, err := parseTaint(st) |
| if err != nil { |
| return err |
| } |
| taints = append(taints, api.Taint{Key: taint.Key, Value: taint.Value, Effect: api.TaintEffect(taint.Effect)}) |
| } |
| *t.ptr = taints |
| return nil |
| } |
| |
| func (t taintsVar) String() string { |
| if len(*t.ptr) == 0 { |
| return "" |
| } |
| var taints []string |
| for _, taint := range *t.ptr { |
| taints = append(taints, fmt.Sprintf("%s=%s:%s", taint.Key, taint.Value, taint.Effect)) |
| } |
| return strings.Join(taints, ",") |
| } |
| |
| func (t taintsVar) Type() string { |
| return "[]api.Taint" |
| } |
| |
| // ParseTaints takes a spec which is an array and creates slices for new taints to be added, taints to be deleted. |
| // It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one. |
| func ParseTaints(spec []string) ([]v1.Taint, []v1.Taint, error) { |
| var taints, taintsToRemove []v1.Taint |
| uniqueTaints := map[v1.TaintEffect]sets.String{} |
| |
| for _, taintSpec := range spec { |
| if strings.HasSuffix(taintSpec, "-") { |
| taintToRemove, err := parseTaint(strings.TrimSuffix(taintSpec, "-")) |
| if err != nil { |
| return nil, nil, err |
| } |
| taintsToRemove = append(taintsToRemove, v1.Taint{Key: taintToRemove.Key, Effect: taintToRemove.Effect}) |
| } else { |
| newTaint, err := parseTaint(taintSpec) |
| if err != nil { |
| return nil, nil, err |
| } |
| // validate that the taint has an effect, which is required to add the taint |
| if len(newTaint.Effect) == 0 { |
| return nil, nil, fmt.Errorf("invalid taint spec: %v", taintSpec) |
| } |
| // validate if taint is unique by <key, effect> |
| if len(uniqueTaints[newTaint.Effect]) > 0 && uniqueTaints[newTaint.Effect].Has(newTaint.Key) { |
| return nil, nil, fmt.Errorf("duplicated taints with the same key and effect: %v", newTaint) |
| } |
| // add taint to existingTaints for uniqueness check |
| if len(uniqueTaints[newTaint.Effect]) == 0 { |
| uniqueTaints[newTaint.Effect] = sets.String{} |
| } |
| uniqueTaints[newTaint.Effect].Insert(newTaint.Key) |
| |
| taints = append(taints, newTaint) |
| } |
| } |
| return taints, taintsToRemove, nil |
| } |
| |
| // ReorganizeTaints returns the updated set of taints, taking into account old taints that were not updated, |
| // old taints that were updated, old taints that were deleted, and new taints. |
| func ReorganizeTaints(node *v1.Node, overwrite bool, taintsToAdd []v1.Taint, taintsToRemove []v1.Taint) (string, []v1.Taint, error) { |
| newTaints := append([]v1.Taint{}, taintsToAdd...) |
| oldTaints := node.Spec.Taints |
| // add taints that already existing but not updated to newTaints |
| added := addTaints(oldTaints, &newTaints) |
| allErrs, deleted := deleteTaints(taintsToRemove, &newTaints) |
| if (added && deleted) || overwrite { |
| return MODIFIED, newTaints, utilerrors.NewAggregate(allErrs) |
| } else if added { |
| return TAINTED, newTaints, utilerrors.NewAggregate(allErrs) |
| } |
| return UNTAINTED, newTaints, utilerrors.NewAggregate(allErrs) |
| } |
| |
| // deleteTaints deletes the given taints from the node's taintlist. |
| func deleteTaints(taintsToRemove []v1.Taint, newTaints *[]v1.Taint) ([]error, bool) { |
| allErrs := []error{} |
| var removed bool |
| for _, taintToRemove := range taintsToRemove { |
| removed = false |
| if len(taintToRemove.Effect) > 0 { |
| *newTaints, removed = DeleteTaint(*newTaints, &taintToRemove) |
| } else { |
| *newTaints, removed = DeleteTaintsByKey(*newTaints, taintToRemove.Key) |
| } |
| if !removed { |
| allErrs = append(allErrs, fmt.Errorf("taint %q not found", taintToRemove.ToString())) |
| } |
| } |
| return allErrs, removed |
| } |
| |
| // addTaints adds the newTaints list to existing ones and updates the newTaints List. |
| // TODO: This needs a rewrite to take only the new values instead of appended newTaints list to be consistent. |
| func addTaints(oldTaints []v1.Taint, newTaints *[]v1.Taint) bool { |
| for _, oldTaint := range oldTaints { |
| existsInNew := false |
| for _, taint := range *newTaints { |
| if taint.MatchTaint(&oldTaint) { |
| existsInNew = true |
| break |
| } |
| } |
| if !existsInNew { |
| *newTaints = append(*newTaints, oldTaint) |
| } |
| } |
| return len(oldTaints) != len(*newTaints) |
| } |
| |
| // CheckIfTaintsAlreadyExists checks if the node already has taints that we want to add and returns a string with taint keys. |
| func CheckIfTaintsAlreadyExists(oldTaints []v1.Taint, taints []v1.Taint) string { |
| var existingTaintList = make([]string, 0) |
| for _, taint := range taints { |
| for _, oldTaint := range oldTaints { |
| if taint.Key == oldTaint.Key && taint.Effect == oldTaint.Effect { |
| existingTaintList = append(existingTaintList, taint.Key) |
| } |
| } |
| } |
| return strings.Join(existingTaintList, ",") |
| } |
| |
| // DeleteTaintsByKey removes all the taints that have the same key to given taintKey |
| func DeleteTaintsByKey(taints []v1.Taint, taintKey string) ([]v1.Taint, bool) { |
| newTaints := []v1.Taint{} |
| deleted := false |
| for i := range taints { |
| if taintKey == taints[i].Key { |
| deleted = true |
| continue |
| } |
| newTaints = append(newTaints, taints[i]) |
| } |
| return newTaints, deleted |
| } |
| |
| // DeleteTaint removes all the taints that have the same key and effect to given taintToDelete. |
| func DeleteTaint(taints []v1.Taint, taintToDelete *v1.Taint) ([]v1.Taint, bool) { |
| newTaints := []v1.Taint{} |
| deleted := false |
| for i := range taints { |
| if taintToDelete.MatchTaint(&taints[i]) { |
| deleted = true |
| continue |
| } |
| newTaints = append(newTaints, taints[i]) |
| } |
| return newTaints, deleted |
| } |
| |
| // RemoveTaint tries to remove a taint from annotations list. Returns a new copy of updated Node and true if something was updated |
| // false otherwise. |
| func RemoveTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) { |
| newNode := node.DeepCopy() |
| nodeTaints := newNode.Spec.Taints |
| if len(nodeTaints) == 0 { |
| return newNode, false, nil |
| } |
| |
| if !TaintExists(nodeTaints, taint) { |
| return newNode, false, nil |
| } |
| |
| newTaints, _ := DeleteTaint(nodeTaints, taint) |
| newNode.Spec.Taints = newTaints |
| return newNode, true, nil |
| } |
| |
| // AddOrUpdateTaint tries to add a taint to annotations list. Returns a new copy of updated Node and true if something was updated |
| // false otherwise. |
| func AddOrUpdateTaint(node *v1.Node, taint *v1.Taint) (*v1.Node, bool, error) { |
| newNode := node.DeepCopy() |
| nodeTaints := newNode.Spec.Taints |
| |
| var newTaints []v1.Taint |
| updated := false |
| for i := range nodeTaints { |
| if taint.MatchTaint(&nodeTaints[i]) { |
| if helper.Semantic.DeepEqual(*taint, nodeTaints[i]) { |
| return newNode, false, nil |
| } |
| newTaints = append(newTaints, *taint) |
| updated = true |
| continue |
| } |
| |
| newTaints = append(newTaints, nodeTaints[i]) |
| } |
| |
| if !updated { |
| newTaints = append(newTaints, *taint) |
| } |
| |
| newNode.Spec.Taints = newTaints |
| return newNode, true, nil |
| } |
| |
| // TaintExists checks if the given taint exists in list of taints. Returns true if exists false otherwise. |
| func TaintExists(taints []v1.Taint, taintToFind *v1.Taint) bool { |
| for _, taint := range taints { |
| if taint.MatchTaint(taintToFind) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func TaintSetDiff(t1, t2 []v1.Taint) (taintsToAdd []*v1.Taint, taintsToRemove []*v1.Taint) { |
| for _, taint := range t1 { |
| if !TaintExists(t2, &taint) { |
| t := taint |
| taintsToAdd = append(taintsToAdd, &t) |
| } |
| } |
| |
| for _, taint := range t2 { |
| if !TaintExists(t1, &taint) { |
| t := taint |
| taintsToRemove = append(taintsToRemove, &t) |
| } |
| } |
| |
| return |
| } |
| |
| func TaintSetFilter(taints []v1.Taint, fn func(*v1.Taint) bool) []v1.Taint { |
| res := []v1.Taint{} |
| |
| for _, taint := range taints { |
| if fn(&taint) { |
| res = append(res, taint) |
| } |
| } |
| |
| return res |
| } |