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 }