github.com/elfadel/cilium@v1.6.12/pkg/option/option.go (about) 1 // Copyright 2016-2018 Authors of Cilium 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package option 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "github.com/cilium/cilium/api/v1/models" 23 "github.com/cilium/cilium/pkg/color" 24 "github.com/cilium/cilium/pkg/lock" 25 ) 26 27 // VerifyFunc validates option key with value and may return an error if the 28 // option should not be applied 29 type VerifyFunc func(key string, value string) error 30 31 // ParseFunc parses the option value and may return an error if the option 32 // cannot be parsed or applied. 33 type ParseFunc func(value string) (OptionSetting, error) 34 35 // FormatFunc formats the specified value as a colored textual representation 36 // of the option. 37 type FormatFunc func(value OptionSetting) string 38 39 // Option is the structure used to specify the semantics of a configurable 40 // boolean option 41 type Option struct { 42 // Define is the name of the #define used for BPF programs 43 Define string 44 // Description is a short human readable description 45 Description string 46 // Immutable marks an option which is read-only 47 Immutable bool 48 // Requires is a list of required options, such options will be 49 // automatically enabled as required. 50 Requires []string 51 // Parse is called to parse the option. If not specified, defaults to 52 // NormalizeBool(). 53 Parse ParseFunc 54 // FormatFunc is called to format the value for an option. If not 55 // specified, defaults to formatting 0 as "Disabled" and other values 56 // as "Enabled". 57 Format FormatFunc 58 // Verify is called prior to applying the option 59 Verify VerifyFunc 60 } 61 62 // OptionSetting specifies the different choices each Option has. 63 type OptionSetting int 64 65 const ( 66 OptionDisabled OptionSetting = iota 67 OptionEnabled 68 ) 69 70 // RequiresOption returns true if the option requires the specified option `name`. 71 func (o Option) RequiresOption(name string) bool { 72 for _, o := range o.Requires { 73 if o == name { 74 return true 75 } 76 } 77 78 return false 79 } 80 81 type OptionLibrary map[string]*Option 82 83 func (l OptionLibrary) Lookup(name string) (string, *Option) { 84 nameLower := strings.ToLower(name) 85 86 for k := range l { 87 if strings.ToLower(k) == nameLower { 88 return k, l[k] 89 } 90 } 91 92 return "", nil 93 } 94 95 func (l OptionLibrary) Define(name string) string { 96 if _, ok := l[name]; ok { 97 return l[name].Define 98 } 99 100 return name 101 } 102 103 func NormalizeBool(value string) (OptionSetting, error) { 104 switch strings.ToLower(value) { 105 case "true", "on", "enable", "enabled", "1": 106 return OptionEnabled, nil 107 case "false", "off", "disable", "disabled", "0": 108 return OptionDisabled, nil 109 default: 110 return OptionDisabled, fmt.Errorf("invalid option value %s", value) 111 } 112 } 113 114 // ValidateConfigurationMap validates a given configuration map based on the 115 // option library 116 func (l *OptionLibrary) ValidateConfigurationMap(n models.ConfigurationMap) (OptionMap, error) { 117 o := make(OptionMap) 118 for k, v := range n { 119 _, newVal, err := ParseKeyValue(l, k, v) 120 if err != nil { 121 return nil, err 122 } 123 124 if err := l.Validate(k, v); err != nil { 125 return nil, err 126 } 127 o[k] = newVal 128 } 129 130 return o, nil 131 } 132 133 func (l OptionLibrary) Validate(name string, value string) error { 134 key, spec := l.Lookup(name) 135 if key == "" { 136 return fmt.Errorf("unknown option %s", name) 137 } 138 139 if spec.Immutable { 140 return fmt.Errorf("specified option is immutable (read-only)") 141 } 142 143 if spec.Verify != nil { 144 return spec.Verify(key, value) 145 } 146 147 return nil 148 } 149 150 type OptionMap map[string]OptionSetting 151 152 func (om OptionMap) DeepCopy() OptionMap { 153 cpy := make(OptionMap, len(om)) 154 for k, v := range om { 155 cpy[k] = v 156 } 157 return cpy 158 } 159 160 // IntOptions member functions with external access do not require 161 // locking by the caller, while functions with internal access presume 162 // the caller to have taken care of any locking needed. 163 type IntOptions struct { 164 optsMU lock.RWMutex // Protects all variables from this structure below this line 165 Opts OptionMap `json:"map"` 166 Library *OptionLibrary `json:"-"` 167 } 168 169 // GetImmutableModel returns the set of immutable options as a ConfigurationMap API model. 170 func (o *IntOptions) GetImmutableModel() *models.ConfigurationMap { 171 immutableCfg := make(models.ConfigurationMap) 172 return &immutableCfg 173 } 174 175 // GetMutableModel returns the set of mutable options as a ConfigurationMap API model. 176 func (o *IntOptions) GetMutableModel() *models.ConfigurationMap { 177 mutableCfg := make(models.ConfigurationMap) 178 o.optsMU.RLock() 179 for k, v := range o.Opts { 180 _, config := o.Library.Lookup(k) 181 182 // It's possible that an option has since been removed and thus has 183 // no corresponding configuration; need to check if configuration is 184 // nil accordingly. 185 if config != nil { 186 if config.Format == nil { 187 if v == OptionDisabled { 188 mutableCfg[k] = fmt.Sprintf("Disabled") 189 } else { 190 mutableCfg[k] = fmt.Sprintf("Enabled") 191 } 192 } else { 193 mutableCfg[k] = config.Format(v) 194 } 195 } 196 } 197 o.optsMU.RUnlock() 198 199 return &mutableCfg 200 } 201 202 func (o *IntOptions) DeepCopy() *IntOptions { 203 o.optsMU.RLock() 204 cpy := &IntOptions{ 205 Opts: o.Opts.DeepCopy(), 206 Library: o.Library, 207 } 208 o.optsMU.RUnlock() 209 return cpy 210 } 211 212 func NewIntOptions(lib *OptionLibrary) *IntOptions { 213 return &IntOptions{ 214 Opts: OptionMap{}, 215 Library: lib, 216 } 217 } 218 219 func (o *IntOptions) getValue(key string) OptionSetting { 220 value, exists := o.Opts[key] 221 if !exists { 222 return OptionDisabled 223 } 224 return value 225 } 226 227 func (o *IntOptions) GetValue(key string) OptionSetting { 228 o.optsMU.RLock() 229 v := o.getValue(key) 230 o.optsMU.RUnlock() 231 return v 232 } 233 234 func (o *IntOptions) IsEnabled(key string) bool { 235 return o.GetValue(key) != OptionDisabled 236 } 237 238 // SetValidated sets the option `key` to the specified value. The caller is 239 // expected to have validated the input to this function. 240 func (o *IntOptions) SetValidated(key string, value OptionSetting) { 241 o.optsMU.Lock() 242 o.Opts[key] = value 243 o.optsMU.Unlock() 244 } 245 246 // SetBool sets the specified option to Enabled. 247 func (o *IntOptions) SetBool(key string, value bool) { 248 intValue := OptionDisabled 249 if value { 250 intValue = OptionEnabled 251 } 252 o.optsMU.Lock() 253 o.Opts[key] = intValue 254 o.optsMU.Unlock() 255 } 256 257 func (o *IntOptions) Delete(key string) { 258 o.optsMU.Lock() 259 delete(o.Opts, key) 260 o.optsMU.Unlock() 261 } 262 263 func (o *IntOptions) SetIfUnset(key string, value OptionSetting) { 264 o.optsMU.Lock() 265 if _, exists := o.Opts[key]; !exists { 266 o.Opts[key] = value 267 } 268 o.optsMU.Unlock() 269 } 270 271 func (o *IntOptions) InheritDefault(parent *IntOptions, key string) { 272 o.optsMU.RLock() 273 o.Opts[key] = parent.GetValue(key) 274 o.optsMU.RUnlock() 275 } 276 277 func ParseOption(arg string, lib *OptionLibrary) (string, OptionSetting, error) { 278 result := OptionEnabled 279 280 if arg[0] == '!' { 281 result = OptionDisabled 282 arg = arg[1:] 283 } 284 285 optionSplit := strings.SplitN(arg, "=", 2) 286 arg = optionSplit[0] 287 if len(optionSplit) > 1 { 288 if result == OptionDisabled { 289 return "", OptionDisabled, fmt.Errorf("invalid boolean format") 290 } 291 292 return ParseKeyValue(lib, arg, optionSplit[1]) 293 } 294 295 return "", OptionDisabled, fmt.Errorf("invalid option format") 296 } 297 298 func ParseKeyValue(lib *OptionLibrary, arg, value string) (string, OptionSetting, error) { 299 var result OptionSetting 300 301 key, spec := lib.Lookup(arg) 302 if key == "" { 303 return "", OptionDisabled, fmt.Errorf("unknown option %q", arg) 304 } 305 306 var err error 307 if spec.Parse != nil { 308 result, err = spec.Parse(value) 309 } else { 310 result, err = NormalizeBool(value) 311 } 312 if err != nil { 313 return "", OptionDisabled, err 314 } 315 316 if spec.Immutable { 317 return "", OptionDisabled, fmt.Errorf("specified option is immutable (read-only)") 318 } 319 320 return key, result, nil 321 } 322 323 // getFmtOpt returns #define name if option exists and is set to true in endpoint's Opts 324 // map or #undef name if option does not exist or exists but is set to false 325 func (o *IntOptions) getFmtOpt(name string) string { 326 define := o.Library.Define(name) 327 if define == "" { 328 return "" 329 } 330 331 value := o.getValue(name) 332 if value != OptionDisabled { 333 return fmt.Sprintf("#define %s %d", o.Library.Define(name), value) 334 } 335 return "#undef " + o.Library.Define(name) 336 } 337 338 func (o *IntOptions) GetFmtList() string { 339 txt := "" 340 341 o.optsMU.RLock() 342 opts := []string{} 343 for k := range o.Opts { 344 opts = append(opts, k) 345 } 346 sort.Strings(opts) 347 348 for _, k := range opts { 349 def := o.getFmtOpt(k) 350 if def != "" { 351 txt += def + "\n" 352 } 353 } 354 o.optsMU.RUnlock() 355 356 return txt 357 } 358 359 func (o *IntOptions) Dump() { 360 if o == nil { 361 return 362 } 363 364 o.optsMU.RLock() 365 opts := []string{} 366 for k := range o.Opts { 367 opts = append(opts, k) 368 } 369 sort.Strings(opts) 370 371 for _, k := range opts { 372 var text string 373 _, option := o.Library.Lookup(k) 374 if option == nil || option.Format == nil { 375 if o.Opts[k] == OptionDisabled { 376 text = color.Red("Disabled") 377 } else { 378 text = color.Green("Enabled") 379 } 380 } else { 381 text = option.Format(o.Opts[k]) 382 } 383 384 fmt.Printf("%-24s %s\n", k, text) 385 } 386 o.optsMU.RUnlock() 387 } 388 389 // Validate validates a given configuration map based on the option library 390 func (o *IntOptions) Validate(n models.ConfigurationMap) error { 391 o.optsMU.RLock() 392 defer o.optsMU.RUnlock() 393 for k, v := range n { 394 _, newVal, err := ParseKeyValue(o.Library, k, v) 395 if err != nil { 396 return err 397 } 398 399 // Ignore validation if value is identical 400 if oldVal, ok := o.Opts[k]; ok && oldVal == newVal { 401 continue 402 } 403 404 if err := o.Library.Validate(k, v); err != nil { 405 return err 406 } 407 } 408 409 return nil 410 } 411 412 // ChangedFunc is called by `Apply()` for each option changed 413 type ChangedFunc func(key string, value OptionSetting, data interface{}) 414 415 // enable enables the option `name` with all its dependencies 416 func (o *IntOptions) enable(name string) { 417 if o.Library != nil { 418 if _, opt := o.Library.Lookup(name); opt != nil { 419 for _, dependency := range opt.Requires { 420 o.enable(dependency) 421 } 422 } 423 } 424 425 o.Opts[name] = OptionEnabled 426 } 427 428 // set enables the option `name` with all its dependencies, and sets the 429 // integer level of the option to `value`. 430 func (o *IntOptions) set(name string, value OptionSetting) { 431 o.enable(name) 432 o.Opts[name] = value 433 } 434 435 // disable disables the option `name`. All options which depend on the option 436 // to be disabled will be disabled. Options which have previously been enabled 437 // as a dependency will not be automatically disabled. 438 func (o *IntOptions) disable(name string) { 439 o.Opts[name] = OptionDisabled 440 441 if o.Library != nil { 442 // Disable all options which have a dependency on the option 443 // that was just disabled 444 for key, opt := range *o.Library { 445 if opt.RequiresOption(name) && o.Opts[key] != OptionDisabled { 446 o.disable(key) 447 } 448 } 449 } 450 } 451 452 type changedOptions struct { 453 key string 454 value OptionSetting 455 } 456 457 // ApplyValidated takes a configuration map and applies the changes. For an 458 // option which is changed, the `ChangedFunc` function is called with the 459 // `data` argument passed in as well. Returns the number of options changed if 460 // any. 461 // 462 // The caller is expected to have validated the configuration options prior to 463 // calling this function. 464 func (o *IntOptions) ApplyValidated(n OptionMap, changed ChangedFunc, data interface{}) int { 465 changes := make([]changedOptions, 0, len(n)) 466 467 o.optsMU.Lock() 468 for k, optVal := range n { 469 val, ok := o.Opts[k] 470 471 if optVal == OptionDisabled { 472 /* Only disable if enabled already */ 473 if ok && val != OptionDisabled { 474 o.disable(k) 475 changes = append(changes, changedOptions{key: k, value: optVal}) 476 } 477 } else { 478 /* Only enable if not enabled already */ 479 if !ok || val == OptionDisabled { 480 o.set(k, optVal) 481 changes = append(changes, changedOptions{key: k, value: optVal}) 482 } 483 } 484 } 485 o.optsMU.Unlock() 486 487 for _, change := range changes { 488 changed(change.key, change.value, data) 489 } 490 491 return len(changes) 492 }