github.com/ferranbt/nomad@v0.9.3-0.20190607002617-85c449b7667c/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  		if b.String != nil {
   300  			return true
   301  		}
   302  		return false
   303  	}
   304  	if a.Bool != nil {
   305  		if b.Bool != nil {
   306  			return true
   307  		}
   308  		return false
   309  	}
   310  
   311  	return true
   312  }
   313  
   314  // Compare compares two attributes. If the returned boolean value is false, it
   315  // means the values are not comparable, either because they are of different
   316  // types (bool versus int) or the units are incompatible for comparison.
   317  // The returned int will be 0 if a==b, -1 if a < b, and +1 if a > b for all
   318  // values but bool. For bool it will be 0 if a==b or 1 if a!=b.
   319  func (a *Attribute) Compare(b *Attribute) (int, bool) {
   320  	if !a.Comparable(b) {
   321  		return 0, false
   322  	}
   323  
   324  	return a.comparator()(b)
   325  }
   326  
   327  // comparator returns the comparator function for the attribute
   328  func (a *Attribute) comparator() compareFn {
   329  	if a.Bool != nil {
   330  		return a.boolComparator
   331  	}
   332  	if a.String != nil {
   333  		return a.stringComparator
   334  	}
   335  	if a.Int != nil || a.Float != nil {
   336  		return a.numberComparator
   337  	}
   338  
   339  	return nullComparator
   340  }
   341  
   342  // boolComparator compares two boolean attributes
   343  func (a *Attribute) boolComparator(b *Attribute) (int, bool) {
   344  	if *a.Bool == *b.Bool {
   345  		return 0, true
   346  	}
   347  
   348  	return 1, true
   349  }
   350  
   351  // stringComparator compares two string attributes
   352  func (a *Attribute) stringComparator(b *Attribute) (int, bool) {
   353  	return strings.Compare(*a.String, *b.String), true
   354  }
   355  
   356  // numberComparator compares two number attributes, having either Int or Float
   357  // set.
   358  func (a *Attribute) numberComparator(b *Attribute) (int, bool) {
   359  	// If they are both integers we do perfect precision comparisons
   360  	if a.Int != nil && b.Int != nil {
   361  		return a.intComparator(b)
   362  	}
   363  
   364  	// Push both into the float space
   365  	af := a.getBigFloat()
   366  	bf := b.getBigFloat()
   367  	if af == nil || bf == nil {
   368  		return 0, false
   369  	}
   370  
   371  	return af.Cmp(bf), true
   372  }
   373  
   374  // intComparator compares two integer attributes.
   375  func (a *Attribute) intComparator(b *Attribute) (int, bool) {
   376  	ai := a.getInt()
   377  	bi := b.getInt()
   378  
   379  	if ai == bi {
   380  		return 0, true
   381  	} else if ai < bi {
   382  		return -1, true
   383  	} else {
   384  		return 1, true
   385  	}
   386  }
   387  
   388  // nullComparator always returns false and is used when no comparison function
   389  // is possible
   390  func nullComparator(*Attribute) (int, bool) {
   391  	return 0, false
   392  }
   393  
   394  // compareFn is used to compare two attributes. It returns -1, 0, 1 for ordering
   395  // and a boolean for if the comparison is possible.
   396  type compareFn func(b *Attribute) (int, bool)
   397  
   398  // getBigFloat returns a big.Float representation of the attribute, converting
   399  // the value to the base unit if a unit is specified.
   400  func (a *Attribute) getBigFloat() *big.Float {
   401  	f := new(big.Float)
   402  	f.SetPrec(floatPrecision)
   403  	if a.Int != nil {
   404  		f.SetInt64(*a.Int)
   405  	} else if a.Float != nil {
   406  		f.SetFloat64(*a.Float)
   407  	} else {
   408  		return nil
   409  	}
   410  
   411  	// Get the unit
   412  	u := a.getTypedUnit()
   413  
   414  	// If there is no unit just return the float
   415  	if u == nil {
   416  		return f
   417  	}
   418  
   419  	// Convert to the base unit
   420  	multiplier := new(big.Float)
   421  	multiplier.SetPrec(floatPrecision)
   422  	multiplier.SetInt64(u.Multiplier)
   423  	if u.InverseMultiplier {
   424  		base := big.NewFloat(1.0)
   425  		base.SetPrec(floatPrecision)
   426  		multiplier = multiplier.Quo(base, multiplier)
   427  	}
   428  
   429  	f.Mul(f, multiplier)
   430  	return f
   431  }
   432  
   433  // getInt returns an int representation of the attribute, converting
   434  // the value to the base unit if a unit is specified.
   435  func (a *Attribute) getInt() int64 {
   436  	if a.Int == nil {
   437  		return 0
   438  	}
   439  
   440  	i := *a.Int
   441  
   442  	// Get the unit
   443  	u := a.getTypedUnit()
   444  
   445  	// If there is no unit just return the int
   446  	if u == nil {
   447  		return i
   448  	}
   449  
   450  	if u.InverseMultiplier {
   451  		i /= u.Multiplier
   452  	} else {
   453  		i *= u.Multiplier
   454  	}
   455  
   456  	return i
   457  }
   458  
   459  // getTypedUnit returns the Unit for the attribute or nil if no unit exists.
   460  func (a *Attribute) getTypedUnit() *Unit {
   461  	return UnitIndex[a.Unit]
   462  }