diff options
Diffstat (limited to 'python/vyos/config.py')
-rw-r--r-- | python/vyos/config.py | 239 |
1 files changed, 239 insertions, 0 deletions
diff --git a/python/vyos/config.py b/python/vyos/config.py index 9ddff8c9d..5af830480 100644 --- a/python/vyos/config.py +++ b/python/vyos/config.py @@ -13,16 +13,80 @@ # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see <http://www.gnu.org/licenses/>. +""" +A library for reading VyOS running config data. + +This library is used internally by all config scripts of VyOS, +but its API should be considered stable and it is safe to use +in user scripts. + +Note that this module will not work outside VyOS. + +Node taxonomy +############# + +There are multiple types of config tree nodes in VyOS, each requires +its own set of operations. + +*Leaf nodes* (such as "address" in interfaces) can have values, but cannot +have children. +Leaf nodes can have one value, multiple values, or no values at all. + +For example, "system host-name" is a single-value leaf node, +"system name-server" is a multi-value leaf node (commonly abbreviated "multi node"), +and "system ip disable-forwarding" is a valueless leaf node. + +Non-leaf nodes cannot have values, but they can have child nodes. They are divided into +two classes depending on whether the names of their children are fixed or not. +For example, under "system", the names of all valid child nodes are predefined +("login", "name-server" etc.). + +To the contrary, children of the "system task-scheduler task" node can have arbitrary names. +Such nodes are called *tag nodes*. This terminology is confusing but we keep using it for lack +of a better word. The knowledge of whether in "task Foo" the "tag" is "task" or "Foo" is lost +in time, luckily, the distinction is irrelevant in practice. + +Configuration modes +################### + +VyOS has two distinct modes: operational mode and configuration mode. When a user logins, +the CLI is in the operational mode. In this mode, only the running (effective) config is accessible for reading. + +When a user enters the "configure" command, a configuration session is setup. Every config session +has its *proposed* config built on top of the current running config. When changes are commited, if commit succeeds, +the proposed config is merged into the running config. + +For this reason, this library has two sets of functions. The base versions, such as ``exists`` or ``return_value`` +are only usable in configuration mode. They take all nodes into account, in both proposed and running configs. +Configuration scripts require access to uncommited changes for obvious reasons. Configuration mode completion helpers +should also use these functions because not having nodes you've just created in completion is annoying. + +However, in operational mode, only the running config is available. Currently, you need to use special functions +for reading it from operational mode scripts, they can be distinguished by the word "effective" in their names. +In the future base versions may be made to detect if they are called from a config session or not. +""" import subprocess import re class VyOSError(Exception): + """ + Raised on config access errors, most commonly if the type of a config tree node + in the system does not match the type of operation. + + """ pass class Config(object): + """ + The class of config access objects. + + Internally, in the current implementation, this object is *almost* stateless, + the only state it keeps is relative *config path* for convenient access to config + subtrees. + """ def __init__(self): self._cli_shell_api = "/bin/cli-shell-api" self._level = "" @@ -42,15 +106,41 @@ class Config(object): return out.decode('ascii') def set_level(self, path): + """ + Set the *edit level*, that is, a relative config tree path. + Once set, all operations will be relative to this path, + for example, after ``set_level("system")``, calling + ``exists("name-server")`` is equivalent to calling + ``exists("system name-server"`` without ``set_level``. + + Args: + path (str): relative config path + """ # Make sure there's always a space between default path (level) # and path supplied as method argument # XXX: for small strings in-place concatenation is not a problem self._level = path + " " def get_level(self): + """ + Gets the current edit level. + + Returns: + str: current edit level + """ return(self._level.strip()) def exists(self, path): + """ + Checks if a node with given path exists in the running or proposed config + + Returns: + True if node exists, False otherwise + + Note: + This function cannot be used outside a configuration sessions. + In operational mode scripts, use ``exists_effective``. + """ try: self._run(self._make_command('exists', self._level + path)) return True @@ -58,6 +148,10 @@ class Config(object): return False def session_changed(self): + """ + Returns: + True if the config session has uncommited changes, False otherwise. + """ try: self._run(self._make_command('sessionChanged', '')) return True @@ -65,6 +159,10 @@ class Config(object): return False def in_session(self): + """ + Returns: + True if called from a configuration session, False otherwise. + """ try: self._run(self._make_command('inSession', '')) return True @@ -72,6 +170,16 @@ class Config(object): return False def is_multi(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node can have multiple values, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ try: self._run(self._make_command('isMulti', self._level + path)) return True @@ -79,6 +187,16 @@ class Config(object): return False def is_tag(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a tag node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ try: self._run(self._make_command('isTag', self._level + path)) return True @@ -86,6 +204,16 @@ class Config(object): return False def is_leaf(self, path): + """ + Args: + path (str): Configuration tree path + + Returns: + True if a node is a leaf node, False otherwise. + + Note: + It also returns False if node doesn't exist. + """ try: self._run(self._make_command('isLeaf', self._level + path)) return True @@ -93,6 +221,27 @@ class Config(object): return False def return_value(self, path, default=None): + """ + Retrieve a value of single-value leaf node in the running or proposed config + + Args: + path (str): Configuration tree path + default (str): Default value to return if node does not exist + + Returns: + str: Node value, if it has any + None: if node is valueless *or* if it doesn't exist + + Raises: + VyOSError: if node is not a single-value leaf node + + Note: + Due to the issue with treatment of valueless nodes by this function, + valueless nodes should be checked with ``exists`` instead. + + This function cannot be used outside a configuration session. + In operational mode scripts, use ``return_effective_value``. + """ full_path = self._level + path if self.is_multi(path): raise VyOSError("Cannot use return_value on multi node: {0}".format(full_path)) @@ -106,6 +255,23 @@ class Config(object): return(default) def return_values(self, path, default=[]): + """ + Retrieve all values of a multi-value leaf node in the running or proposed config + + Args: + path (str): Configuration tree path + + Returns: + str list: Node values, if it has any + None: if node does not exist + + Raises: + VyOSError: if node is not a multi-value leaf node + + Note: + This function cannot be used outside a configuration session. + In operational mode scripts, use ``return_effective_values``. + """ full_path = self._level + path if not self.is_multi(path): raise VyOSError("Cannot use return_values on non-multi node: {0}".format(full_path)) @@ -120,6 +286,25 @@ class Config(object): return(default) def list_nodes(self, path, default=[]): + """ + Retrieve names of all children of a tag node in the running or proposed config + + Args: + path (str): Configuration tree path + + Returns: + string list: child node names + + Raises: + VyOSError: if the node is not a tag node + + Note: + There is no way to list all children of a non-tag node in + the current config backend. + + This function cannot be used outside a configuration session. + In operational mode scripts, use ``list_effective_nodes``. + """ full_path = self._level + path if self.is_tag(path): try: @@ -132,6 +317,19 @@ class Config(object): raise VyOSError("Cannot use list_nodes on a non-tag node: {0}".format(full_path)) def exists_effective(self, path): + """ + Check if a node exists in the running (effective) config + + Args: + path (str): Configuration tree path + + Returns: + True if node exists in the running config, False otherwise + + Note: + This function is safe to use in operational mode. In configuration mode, + it ignores uncommited changes. + """ try: self._run(self._make_command('existsEffective', self._level + path)) return True @@ -139,6 +337,19 @@ class Config(object): return False def return_effective_value(self, path, default=None): + """ + Retrieve a values of a single-value leaf node in a running (effective) config + + Args: + path (str): Configuration tree path + default (str): Default value to return if node does not exist + + Returns: + str: Node value + + Raises: + VyOSError: if node is not a multi-value leaf node + """ full_path = self._level + path if self.is_multi(path): raise VyOSError("Cannot use return_effective_value on multi node: {0}".format(full_path)) @@ -152,6 +363,18 @@ class Config(object): return(default) def return_effective_values(self, path, default=[]): + """ + Retrieve all values of a multi-value node in a running (effective) config + + Args: + path (str): Configuration tree path + + Returns: + str list: A list of values + + Raises: + VyOSError: if node is not a multi-value leaf node + """ full_path = self._level + path if not self.is_multi(path): raise VyOSError("Cannot use return_effective_values on non-multi node: {0}".format(full_path)) @@ -165,6 +388,22 @@ class Config(object): return(default) def list_effective_nodes(self, path, default=[]): + """ + Retrieve names of all children of a tag node in the running config + + Args: + path (str): Configuration tree path + + Returns: + str list: child node names + + Raises: + VyOSError: if the node is not a tag node + + Note: + There is no way to list all children of a non-tag node in + the current config backend. + """ full_path = self._level + path if self.is_tag(path): try: |