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  }