git.frostfs.info/TrueCloudLab/frostfs-sdk-go@v0.0.0-20241022124111-5361f0ecebd3/netmap/filter.go (about)

     1  package netmap
     2  
     3  import (
     4  	"fmt"
     5  	"strconv"
     6  	"strings"
     7  
     8  	"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/netmap"
     9  )
    10  
    11  // mainFilterName is a name of the filter
    12  // which points to the whole netmap.
    13  const mainFilterName = "*"
    14  
    15  const likeWildcard = "*"
    16  
    17  // processFilters processes filters and returns error is any of them is invalid.
    18  func (c *context) processFilters(p PlacementPolicy) error {
    19  	for i := range p.filters {
    20  		if err := c.processFilter(p.filters[i], true); err != nil {
    21  			return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].GetName(), err)
    22  		}
    23  	}
    24  
    25  	return nil
    26  }
    27  
    28  func (c *context) processFilter(f netmap.Filter, top bool) error {
    29  	fName := f.GetName()
    30  	if fName == mainFilterName {
    31  		return fmt.Errorf("%w: '%s' is reserved", errInvalidFilterName, mainFilterName)
    32  	}
    33  
    34  	if top && fName == "" {
    35  		return errUnnamedTopFilter
    36  	}
    37  
    38  	if !top && fName != "" && c.processedFilters[fName] == nil {
    39  		return errFilterNotFound
    40  	}
    41  
    42  	inner := f.GetFilters()
    43  
    44  	switch op := f.GetOp(); op {
    45  	case netmap.AND, netmap.OR, netmap.NOT:
    46  		for i := range inner {
    47  			if err := c.processFilter(inner[i], false); err != nil {
    48  				return fmt.Errorf("process inner filter #%d: %w", i, err)
    49  			}
    50  		}
    51  	default:
    52  		if len(inner) != 0 {
    53  			return errNonEmptyFilters
    54  		} else if !top && fName != "" { // named reference
    55  			return nil
    56  		}
    57  
    58  		switch op {
    59  		case netmap.EQ, netmap.NE, netmap.LIKE:
    60  		case netmap.GT, netmap.GE, netmap.LT, netmap.LE:
    61  			val := f.GetValue()
    62  			n, err := strconv.ParseUint(val, 10, 64)
    63  			if err != nil {
    64  				return fmt.Errorf("%w: '%s'", errInvalidNumber, f.GetValue())
    65  			}
    66  
    67  			c.numCache[val] = n
    68  		default:
    69  			return fmt.Errorf("%w: %s", errInvalidFilterOp, op)
    70  		}
    71  	}
    72  
    73  	if top {
    74  		c.processedFilters[fName] = &f
    75  	}
    76  
    77  	return nil
    78  }
    79  
    80  // match matches f against b. It returns no errors because
    81  // filter should have been parsed during context creation
    82  // and missing node properties are considered as a regular fail.
    83  func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
    84  	switch f.GetOp() {
    85  	case netmap.NOT:
    86  		inner := f.GetFilters()
    87  		fSub := &inner[0]
    88  		if name := inner[0].GetName(); name != "" {
    89  			fSub = c.processedFilters[name]
    90  		}
    91  		return !c.match(fSub, b)
    92  	case netmap.AND, netmap.OR:
    93  		inner := f.GetFilters()
    94  		for i := range inner {
    95  			fSub := &inner[i]
    96  			if name := inner[i].GetName(); name != "" {
    97  				fSub = c.processedFilters[name]
    98  			}
    99  
   100  			ok := c.match(fSub, b)
   101  			if ok == (f.GetOp() == netmap.OR) {
   102  				return ok
   103  			}
   104  		}
   105  
   106  		return f.GetOp() == netmap.AND
   107  	default:
   108  		return c.matchKeyValue(f, b)
   109  	}
   110  }
   111  
   112  func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool {
   113  	switch op := f.GetOp(); op {
   114  	case netmap.EQ:
   115  		return b.Attribute(f.GetKey()) == f.GetValue()
   116  	case netmap.LIKE:
   117  		str, prefix := strings.CutPrefix(f.GetValue(), likeWildcard)
   118  		str, suffix := strings.CutSuffix(str, likeWildcard)
   119  		if prefix && suffix {
   120  			return strings.Contains(b.Attribute(f.GetKey()), str)
   121  		}
   122  		if prefix && !suffix {
   123  			return strings.HasSuffix(b.Attribute(f.GetKey()), str)
   124  		}
   125  		if !prefix && suffix {
   126  			return strings.HasPrefix(b.Attribute(f.GetKey()), str)
   127  		}
   128  		return b.Attribute(f.GetKey()) == f.GetValue()
   129  	case netmap.NE:
   130  		return b.Attribute(f.GetKey()) != f.GetValue()
   131  	default:
   132  		var attr uint64
   133  
   134  		switch f.GetKey() {
   135  		case attrPrice:
   136  			attr = b.Price()
   137  		case attrCapacity:
   138  			attr = b.capacity()
   139  		default:
   140  			var err error
   141  
   142  			attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64)
   143  			if err != nil {
   144  				// Note: because filters are somewhat independent from nodes attributes,
   145  				// We don't report an error here, and fail filter instead.
   146  				return false
   147  			}
   148  		}
   149  
   150  		switch op {
   151  		case netmap.GT:
   152  			return attr > c.numCache[f.GetValue()]
   153  		case netmap.GE:
   154  			return attr >= c.numCache[f.GetValue()]
   155  		case netmap.LT:
   156  			return attr < c.numCache[f.GetValue()]
   157  		case netmap.LE:
   158  			return attr <= c.numCache[f.GetValue()]
   159  		default:
   160  			// do nothing and return false
   161  		}
   162  	}
   163  	// will not happen if context was created from f (maybe panic?)
   164  	return false
   165  }