summaryrefslogtreecommitdiff
path: root/src/conf_mode/interfaces-tunnel.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/conf_mode/interfaces-tunnel.py')
-rwxr-xr-xsrc/conf_mode/interfaces-tunnel.py151
1 files changed, 149 insertions, 2 deletions
diff --git a/src/conf_mode/interfaces-tunnel.py b/src/conf_mode/interfaces-tunnel.py
index 4a042f1b9..38d490c3c 100755
--- a/src/conf_mode/interfaces-tunnel.py
+++ b/src/conf_mode/interfaces-tunnel.py
@@ -29,18 +29,99 @@ from vyos import ConfigError
class FixedDict(dict):
+ """
+ FixedDict: A dictionnary not allowing new keys to be created after initialisation.
+
+ >>> f = FixedDict(**{'count':1})
+ >>> f['count'] = 2
+ >>> f['king'] = 3
+ File "...", line ..., in __setitem__
+ raise ConfigError(f'Option "{k}" has no defined default')
+ """
def __init__ (self, **options):
self._allowed = options.keys()
super().__init__(**options)
def __setitem__ (self, k, v):
+ """
+ __setitem__ is a builtin which is called by python when setting dict values:
+ >>> d = dict()
+ >>> d['key'] = 'value'
+ >>> d
+ {'key': 'value'}
+
+ is syntaxic sugar for
+
+ >>> d = dict()
+ >>> d.__setitem__('key','value')
+ >>> d
+ {'key': 'value'}
+ """
if k not in self._allowed:
raise ConfigError(f'Option "{k}" has no defined default')
super().__setitem__(k, v)
-class ConfigurationState (Config):
+class ConfigurationState(Config):
+ """
+ The current API require a dict to be generated by get_config()
+ which is then consumed by verify(), generate() and apply()
+
+ ConfiguartionState is an helper class wrapping Config and providing
+ an common API to this dictionary structure
+
+ Its to_dict() function return a dictionary containing three fields,
+ each a dict, called options, changes, actions.
+
+ options:
+
+ contains the configuration options for the dict and its value
+ {'options': {'commment': 'test'}} will be set if
+ 'set interface dummy dum1 description test' was used and
+ the key 'commment' is used to index the description info.
+
+ changes:
+
+ per key, let us know how the data was modified using one of the action
+ a special key called 'section' is used to indicate what happened to the
+ section. for example:
+
+ 'set interface dummy dum1 description test' when no interface was setup
+ will result in the following changes
+ {'changes': {'section': 'create', 'comment': 'create'}}
+
+ on an existing interface, depending if there was a description
+ 'set interface dummy dum1 description test' will result in one of
+ {'changes': {'comment': 'create'}} (not present before)
+ {'changes': {'comment': 'static'}} (unchanged)
+ {'changes': {'comment': 'modify'}} (changed from half)
+
+ and 'delete interface dummy dummy1 description' will result in:
+ {'changes': {'comment': 'delete'}}
+
+ actions:
+
+ for each action list the configuration key which were changes
+ in our example if we added the 'description' and added an IP we would have
+ {'actions': { 'create': ['comment'], 'modify': ['addresses-add']}}
+
+ the actions are:
+ 'create': it did not exist previously and was created
+ 'modify': it did exist previously but its content changed
+ 'static': it did exist and did not change
+ 'delete': it was present but was removed from the configuration
+ 'absent': it was not and is not present
+ which for each field represent how it was modified since the last commit
+ """
+
def __init__ (self, section, default):
+ """
+ initialise the class for a given configuration path:
+
+ >>> conf = ConfigurationState('interfaces ethernet eth1')
+ all further references to get_value(s) and get_effective(s)
+ will be for this part of the configuration (eth1)
+ """
super().__init__()
self.section = section
self.default = deepcopy(default)
@@ -61,6 +142,15 @@ class ConfigurationState (Config):
self.changes['section'] = 'create'
def _act(self, section):
+ """
+ Returns for a given configuration field determine what happened to it
+
+ 'create': it did not exist previously and was created
+ 'modify': it did exist previously but its content changed
+ 'static': it did exist and did not change
+ 'delete': it was present but was removed from the configuration
+ 'absent': it was not and is not present
+ """
if self.exists(section):
if self.exists_effective(section):
if self.return_value(section) != self.return_effective_value(section):
@@ -89,24 +179,71 @@ class ConfigurationState (Config):
self.options[name] = value
def get_value(self, name, key, default=None):
+ """
+ >>> conf.get_value('comment', 'description')
+ will place the string of 'interface dummy description test'
+ into the dictionnary entry 'comment' using Config.return_value
+ (the data in the configuration to apply)
+ """
if self._action(name, key) in ('delete', 'absent'):
return
return self._get(name, key, default, self.return_value)
def get_values(self, name, key, default=None):
+ """
+ >>> conf.get_values('addresses-add', 'address')
+ will place a list made of the IP present in 'interface dummy dum1 address'
+ into the dictionnary entry 'addr' using Config.return_values
+ (the data in the configuration to apply)
+ """
if self._action(name, key) in ('delete', 'absent'):
return
return self._get(name, key, default, self.return_values)
def get_effective(self, name, key, default=None):
+ """
+ >>> conf.get_value('comment', 'description')
+ will place the string of 'interface dummy description test'
+ into the dictionnary entry 'comment' using Config.return_effective_value
+ (the data in the configuration to apply)
+ """
self._action(name, key)
return self._get(name, key, default, self.return_effective_value)
def get_effectives(self, name, key, default=None):
+ """
+ >>> conf.get_effectives('addresses-add', 'address')
+ will place a list made of the IP present in 'interface ethernet eth1 address'
+ into the dictionnary entry 'addresses-add' using Config.return_effectives_value
+ (the data in the un-modified configuration)
+ """
self._action(name, key)
return self._get(name, key, default, self.return_effectives_value)
def load(self, mapping):
+ """
+ load will take a dictionary defining how we wish the configuration
+ to be parsed and apply this definition to set the data.
+
+ >>> mapping = {
+ 'addresses-add' : ('address', True, None),
+ 'comment' : ('description', False, 'auto'),
+ }
+ >>> conf.load(mapping)
+
+ mapping is a dictionary where each key represents the name we wish
+ to have (such as 'addresses-add'), with a list a content representing
+ how the data should be parsed:
+ - the configuration section name
+ such as 'address' under 'interface ethernet eth1'
+ - boolean indicating if this data can have multiple values
+ for 'address', True, as multiple IPs can be set
+ for 'description', False, as it is a single string
+ - default represent the default value if absent from the configuration
+ 'None' indicate that no default should be set if the configuration
+ does not have the configuration section
+
+ """
for local_name, (config_name, multiple, default) in mapping.items():
if multiple:
self.get_values(local_name, config_name, default)
@@ -114,12 +251,21 @@ class ConfigurationState (Config):
self.get_value(local_name, config_name, default)
def remove_default (self,*options):
+ """
+ remove all the values which were not changed from the default
+ """
for option in options:
if self.exists(option) and self_return_value(option) != self.default[option]:
continue
del self.options[option]
def to_dict (self):
+ """
+ provide a dictionary with the generated data for the configuration
+ options: the configuration value for the key
+ changes: per key how they changed from the previous configuration
+ actions: per changes all the options which were changed
+ """
# as we have to use a dict() for the API for verify and apply the options
return {
'options': self.options,
@@ -203,7 +349,8 @@ def get_class (options):
}
kls = dispatch[options['type']]
- if options['type'] == 'gre' and not options['remote']:
+ if options['type'] == 'gre' and not options['remote'] \
+ and not options['key'] and not options['multicast']:
# will use GreTapIf on GreIf deletion but it does not matter
return GRETapIf
elif options['type'] == 'sit' and options['6rd-prefix']: