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  }