forked from TrueCloudLab/frostfs-sdk-go
165 lines
3.9 KiB
Go
165 lines
3.9 KiB
Go
package netmap
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/netmap"
|
|
)
|
|
|
|
// mainFilterName is a name of the filter
|
|
// which points to the whole netmap.
|
|
const mainFilterName = "*"
|
|
|
|
const likeWildcard = "*"
|
|
|
|
// processFilters processes filters and returns error is any of them is invalid.
|
|
func (c *context) processFilters(p PlacementPolicy) error {
|
|
for i := range p.filters {
|
|
if err := c.processFilter(p.filters[i], true); err != nil {
|
|
return fmt.Errorf("process filter #%d (%s): %w", i, p.filters[i].GetName(), err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (c *context) processFilter(f netmap.Filter, top bool) error {
|
|
fName := f.GetName()
|
|
if fName == mainFilterName {
|
|
return fmt.Errorf("%w: '%s' is reserved", errInvalidFilterName, mainFilterName)
|
|
}
|
|
|
|
if top && fName == "" {
|
|
return errUnnamedTopFilter
|
|
}
|
|
|
|
if !top && fName != "" && c.processedFilters[fName] == nil {
|
|
return errFilterNotFound
|
|
}
|
|
|
|
inner := f.GetFilters()
|
|
|
|
switch op := f.GetOp(); op {
|
|
case netmap.AND, netmap.OR, netmap.NOT:
|
|
for i := range inner {
|
|
if err := c.processFilter(inner[i], false); err != nil {
|
|
return fmt.Errorf("process inner filter #%d: %w", i, err)
|
|
}
|
|
}
|
|
default:
|
|
if len(inner) != 0 {
|
|
return errNonEmptyFilters
|
|
} else if !top && fName != "" { // named reference
|
|
return nil
|
|
}
|
|
|
|
switch op {
|
|
case netmap.EQ, netmap.NE, netmap.LIKE:
|
|
case netmap.GT, netmap.GE, netmap.LT, netmap.LE:
|
|
val := f.GetValue()
|
|
n, err := strconv.ParseUint(val, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("%w: '%s'", errInvalidNumber, f.GetValue())
|
|
}
|
|
|
|
c.numCache[val] = n
|
|
default:
|
|
return fmt.Errorf("%w: %s", errInvalidFilterOp, op)
|
|
}
|
|
}
|
|
|
|
if top {
|
|
c.processedFilters[fName] = &f
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// match matches f against b. It returns no errors because
|
|
// filter should have been parsed during context creation
|
|
// and missing node properties are considered as a regular fail.
|
|
func (c *context) match(f *netmap.Filter, b NodeInfo) bool {
|
|
switch f.GetOp() {
|
|
case netmap.NOT:
|
|
inner := f.GetFilters()
|
|
fSub := &inner[0]
|
|
if name := inner[0].GetName(); name != "" {
|
|
fSub = c.processedFilters[name]
|
|
}
|
|
return !c.match(fSub, b)
|
|
case netmap.AND, netmap.OR:
|
|
inner := f.GetFilters()
|
|
for i := range inner {
|
|
fSub := &inner[i]
|
|
if name := inner[i].GetName(); name != "" {
|
|
fSub = c.processedFilters[name]
|
|
}
|
|
|
|
ok := c.match(fSub, b)
|
|
if ok == (f.GetOp() == netmap.OR) {
|
|
return ok
|
|
}
|
|
}
|
|
|
|
return f.GetOp() == netmap.AND
|
|
default:
|
|
return c.matchKeyValue(f, b)
|
|
}
|
|
}
|
|
|
|
func (c *context) matchKeyValue(f *netmap.Filter, b NodeInfo) bool {
|
|
switch op := f.GetOp(); op {
|
|
case netmap.EQ:
|
|
return b.Attribute(f.GetKey()) == f.GetValue()
|
|
case netmap.LIKE:
|
|
str, prefix := strings.CutPrefix(f.GetValue(), likeWildcard)
|
|
str, suffix := strings.CutSuffix(str, likeWildcard)
|
|
if prefix && suffix {
|
|
return strings.Contains(b.Attribute(f.GetKey()), str)
|
|
}
|
|
if prefix && !suffix {
|
|
return strings.HasSuffix(b.Attribute(f.GetKey()), str)
|
|
}
|
|
if !prefix && suffix {
|
|
return strings.HasPrefix(b.Attribute(f.GetKey()), str)
|
|
}
|
|
return b.Attribute(f.GetKey()) == f.GetValue()
|
|
case netmap.NE:
|
|
return b.Attribute(f.GetKey()) != f.GetValue()
|
|
default:
|
|
var attr uint64
|
|
|
|
switch f.GetKey() {
|
|
case attrPrice:
|
|
attr = b.Price()
|
|
case attrCapacity:
|
|
attr = b.capacity()
|
|
default:
|
|
var err error
|
|
|
|
attr, err = strconv.ParseUint(b.Attribute(f.GetKey()), 10, 64)
|
|
if err != nil {
|
|
// Note: because filters are somewhat independent from nodes attributes,
|
|
// We don't report an error here, and fail filter instead.
|
|
return false
|
|
}
|
|
}
|
|
|
|
switch op {
|
|
case netmap.GT:
|
|
return attr > c.numCache[f.GetValue()]
|
|
case netmap.GE:
|
|
return attr >= c.numCache[f.GetValue()]
|
|
case netmap.LT:
|
|
return attr < c.numCache[f.GetValue()]
|
|
case netmap.LE:
|
|
return attr <= c.numCache[f.GetValue()]
|
|
default:
|
|
// do nothing and return false
|
|
}
|
|
}
|
|
// will not happen if context was created from f (maybe panic?)
|
|
return false
|
|
}
|