github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/plugins/shared/structs/attribute.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"math/big"
     6  	"strconv"
     7  	"strings"
     8  	"unicode"
     9  
    10  	"github.com/hashicorp/nomad/helper"
    11  )
    12  
    13  const (
    14  	// floatPrecision is the precision used before rounding. It is set to a high
    15  	// number to give a high chance of correctly returning equality.
    16  	floatPrecision = uint(256)
    17  )
    18  
    19  // BaseUnit is a unique base unit. All units that share the same base unit
    20  // should be comparable.
    21  type BaseUnit uint16
    22  
    23  const (
    24  	UnitScalar BaseUnit = iota
    25  	UnitByte
    26  	UnitByteRate
    27  	UnitHertz
    28  	UnitWatt
    29  )
    30  
    31  // Unit describes a unit and its multiplier over the base unit type
    32  type Unit struct {
    33  	// Name is the name of the unit (GiB, MB/s)
    34  	Name string
    35  
    36  	// Base is the base unit for the unit
    37  	Base BaseUnit
    38  
    39  	// Multiplier is the multiplier over the base unit (KiB multiplier is 1024)
    40  	Multiplier int64
    41  
    42  	// InverseMultiplier specifies that the multiplier is an inverse so:
    43  	// Base / Multiplier. For example a mW is a W/1000.
    44  	InverseMultiplier bool
    45  }
    46  
    47  // Comparable returns if two units are comparable
    48  func (u *Unit) Comparable(o *Unit) bool {
    49  	if u == nil || o == nil {
    50  		return false
    51  	}
    52  
    53  	return u.Base == o.Base
    54  }
    55  
    56  // ParseAttribute takes a string and parses it into an attribute, pulling out
    57  // units if they are specified as a suffix on a number.
    58  func ParseAttribute(input string) *Attribute {
    59  	ll := len(input)
    60  	if ll == 0 {
    61  		return &Attribute{String: helper.StringToPtr(input)}
    62  	}
    63  
    64  	// Check if the string is a number ending with potential units
    65  	var unit string
    66  	numeric := input
    67  	if unicode.IsLetter(rune(input[ll-1])) {
    68  		// Try suffix matching
    69  		for _, u := range lengthSortedUnits {
    70  			if strings.HasSuffix(input, u) {
    71  				unit = u
    72  				break
    73  			}
    74  		}
    75  
    76  		// Check if we know about the unit.
    77  		if len(unit) != 0 {
    78  			numeric = strings.TrimSpace(strings.TrimSuffix(input, unit))
    79  		}
    80  	}
    81  
    82  	// Try to parse as an int
    83  	i, err := strconv.ParseInt(numeric, 10, 64)
    84  	if err == nil {
    85  		return &Attribute{Int: helper.Int64ToPtr(i), Unit: unit}
    86  	}
    87  
    88  	// Try to parse as a float
    89  	f, err := strconv.ParseFloat(numeric, 64)
    90  	if err == nil {
    91  		return &Attribute{Float: helper.Float64ToPtr(f), Unit: unit}
    92  	}
    93  
    94  	// Try to parse as a bool
    95  	b, err := strconv.ParseBool(input)
    96  	if err == nil {
    97  		return &Attribute{Bool: helper.BoolToPtr(b)}
    98  	}
    99  
   100  	return &Attribute{String: helper.StringToPtr(input)}
   101  }
   102  
   103  // Attribute is used to describe the value of an attribute, optionally
   104  // specifying units
   105  type Attribute struct {
   106  	// Float is the float value for the attribute
   107  	Float *float64
   108  
   109  	// Int is the int value for the attribute
   110  	Int *int64
   111  
   112  	// String is the string value for the attribute
   113  	String *string
   114  
   115  	// Bool is the bool value for the attribute
   116  	Bool *bool
   117  
   118  	// Unit is the optional unit for the set int or float value
   119  	Unit string
   120  }
   121  
   122  // NewStringAttribute returns a new string attribute.
   123  func NewStringAttribute(s string) *Attribute {
   124  	return &Attribute{
   125  		String: helper.StringToPtr(s),
   126  	}
   127  }
   128  
   129  // NewBoolAttribute returns a new boolean attribute.
   130  func NewBoolAttribute(b bool) *Attribute {
   131  	return &Attribute{
   132  		Bool: helper.BoolToPtr(b),
   133  	}
   134  }
   135  
   136  // NewIntergerAttribute returns a new integer attribute. The unit is not checked
   137  // to be valid.
   138  func NewIntAttribute(i int64, unit string) *Attribute {
   139  	return &Attribute{
   140  		Int:  helper.Int64ToPtr(i),
   141  		Unit: unit,
   142  	}
   143  }
   144  
   145  // NewFloatAttribute returns a new float attribute. The unit is not checked to
   146  // be valid.
   147  func NewFloatAttribute(f float64, unit string) *Attribute {
   148  	return &Attribute{
   149  		Float: helper.Float64ToPtr(f),
   150  		Unit:  unit,
   151  	}
   152  }
   153  
   154  // GetString returns the string value of the attribute or false if the attribute
   155  // doesn't contain a string.
   156  func (a *Attribute) GetString() (value string, ok bool) {
   157  	if a.String == nil {
   158  		return "", false
   159  	}
   160  
   161  	return *a.String, true
   162  }
   163  
   164  // GetBool returns the boolean value of the attribute or false if the attribute
   165  // doesn't contain a boolean.
   166  func (a *Attribute) GetBool() (value bool, ok bool) {
   167  	if a.Bool == nil {
   168  		return false, false
   169  	}
   170  
   171  	return *a.Bool, true
   172  }
   173  
   174  // GetInt returns the integer value of the attribute or false if the attribute
   175  // doesn't contain a integer.
   176  func (a *Attribute) GetInt() (value int64, ok bool) {
   177  	if a.Int == nil {
   178  		return 0, false
   179  	}
   180  
   181  	return *a.Int, true
   182  }
   183  
   184  // GetFloat returns the float value of the attribute or false if the attribute
   185  // doesn't contain a float.
   186  func (a *Attribute) GetFloat() (value float64, ok bool) {
   187  	if a.Float == nil {
   188  		return 0.0, false
   189  	}
   190  
   191  	return *a.Float, true
   192  }
   193  
   194  // Copy returns a copied version of the attribute
   195  func (a *Attribute) Copy() *Attribute {
   196  	if a == nil {
   197  		return nil
   198  	}
   199  
   200  	ca := &Attribute{
   201  		Unit: a.Unit,
   202  	}
   203  
   204  	if a.Float != nil {
   205  		ca.Float = helper.Float64ToPtr(*a.Float)
   206  	}
   207  	if a.Int != nil {
   208  		ca.Int = helper.Int64ToPtr(*a.Int)
   209  	}
   210  	if a.Bool != nil {
   211  		ca.Bool = helper.BoolToPtr(*a.Bool)
   212  	}
   213  	if a.String != nil {
   214  		ca.String = helper.StringToPtr(*a.String)
   215  	}
   216  
   217  	return ca
   218  }
   219  
   220  // GoString returns a string representation of the attribute
   221  func (a *Attribute) GoString() string {
   222  	if a == nil {
   223  		return "nil attribute"
   224  	}
   225  
   226  	var b strings.Builder
   227  	if a.Float != nil {
   228  		b.WriteString(fmt.Sprintf("%v", *a.Float))
   229  	} else if a.Int != nil {
   230  		b.WriteString(fmt.Sprintf("%v", *a.Int))
   231  	} else if a.Bool != nil {
   232  		b.WriteString(fmt.Sprintf("%v", *a.Bool))
   233  	} else if a.String != nil {
   234  		b.WriteString(*a.String)
   235  	}
   236  
   237  	if a.Unit != "" {
   238  		b.WriteString(a.Unit)
   239  	}
   240  
   241  	return b.String()
   242  }
   243  
   244  // Validate checks if the attribute is valid
   245  func (a *Attribute) Validate() error {
   246  	if a.Unit != "" {
   247  		if _, ok := UnitIndex[a.Unit]; !ok {
   248  			return fmt.Errorf("unrecognized unit %q", a.Unit)
   249  		}
   250  
   251  		// Check only int/float set
   252  		if a.String != nil || a.Bool != nil {
   253  			return fmt.Errorf("unit can not be specified on a boolean or string attribute")
   254  		}
   255  	}
   256  
   257  	// Assert only one of the attributes is set
   258  	set := 0
   259  	if a.Float != nil {
   260  		set++
   261  	}
   262  	if a.Int != nil {
   263  		set++
   264  	}
   265  	if a.String != nil {
   266  		set++
   267  	}
   268  	if a.Bool != nil {
   269  		set++
   270  	}
   271  
   272  	if set == 0 {
   273  		return fmt.Errorf("no attribute value set")
   274  	} else if set > 1 {
   275  		return fmt.Errorf("only one attribute value may be set")
   276  	}
   277  
   278  	return nil
   279  }
   280  
   281  // Comparable returns whether the two attributes are comparable
   282  func (a *Attribute) Comparable(b *Attribute) bool {
   283  	if a == nil || b == nil {
   284  		return false
   285  	}
   286  
   287  	// First use the units to decide if comparison is possible
   288  	aUnit := a.getTypedUnit()
   289  	bUnit := b.getTypedUnit()
   290  	if aUnit != nil && bUnit != nil {
   291  		return aUnit.Comparable(bUnit)
   292  	} else if aUnit != nil && bUnit == nil {
   293  		return false
   294  	} else if aUnit == nil && bUnit != nil {
   295  		return false
   296  	}
   297  
   298  	if a.String != nil {
   299  		return b.String != nil
   300  	}
   301  
   302  	if a.Bool != nil {
   303  		return b.Bool != nil
   304  	}
   305  
   306  	return true
   307  }
   308  
   309  // Compare compares two attributes. If the returned boolean value is false, it
   310  // means the values are not comparable, either because they are of different
   311  // types (bool versus int) or the units are incompatible for comparison.
   312  // The returned int will be 0 if a==b, -1 if a < b, and +1 if a > b for all
   313  // values but bool. For bool it will be 0 if a==b or 1 if a!=b.
   314  func (a *Attribute) Compare(b *Attribute) (int, bool) {
   315  	if !a.Comparable(b) {
   316  		return 0, false
   317  	}
   318  
   319  	return a.comparator()(b)
   320  }
   321  
   322  // comparator returns the comparator function for the attribute
   323  func (a *Attribute) comparator() compareFn {
   324  	if a.Bool != nil {
   325  		return a.boolComparator
   326  	}
   327  	if a.String != nil {
   328  		return a.stringComparator
   329  	}
   330  	if a.Int != nil || a.Float != nil {
   331  		return a.numberComparator
   332  	}
   333  
   334  	return nullComparator
   335  }
   336  
   337  // boolComparator compares two boolean attributes
   338  func (a *Attribute) boolComparator(b *Attribute) (int, bool) {
   339  	if *a.Bool == *b.Bool {
   340  		return 0, true
   341  	}
   342  
   343  	return 1, true
   344  }
   345  
   346  // stringComparator compares two string attributes
   347  func (a *Attribute) stringComparator(b *Attribute) (int, bool) {
   348  	return strings.Compare(*a.String, *b.String), true
   349  }
   350  
   351  // numberComparator compares two number attributes, having either Int or Float
   352  // set.
   353  func (a *Attribute) numberComparator(b *Attribute) (int, bool) {
   354  	// If they are both integers we do perfect precision comparisons
   355  	if a.Int != nil && b.Int != nil {
   356  		return a.intComparator(b)
   357  	}
   358  
   359  	// Push both into the float space
   360  	af := a.getBigFloat()
   361  	bf := b.getBigFloat()
   362  	if af == nil || bf == nil {
   363  		return 0, false
   364  	}
   365  
   366  	return af.Cmp(bf), true
   367  }
   368  
   369  // intComparator compares two integer attributes.
   370  func (a *Attribute) intComparator(b *Attribute) (int, bool) {
   371  	ai := a.getInt()
   372  	bi := b.getInt()
   373  
   374  	if ai == bi {
   375  		return 0, true
   376  	} else if ai < bi {
   377  		return -1, true
   378  	} else {
   379  		return 1, true
   380  	}
   381  }
   382  
   383  // nullComparator always returns false and is used when no comparison function
   384  // is possible
   385  func nullComparator(*Attribute) (int, bool) {
   386  	return 0, false
   387  }
   388  
   389  // compareFn is used to compare two attributes. It returns -1, 0, 1 for ordering
   390  // and a boolean for if the comparison is possible.
   391  type compareFn func(b *Attribute) (int, bool)
   392  
   393  // getBigFloat returns a big.Float representation of the attribute, converting
   394  // the value to the base unit if a unit is specified.
   395  func (a *Attribute) getBigFloat() *big.Float {
   396  	f := new(big.Float)
   397  	f.SetPrec(floatPrecision)
   398  	if a.Int != nil {
   399  		f.SetInt64(*a.Int)
   400  	} else if a.Float != nil {
   401  		f.SetFloat64(*a.Float)
   402  	} else {
   403  		return nil
   404  	}
   405  
   406  	// Get the unit
   407  	u := a.getTypedUnit()
   408  
   409  	// If there is no unit just return the float
   410  	if u == nil {
   411  		return f
   412  	}
   413  
   414  	// Convert to the base unit
   415  	multiplier := new(big.Float)
   416  	multiplier.SetPrec(floatPrecision)
   417  	multiplier.SetInt64(u.Multiplier)
   418  	if u.InverseMultiplier {
   419  		base := big.NewFloat(1.0)
   420  		base.SetPrec(floatPrecision)
   421  		multiplier = multiplier.Quo(base, multiplier)
   422  	}
   423  
   424  	f.Mul(f, multiplier)
   425  	return f
   426  }
   427  
   428  // getInt returns an int representation of the attribute, converting
   429  // the value to the base unit if a unit is specified.
   430  func (a *Attribute) getInt() int64 {
   431  	if a.Int == nil {
   432  		return 0
   433  	}
   434  
   435  	i := *a.Int
   436  
   437  	// Get the unit
   438  	u := a.getTypedUnit()
   439  
   440  	// If there is no unit just return the int
   441  	if u == nil {
   442  		return i
   443  	}
   444  
   445  	if u.InverseMultiplier {
   446  		i /= u.Multiplier
   447  	} else {
   448  		i *= u.Multiplier
   449  	}
   450  
   451  	return i
   452  }
   453  
   454  // getTypedUnit returns the Unit for the attribute or nil if no unit exists.
   455  func (a *Attribute) getTypedUnit() *Unit {
   456  	return UnitIndex[a.Unit]
   457  }