github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/aggregator/sharding/shard_set.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package sharding
    22  
    23  import (
    24  	"errors"
    25  	"fmt"
    26  	"regexp"
    27  	"strconv"
    28  )
    29  
    30  const (
    31  	defaultNumShards = 1024
    32  )
    33  
    34  var (
    35  	// Shard range is expected to be provided in the form of startShard..endShard.
    36  	// Both startShard and endShard are inclusive. An example shard range is 0..63.
    37  	rangeRegexp = regexp.MustCompile(`^([0-9]+)(\.\.([0-9]+))?$`)
    38  
    39  	errInvalidShard = errors.New("invalid shard")
    40  )
    41  
    42  // ParseShardSet parses a shard set from the input string.
    43  func ParseShardSet(s string) (ShardSet, error) {
    44  	ss := make(ShardSet, defaultNumShards)
    45  	if err := ss.ParseRange(s); err != nil {
    46  		return nil, err
    47  	}
    48  	return ss, nil
    49  }
    50  
    51  // MustParseShardSet parses a shard set from the input string, and panics
    52  // if parsing is unsuccessful.
    53  func MustParseShardSet(s string) ShardSet {
    54  	ss, err := ParseShardSet(s)
    55  	if err == nil {
    56  		return ss
    57  	}
    58  	panic(fmt.Errorf("unable to parse shard set from %s: %v", s, err))
    59  }
    60  
    61  // ShardSet is a collection of shards organized as a set.
    62  // The shards contained in the set can be discontinuous.
    63  type ShardSet map[uint32]struct{}
    64  
    65  // UnmarshalYAML unmarshals YAML into a shard set.
    66  // The following formats are supported:
    67  // * StartShard..EndShard, e.g., 0..63.
    68  // * Single shard, e.g., 5.
    69  // * Array containing shard ranges and single shards.
    70  func (ss *ShardSet) UnmarshalYAML(f func(interface{}) error) error {
    71  	*ss = make(ShardSet, defaultNumShards)
    72  
    73  	// If YAML contains a single string, attempt to parse out a single range.
    74  	var s string
    75  	if err := f(&s); err == nil {
    76  		return ss.ParseRange(s)
    77  	}
    78  
    79  	// Otherwise try to parse out a list of ranges or single shards.
    80  	var a []interface{}
    81  	if err := f(&a); err == nil {
    82  		for _, v := range a {
    83  			switch c := v.(type) {
    84  			case string:
    85  				if err := ss.ParseRange(c); err != nil {
    86  					return err
    87  				}
    88  			case int:
    89  				ss.Add(uint32(c))
    90  			default:
    91  				return fmt.Errorf("unexpected range %v", c)
    92  			}
    93  		}
    94  		return nil
    95  	}
    96  
    97  	// Otherwise try to parse out a single shard.
    98  	var n int
    99  	if err := f(&n); err == nil {
   100  		ss.Add(uint32(n))
   101  		return nil
   102  	}
   103  
   104  	return errInvalidShard
   105  }
   106  
   107  // Contains returns true if the shard set contains the given shard.
   108  func (ss ShardSet) Contains(p uint32) bool {
   109  	_, found := ss[p]
   110  	return found
   111  }
   112  
   113  // Add adds the shard to the set.
   114  func (ss ShardSet) Add(p uint32) {
   115  	ss[p] = struct{}{}
   116  }
   117  
   118  // AddBetween adds shards between the given min (inclusive) and max (exclusive).
   119  func (ss ShardSet) AddBetween(minInclusive, maxExclusive uint32) {
   120  	for i := minInclusive; i < maxExclusive; i++ {
   121  		ss.Add(i)
   122  	}
   123  }
   124  
   125  // ParseRange parses a range of shards and adds them to the set.
   126  func (ss ShardSet) ParseRange(s string) error {
   127  	rangeMatches := rangeRegexp.FindStringSubmatch(s)
   128  	if len(rangeMatches) != 0 {
   129  		return ss.addRange(rangeMatches)
   130  	}
   131  
   132  	return fmt.Errorf("invalid range '%s'", s)
   133  }
   134  
   135  func (ss ShardSet) addRange(matches []string) error {
   136  	min, err := strconv.ParseInt(matches[1], 10, 32)
   137  	if err != nil {
   138  		return err
   139  	}
   140  
   141  	max := min
   142  	if matches[3] != "" {
   143  		max, err = strconv.ParseInt(matches[3], 10, 32)
   144  		if err != nil {
   145  			return err
   146  		}
   147  	}
   148  
   149  	if min > max {
   150  		return fmt.Errorf("invalid range: %d > %d", min, max)
   151  	}
   152  
   153  	ss.AddBetween(uint32(min), uint32(max)+1)
   154  	return nil
   155  }