summaryrefslogtreecommitdiff
path: root/python/vyos/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/vyos/config.py')
-rw-r--r--python/vyos/config.py239
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: