github.com/matrixorigin/matrixone@v1.2.0/pkg/clusterservice/selector.go (about) 1 // Copyright 2023 Matrix Origin 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package clusterservice 16 17 import ( 18 "regexp" 19 "strings" 20 21 "github.com/matrixorigin/matrixone/pkg/pb/metadata" 22 ) 23 24 // NewSelector return a default selector, this empty selector 25 // will select all instances. For CN type, it only select the 26 // ones whose work state is working. 27 func NewSelector() Selector { 28 return Selector{} 29 } 30 31 // NewSelectAll will return all CN services. 32 func NewSelectAll() Selector { 33 return Selector{ 34 all: true, 35 } 36 } 37 38 // NewServiceIDSelector 39 func NewServiceIDSelector(serviceID string) Selector { 40 return NewSelector().SelectByServiceID(serviceID) 41 } 42 43 // SelectByServiceID select service by service ID 44 func (s Selector) SelectByServiceID(serviceID string) Selector { 45 s.byServiceID = true 46 s.serviceID = serviceID 47 return s 48 } 49 50 // SelectByLabel select service by label 51 func (s Selector) SelectByLabel(labels map[string]string, op Op) Selector { 52 s.byLabel = true 53 s.labelOp = op 54 s.labels = labels 55 return s 56 } 57 58 // SelectWithoutLabel updates the selector by removing the labels. 59 func (s Selector) SelectWithoutLabel(labels map[string]string) Selector { 60 for labelKey, labelValue := range s.labels { 61 v, ok := labels[labelKey] 62 if ok && v == labelValue { 63 delete(s.labels, labelKey) 64 } 65 } 66 return s 67 } 68 69 // LabelNum returns the number of labels in this selector. 70 func (s Selector) LabelNum() int { 71 return len(s.labels) 72 } 73 74 func (s Selector) filterCN(cn metadata.CNService) bool { 75 return s.filter(cn.ServiceID, cn.Labels) 76 } 77 78 func (s Selector) filterTN(tn metadata.TNService) bool { 79 return s.filter(tn.ServiceID, tn.Labels) 80 } 81 82 func (s Selector) filter(serviceID string, labels map[string]metadata.LabelList) bool { 83 if s.byServiceID { 84 return serviceID == s.serviceID 85 } 86 return s.Match(labels) 87 } 88 89 // Match check if @labels match the selector. 90 // At most case, the selector's labels is subset of @labels 91 // 92 // return true, if @labels is empty. 93 func (s Selector) Match(labels map[string]metadata.LabelList) bool { 94 if s.byLabel { 95 switch s.labelOp { 96 case Contain: 97 if s.emptyLabel() && len(labels) > 0 { 98 return false 99 } 100 // If it is an empty CN server, return true. 101 if len(labels) == 0 { 102 return true 103 } 104 return labelContains(s.labels, labels, nil) 105 106 case EQ: 107 if s.emptyLabel() && len(labels) > 0 { 108 return false 109 } 110 // If it is an empty CN server, return true. 111 if len(labels) == 0 { 112 return true 113 } 114 return labelEQ(s.labels, labels) 115 116 case EQ_Globbing: 117 if s.emptyLabel() && len(labels) > 0 { 118 return false 119 } 120 // If it is an empty CN server, return true. 121 if len(labels) == 0 { 122 return true 123 } 124 return labelEQGlobbing(s.labels, labels) 125 126 default: 127 return false 128 } 129 } 130 return true 131 } 132 133 func (s Selector) emptyLabel() bool { 134 if s.labels == nil || len(s.labels) == 0 { 135 return true 136 } 137 return false 138 } 139 140 // labelContains checks if the source labels contained by destination labels. 141 // The source labels are requested from client, and the destination labels are 142 // defined on CN servers. So this method is used to check if the request 143 // can be sent to this CN server. 144 func labelContains( 145 src map[string]string, dst map[string]metadata.LabelList, comp func(string, string) bool, 146 ) bool { 147 for k, v := range src { 148 values, ok := dst[k] 149 if !ok { 150 return false 151 } 152 if !containLabel(v, values.Labels, comp) { 153 return false 154 } 155 } 156 return true 157 } 158 159 func labelEQ(src map[string]string, dst map[string]metadata.LabelList) bool { 160 l1 := len(src) 161 l2 := len(dst) 162 if l1 != l2 { 163 return false 164 } 165 return labelContains(src, dst, nil) 166 } 167 168 func labelEQGlobbing(src map[string]string, dst map[string]metadata.LabelList) bool { 169 l1 := len(src) 170 l2 := len(dst) 171 if l1 != l2 { 172 return false 173 } 174 return labelContains(src, dst, globbing) 175 } 176 177 func containLabel(src string, dst []string, comp func(string, string) bool) bool { 178 if comp == nil { 179 comp = strings.EqualFold 180 } 181 for _, l := range dst { 182 if comp(src, l) { 183 return true 184 } 185 } 186 return false 187 } 188 189 func globbing(src, dst string) bool { 190 if dst == "" { 191 return false 192 } 193 // dst is '*', or starts with '*'. 194 if strings.HasPrefix(dst, "*") { 195 dst = "." + dst 196 } else { 197 for i := 1; i < len(dst); i++ { 198 if dst[i] == '*' && dst[i-1] != '.' { 199 dst = dst[:i] + "." + dst[i:] 200 } 201 } 202 } 203 if dst[0] != '^' { 204 dst = "^" + dst 205 } 206 if dst[len(dst)-1] != '$' { 207 dst = dst + "$" 208 } 209 ok, err := regexp.MatchString(dst, src) 210 if err != nil { 211 return false 212 } 213 return ok 214 }