diff options
-rw-r--r-- | vymgmt/base_exceptions/__init__.py | 0 | ||||
-rw-r--r-- | vymgmt/base_exceptions/base.py | 23 | ||||
-rw-r--r-- | vymgmt/base_exceptions/exceptions_for_commit.py | 21 | ||||
-rw-r--r-- | vymgmt/base_exceptions/exceptions_for_set_and_delete.py | 22 | ||||
-rw-r--r-- | vymgmt/error_distinguish.py | 65 | ||||
-rw-r--r-- | vymgmt/mgmt_common.py | 62 | ||||
-rw-r--r-- | vymgmt/router.py | 421 |
7 files changed, 144 insertions, 470 deletions
diff --git a/vymgmt/base_exceptions/__init__.py b/vymgmt/base_exceptions/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/vymgmt/base_exceptions/__init__.py +++ /dev/null diff --git a/vymgmt/base_exceptions/base.py b/vymgmt/base_exceptions/base.py deleted file mode 100644 index 839224b..0000000 --- a/vymgmt/base_exceptions/base.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright (c) 2016 Hochikong - -prefix = "\r\n" - - -class Error(Exception): - pass - - -class CommonError(Error): - def __init__(self, message): - self.error_message = message - - def __str__(self): - return prefix + self.error_message - - -class MaintenanceError(Error): - def __init__(self, message): - self.error_message = message - - def __str__(self): - return prefix + self.error_message diff --git a/vymgmt/base_exceptions/exceptions_for_commit.py b/vymgmt/base_exceptions/exceptions_for_commit.py deleted file mode 100644 index 26c14c4..0000000 --- a/vymgmt/base_exceptions/exceptions_for_commit.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright (c) 2016 Hochikong - -from .base import Error - -prefix = "\r\n" - - -class CommitFailed(Error): - def __init__(self, message): - self.error_message = message - - def __str__(self): - return prefix + self.error_message - - -class CommitConflict(Error): - def __init__(self, message): - self.error_message = message - - def __str__(self): - return prefix + self.error_message diff --git a/vymgmt/base_exceptions/exceptions_for_set_and_delete.py b/vymgmt/base_exceptions/exceptions_for_set_and_delete.py deleted file mode 100644 index 0f65c88..0000000 --- a/vymgmt/base_exceptions/exceptions_for_set_and_delete.py +++ /dev/null @@ -1,22 +0,0 @@ -# Copyright (c) 2016 Hochikong - - -from .base import Error - -prefix = "\r\n" - - -class ConfigPathError(Error): - def __init__(self, message): - self.error_message = message - - def __str__(self): - return prefix + self.error_message - - -class ConfigValueError(Error): - def __init__(self, message): - self.error_message = message - - def __str__(self): - return prefix + self.error_message diff --git a/vymgmt/error_distinguish.py b/vymgmt/error_distinguish.py deleted file mode 100644 index 1f889f8..0000000 --- a/vymgmt/error_distinguish.py +++ /dev/null @@ -1,65 +0,0 @@ -# Copyright (c) 2016 Hochikong - - -def distinguish_for_set(message): - """Distinguish the error type,PathError or ValueError - - :param message: A error message string from VyOS - :return: The type of error - """ - path_error_string = ['Configuration path:', 'is not valid', 'already exists'] - value_error_string = ['Value validation failed'] - all_strings = [path_error_string, value_error_string] - condition = 0 - for i in all_strings: - for x in i: - if x in message: - condition += 1 - - if condition == 2: - return "ConfigPathError" - elif condition == 1: - return "ConfigValueError" - else: - return "NonsupportButError" - - -def distinguish_for_delete(message): - """Distinguish the error type,PathError or ValueError - - :param message: A error message string from VyOS - :return: The type of error - """ - path_error_string = ['Configuration path:', 'is not valid', 'Delete failed'] - value_error_string = ['Nothing to delete', 'the specified value does not exist', - "the specified node does not exist"] - all_strings = [path_error_string, value_error_string] - condition = 0 - - for i in all_strings: - for x in i: - if x in message: - condition += 1 - - if condition == 3: - return "ConfigPathError" - elif condition == 2: - return "ConfigValueError" - else: - return "NonsupportButError" - - -def distinguish_for_commit(message): - """Distinguish the error type - - :param message: A error message string from VyOS - :return: The type of error - """ - all_strings = ['Commit failed', 'due to another commit in progress'] - - for i in all_strings: - if i in message: - if i == all_strings[0]: - return "CommitFailed" - if i == all_strings[1]: - return "CommitConflict" diff --git a/vymgmt/mgmt_common.py b/vymgmt/mgmt_common.py deleted file mode 100644 index 1153c54..0000000 --- a/vymgmt/mgmt_common.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright (c) 2016 Hochikong - -CODEC = 'utf8' - - -def messenger(obj, config): - """This method used for sending configuration to VyOS - - :param obj: A connection object - :param config: A configuration string - :return: A message or an error - """ - try: - obj.sendline(config) - obj.prompt() - result = decodetool(obj.before, CODEC) - if len(result) > result.index('\r\n') + 2: - return result - else: - return "Result : Configured successfully" - except Exception as e: - return e - - -def committer(obj, config): - """This method used for sending commit task to VyOS - - :param obj: A connection object - :param config: A configuration string - :return: A message or an error - """ - exception_string = "enhanced syslogd: rsyslogd" - try: - obj.sendline(config) - obj.prompt() - result = decodetool(obj.before, CODEC) - if len(result) > result.index('\r\n') + 2: - if exception_string in result: - return "Result : Commit successfully" - else: - return result - else: - return "Result : Commit successfully" - except Exception as e: - return e - - -def decodetool(target, codec): - """This method is used for decoding obj.before to string when run this - library under python3 - - :param target: The obj.before - :param codec: The codec use to decode - :return: - """ - try: - if type(target) == str: - return target - if type(target) == bytes: - return target.decode(codec) - except Exception as e: - return e diff --git a/vymgmt/router.py b/vymgmt/router.py index d2b35d5..99e9f8f 100644 --- a/vymgmt/router.py +++ b/vymgmt/router.py @@ -1,342 +1,209 @@ # Copyright (c) 2016 Hochikong +import re from pexpect import pxssh -from .mgmt_common import messenger, committer -from .base_exceptions.exceptions_for_set_and_delete import ConfigPathError, ConfigValueError -from .base_exceptions.exceptions_for_commit import CommitFailed, CommitConflict -from .base_exceptions.base import CommonError, MaintenanceError -from .error_distinguish import distinguish_for_set, distinguish_for_delete, distinguish_for_commit + + +class VyOSError(Exception): + pass + + +class ConfigError(VyOSError): + pass + + +class CommitError(ConfigError): + pass + + +class ConfigLocked(CommitError): + pass class Router(object): - def __init__(self, address, cred): + def __init__(self, address, user, password): """Initial a router object :param address: Router address,example:'192.168.10.10' :param cred: Router user and password,example:'vyos:vyos' """ self.__address = address - self.__cred = list(cred) - self.__divi = self.__cred.index(":") - self.__username = ''.join(self.__cred[:self.__divi]) - self.__passwd = ''.join(self.__cred[self.__divi+1:]) + self.__user = user + self.__password = password + + # Session flags + self.__logged_in = False + self.__session_modified = False + self.__session_saved = True + self.__conf_mode = False + self.__conn = pxssh.pxssh() - self.__status = {"status": None, "commit": None, "save": None, "configure": None} - self.__basic_string = {0: 'set ', 1: 'delete '} - def status(self): + # String codec, hardcoded for now + self.__codec = "utf8" + + def __execute_command(self, command): + """This method used for sending configuration to VyOS + + :param obj: A connection object + :param config: A configuration string + :return: A message or an error + """ + self.__conn.sendline(command) + + if not self.__conn.prompt(): + raise VyOSError("Connection timed out") + + output = self.__conn.before + + # XXX: In python3 it's bytes rather than str + if isinstance(output, bytes): + output = output.decode(self.__codec) + return output + + def _status(self): """Check the router object inner status :return: A python dictionary include the status of the router object - """ - return self.__status + """ + return { "logged_in": self.__logged_in, + "session_modified": self.__session_modified, + "session_saved": self.__session_saved, + "conf_mode": self.__conf_mode } def login(self): """Login the router - :return: A message or an error """ - has_error = None - try: - if self.__conn.login(self.__address, self.__username, self.__passwd) is True: - self.__status["status"] = "login" - else: - has_error = 'Type1' - except Exception as e: - return e - - if has_error == 'Type1': - raise MaintenanceError("Error : Connect Failed.") + self.__conn.login(self.__address, self.__user, self.__password) + self.__logged_in = True def logout(self): """Logout the router :return: A message or an error """ - has_error = None - try: - if self.__status["status"] == "login": - if self.__status["configure"] == "No": - self.__conn.close() - self.__status["status"] = "logout" - self.__status["configure"] = None - self.__conn = pxssh.pxssh() - elif self.__status["configure"] is None: - self.__conn.close() - self.__status["status"] = "logout" - self.__conn = pxssh.pxssh() - else: - if self.__status["save"] == "Yes": - has_error = 'Type3' - elif self.__status["save"] is None: - has_error = 'Type3' - else: - if self.__status["commit"] == "Yes": - has_error = 'Type3' - elif self.__status["commit"] is None: - has_error = 'Type3' - else: - has_error = 'Type1' + + if not self.__logged_in: + raise VyOSError("Not logged in") + else: + if self.__conf_mode: + raise VyOSError("Cannot logout before exiting configuration mode") else: - has_error = 'Type4' - except Exception as e: - return e - - if has_error == 'Type1': - raise MaintenanceError("Error : You should commit and exit configure mode first.") -# if has_error == 'Type2': -# raise MaintenanceError("Error : You should save and exit configure mode first.") - if has_error == 'Type3': - raise MaintenanceError("Error : You should exit configure mode first.") - if has_error == 'Type4': - raise MaintenanceError("Error : Router object not connect to a router.") + self.__conn.close() + self.__logged_in = False def configure(self): """Enter the VyOS configure mode - :return: A message or an error """ - has_error = None - try: - if self.__status["status"] == "login": - if self.__status["configure"] is not "Yes": - self.__conn.sendline("configure") - self.__conn.prompt(0) - self.__conn.set_unique_prompt() - self.__status["configure"] = "Yes" - else: - has_error = 'Type1' + if not self.__logged_in: + raise VyOSError("Cannot enter configuration mode when not logged in") + else: + if self.__conf_mode: + raise VyOSError("Session is already in configuration mode") else: - has_error = 'Type2' - except Exception as e: - return e + # configure changes the prompt (from $ to #), so this is + # a bit of a special case, and we use pxssh directly instead + # of the __execute_command wrapper... + self.__conn.sendline("configure") + + # XXX: set_unique_prompt() after this breaks things, for some reason + # We should find out why. + self.__conn.PROMPT = "[#$]" + + if not self.__conn.prompt(): + raise VyOSError("Entering configure mode failed (possibly due to timeout)") + + #self.__conn.set_unique_prompt() + self.__conf_mode = True - if has_error == 'Type1': - raise MaintenanceError("Error : In configure mode now!") - if has_error == 'Type2': - raise MaintenanceError("Error : Router object not connect to a router.") + # XXX: There should be a check for operator vs. admin + # mode and appropriate exception, but pexpect doesn't work + # with operator's overly restricted shell... def commit(self): """Commit the configuration changes - :return: A message or an error """ - has_error = None - result = None - res = None - try: - if self.__status["status"] == "login": - if self.__status["configure"] == "Yes": - if self.__status["commit"] is None: - has_error = 'Type1' - if self.__status["commit"] == "No": - res = committer(self.__conn, "commit") - if "Result" in res: - self.__status["commit"] = "Yes" - else: - result = distinguish_for_commit(res) - else: - has_error = 'Type2' - else: - has_error = 'Type3' + if not self.__conf_mode: + raise VyOSError("Cannot commit without entering configuration mode") + else: + if not self.__session_modified: + raise ConfigError("No configuration changes to commit") else: - has_error = 'Type4' - except Exception as e: - return e - - if has_error == 'Type1': - raise MaintenanceError("Error : You don't need to commit.") - if has_error == 'Type2': - raise MaintenanceError("Error : You have commit!") - if has_error == 'Type3': - raise MaintenanceError("Error : Router not in configure mode!") - if has_error == 'Type4': - raise MaintenanceError("Error : Router object not connect to a router.") - - if result == "CommitFailed": - raise CommitFailed(res) - elif result == "CommitConflict": - raise CommitConflict(res) + output = self.__execute_command("commit") + + if re.search(r"Commit\s+failed", output): + raise CommitError(output) + if re.search(r"another\s+commit\s+in\s+progress", output): + raise ConfigLocked("Configuration is locked due to another commit in progress") + + self.__session_modified = False + self.__session_saved = False def save(self): """Save the configuration after commit - :return: A message or an error """ - has_error = None - try: - if self.__status["status"] == "login": - if self.__status["configure"] == "Yes": - if self.__status["commit"] == "Yes": - if self.__status["save"] is None: - has_error = 'Type1' - if self.__status["save"] == "No": - self.__conn.sendline("save") - self.__conn.prompt(0) - self.__status["save"] = "Yes" - else: - has_error = 'Type2' - elif self.__status["commit"] is None: - has_error = 'Type3' - else: - has_error = 'Type4' - else: - has_error = 'Type5' - else: - has_error = 'Type6' - except Exception as e: - return e - - if has_error == 'Type1': - raise MaintenanceError("Error : You don't need to save.") - if has_error == 'Type2': - raise MaintenanceError("Error : You have saved!") - if has_error == 'Type3': - raise MaintenanceError("Error : You don't need to save.") - if has_error == 'Type4': - raise MaintenanceError("Error : You need to commit first!") - if has_error == 'Type5': - raise MaintenanceError("Error : Router not in configure mode!") - if has_error == 'Type6': - raise MaintenanceError("Error : Router object not connect to a router.") + if not self.__conf_mode: + raise VyOSError("Cannot save when not in configuration mode") + elif self.__session_modified: + raise VyOSError("Cannot save when there are uncommited changes") + else: + self.__execute_command("save") + self.__session_saved = True + def exit(self, force=False): """Exit VyOS configure mode :param force: True or False - :return: A message or an error """ - has_error = None - try: - if self.__status["status"] == "login": - if self.__status["configure"] == "Yes": - if force is True: - self.__conn.sendline("exit discard") - self.__conn.prompt() - self.__status["configure"] = "No" - self.__status["save"] = None - self.__status["commit"] = None - else: - if self.__status["commit"] == "Yes": - if self.__status["save"] == "Yes": - self.__conn.sendline("exit") - self.__conn.prompt() - self.__status["configure"] = "No" - self.__status["save"] = None - self.__status["commit"] = None - elif self.__status["save"] == "No": - self.__conn.sendline("exit") - self.__conn.prompt() - self.__status["configure"] = "No" - self.__status["save"] = None - self.__status["commit"] = None - elif self.__status["commit"] is None: - self.__conn.sendline("exit") - self.__conn.prompt() - self.__status['configure'] = "No" - else: - has_error = 'Type2' + if not self.__conf_mode: + pass + else: + # XXX: would be nice to simplify these conditionals + if self.__session_modified: + if not force: + raise VyOSError("Cannot exit a session with uncommited changes, use force flag to discard") else: - has_error = 'Type3' + self.__execute_command("exit discard") + self.__conf_mode = False + return + elif (not self.__session_saved) and (not force): + raise VyOSError("Cannot exit a session with unsaved changes, use force flag to ignore") else: - has_error = 'Type4' - except Exception as e: - return e - - if has_error == 'Type2': - raise MaintenanceError("Error : You should commit first.") - if has_error == 'Type3': - raise MaintenanceError("Error : You are not in configure mode,no need to exit.") - if has_error == 'Type4': - raise MaintenanceError("Error : Router object not connect to a router.") - - def set(self, config): + self.__execute_command("exit") + self.__conf_mode = False + + def set(self, path): """Basic 'set' method,execute the set command in VyOS :param config: A configuration string. e.g. 'protocols static route ... next-hop ... distance ...' - :return: A message or an error """ - has_error = None - result = None - res = None - full_config = self.__basic_string[0] + config - try: - if self.__status["status"] == "login": - if self.__status["configure"] == "Yes": - res = messenger(self.__conn, full_config) - if "Result" in res: - if self.__status["commit"] == "No": - pass - else: - self.__status["commit"] = "No" - if self.__status["save"] == "No": - pass - else: - self.__status["save"] = "No" - else: - result = distinguish_for_set(res) - else: - has_error = 'Type1' - else: - has_error = 'Type2' - except Exception as e: - return e - - if has_error == 'Type1': - raise MaintenanceError("Error : You are not in configure mode.") - if has_error == 'Type2': - raise MaintenanceError("Error : Router object not connect to a router.") - - if result == "ConfigPathError": - raise ConfigPathError(res) - elif result == "ConfigValueError": - raise ConfigValueError(res) - elif result == "NonsupportButError": - raise CommonError(res) - - def delete(self, config): + if not self.__conf_mode: + raise ConfigError("Cannot execute set commands when not in configuration mode") + else: + output = self.__execute_command("{0} {1}". format("set", path)) + if re.search(r"Set\s+failed", output): + raise ConfigError(output) + elif re.search(r"already exists", output): + raise ConfigError("Configuration path already exists") + self.__session_modified = True + + def delete(self, path): """Basic 'delete' method,execute the delete command in VyOS - :param config: A configuration string. + :param path: A configuration string. e.g. 'protocols static route ... next-hop ... distance ...' - :return: A message or an error """ - has_error = None - result = None - res = None - full_config = self.__basic_string[1] + config - try: - if self.__status["status"] == "login": - if self.__status["configure"] == "Yes": - res = messenger(self.__conn, full_config) - if "Result" in res: - if self.__status["commit"] == "No": - pass - else: - self.__status["commit"] = "No" - if self.__status["save"] == "No": - pass - else: - self.__status["save"] = "No" - else: - result = distinguish_for_delete(res) - else: - has_error = 'Type1' - else: - has_error = 'Type2' - except Exception as e: - return e - - if has_error == 'Type1': - raise MaintenanceError("Error : You are not in configure mode.") - if has_error == 'Type2': - raise MaintenanceError("Error : Router object not connect to a router.") - - if result == "ConfigPathError": - raise ConfigPathError(res) - elif result == "ConfigValueError": - raise ConfigValueError(res) - elif result == "NonsupportButError": - raise CommonError(res) + if not self.__conf_mode: + raise ConfigError("Cannot execute delete commands when not in configuration mode") + else: + output = self.__execute_command("{0} {1}". format("delete", path)) + if re.search(r"Nothing\s+to\s+delete", output): + raise ConfigError(output) + self.__session_modified = True |