github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/preflight/analyzer/kb_taint.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package analyzer 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "strings" 26 27 "helm.sh/helm/v3/pkg/cli" 28 "helm.sh/helm/v3/pkg/getter" 29 30 analyze "github.com/replicatedhq/troubleshoot/pkg/analyze" 31 "helm.sh/helm/v3/pkg/cli/values" 32 v1 "k8s.io/api/core/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 35 preflightv1beta2 "github.com/1aal/kubeblocks/externalapis/preflight/v1beta2" 36 "github.com/1aal/kubeblocks/pkg/cli/preflight/util" 37 ) 38 39 const ( 40 NodesPath = "cluster-resources/nodes.json" 41 NodesErrorPath = "cluster-resources/nodes-errors.json" 42 Tolerations = "tolerations" 43 KubeBlocks = "kubeblocks" 44 ) 45 46 type AnalyzeTaintClassByKb struct { 47 analyzer *preflightv1beta2.KBTaintAnalyze 48 HelmOpts *values.Options 49 } 50 51 func (a *AnalyzeTaintClassByKb) Title() string { 52 return util.TitleOrDefault(a.analyzer.AnalyzeMeta, "KubeBlocks Taints") 53 } 54 55 func (a *AnalyzeTaintClassByKb) GetAnalyzer() *preflightv1beta2.KBTaintAnalyze { 56 return a.analyzer 57 } 58 59 func (a *AnalyzeTaintClassByKb) IsExcluded() (bool, error) { 60 return util.IsExcluded(a.analyzer.Exclude) 61 } 62 63 func (a *AnalyzeTaintClassByKb) Analyze(getFile GetCollectedFileContents, findFiles GetChildCollectedFileContents) ([]*analyze.AnalyzeResult, error) { 64 result, err := a.analyzeTaint(getFile, findFiles) 65 if err != nil { 66 return []*analyze.AnalyzeResult{result}, err 67 } 68 result.Strict = a.analyzer.Strict.BoolOrDefaultFalse() 69 return []*analyze.AnalyzeResult{result}, nil 70 } 71 72 func (a *AnalyzeTaintClassByKb) analyzeTaint(getFile GetCollectedFileContents, findFiles GetChildCollectedFileContents) (*analyze.AnalyzeResult, error) { 73 nodesData, err := getFile(NodesPath) 74 if err != nil { 75 return newFailedResultWithMessage(a.Title(), fmt.Sprintf("get nodes from jsonfile failed, err:%v", err)), err 76 } 77 78 nodesErrorData, err := getFile(NodesErrorPath) 79 if err != nil && nodesErrorData != nil && len(nodesErrorData) > 0 && len(nodesData) == 0 { 80 var values []string 81 err = json.Unmarshal(nodesErrorData, &values) 82 if err != nil || len(values) == 0 { 83 return newFailedResultWithMessage(a.Title(), fmt.Sprintf("get nodes from k8s failed, err:%v", nodesErrorData)), err 84 } 85 return newFailedResultWithMessage(a.Title(), fmt.Sprintf("get nodes from k8s failed, err:%v", values[0])), nil 86 } 87 88 var nodes v1.NodeList 89 if err = json.Unmarshal(nodesData, &nodes); err != nil { 90 return newFailedResultWithMessage(a.Title(), fmt.Sprintf("unmarshal nodes jsonfile failed, err:%v", err)), err 91 } 92 93 err = a.generateTolerations() 94 if err != nil { 95 return newFailedResultWithMessage(a.Title(), fmt.Sprintf("get tolerations failed, err:%v", err)), err 96 } 97 return a.doAnalyzeTaint(nodes) 98 } 99 100 func (a *AnalyzeTaintClassByKb) doAnalyzeTaint(nodes v1.NodeList) (*analyze.AnalyzeResult, error) { 101 taintFailResult := []string{} 102 for _, node := range nodes.Items { 103 if node.Spec.Taints == nil || len(node.Spec.Taints) == 0 { 104 return newAnalyzeResult(a.Title(), PassType, a.analyzer.Outcomes), nil 105 } 106 } 107 108 if a.analyzer.TolerationsMap == nil || len(a.analyzer.TolerationsMap) == 0 { 109 return newAnalyzeResult(a.Title(), FailType, a.analyzer.Outcomes), nil 110 } 111 112 for k, tolerations := range a.analyzer.TolerationsMap { 113 count := 0 114 for _, node := range nodes.Items { 115 if isTolerableTaints(node.Spec.Taints, tolerations) { 116 count++ 117 } 118 } 119 if count <= 0 { 120 taintFailResult = append(taintFailResult, k) 121 } 122 } 123 if len(taintFailResult) > 0 { 124 result := newAnalyzeResult(a.Title(), FailType, a.analyzer.Outcomes) 125 result.Message += fmt.Sprintf(" Taint check failed components: %s", strings.Join(taintFailResult, ", ")) 126 return result, nil 127 } 128 return newAnalyzeResult(a.Title(), PassType, a.analyzer.Outcomes), nil 129 } 130 131 func (a *AnalyzeTaintClassByKb) generateTolerations() error { 132 tolerations := map[string][]v1.Toleration{} 133 if a.HelmOpts != nil { 134 optsMap, err := a.getHelmValues() 135 if err != nil { 136 return err 137 } 138 getTolerationsMap(optsMap, "", tolerations) 139 } 140 a.analyzer.TolerationsMap = tolerations 141 return nil 142 } 143 144 func (a *AnalyzeTaintClassByKb) getHelmValues() (map[string]interface{}, error) { 145 settings := cli.New() 146 p := getter.All(settings) 147 vals, err := a.HelmOpts.MergeValues(p) 148 if err != nil { 149 return nil, err 150 } 151 return vals, nil 152 } 153 154 func getTolerationsMap(tolerationData map[string]interface{}, addonName string, tolerationsMap map[string][]v1.Toleration) { 155 var tmpTolerationList []v1.Toleration 156 var tmpToleration v1.Toleration 157 158 for k, v := range tolerationData { 159 if k == Tolerations { 160 tolerationList := v.([]interface{}) 161 tmpTolerationList = []v1.Toleration{} 162 for _, t := range tolerationList { 163 toleration := t.(map[string]interface{}) 164 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(toleration, &tmpToleration); err != nil { 165 continue 166 } 167 tmpTolerationList = append(tmpTolerationList, tmpToleration) 168 } 169 if addonName == "" { 170 addonName = KubeBlocks 171 } 172 tolerationsMap[addonName] = tmpTolerationList 173 continue 174 } 175 176 switch v := v.(type) { 177 case map[string]interface{}: 178 if addonName != "" { 179 addonName += "." 180 } 181 addonName += k 182 getTolerationsMap(v, addonName, tolerationsMap) 183 default: 184 continue 185 } 186 } 187 } 188 189 func isTolerableTaints(taints []v1.Taint, tolerations []v1.Toleration) bool { 190 tolerableCount := 0 191 for _, taint := range taints { 192 // check only on taints that have effect NoSchedule 193 if taint.Effect != v1.TaintEffectNoSchedule { 194 continue 195 } 196 for _, toleration := range tolerations { 197 if toleration.ToleratesTaint(&taint) { 198 tolerableCount++ 199 break 200 } 201 } 202 } 203 return tolerableCount >= len(taints) 204 }