selfuryon / netdev

Asynchronous multi-vendor library for interacting with network devices
http://netdev.readthedocs.io/
Apache License 2.0
213 stars 45 forks source link

Refactoring the code #34

Open selfuryon opened 5 years ago

selfuryon commented 5 years ago

Hello! I want to make the module refactoring.

The first thing I want to separate the connection and the class which knows how to work with device. The second thing I want to use the Strategy pattern instead of Inheritance due to lack of isolation.

Common class structure:

Try to show the concept of these changes.

Public factory method:


# Public Factory function for creating netdev classes
def create(device_type, connection_type, *args, **kwargs):
    # Create IOConnection separately
    if connection_type == "ssh":
        IOConnection = SSHConnectiom(*args, **kwargs) # As Example for SSHConnection

    # Create DeviceStream and all needed instances for a particular device
    device_stream = DeviceStream(IOConnection)

    # Create Device separately
    if device_type == "cisco_ios":
        layer_manager = LayerManager(device_stream, cisco_checker)
            .add_layer(UserExecMode())
            .add_layer(PrivilegeExecMode())
            .add_layer(ConfigMode())
        return Cisco_IOS(layer_manager)

IOConnection is an abstract class with a public interface which can be used by all particular device type classes.

class IOConnection(abc.ABC):
    """ Abstract IO Connection class"""

    async def connect(self):
        """ Establish Connection """
        pass

    async def disconnect(self):
        pass

    def send(self, cmd):
        """ Send command to stream """
        pass

    async def read(self):
        """ Read from stream """
        pass

IOConnection can be implemented in SSHConnection, TelnetConnection and SerialConnection.

DeviceStream adds some logic to IOConnection. It understands the end of the output for commands: it understands the prompt

class DeviceStream():
    """ Class which know how to work with the device in a stream mode """

    def __init__ (self, IOConnection, prompt_pattern = r"", ):
        self._conn = IOConnection
        self._prompt_pattern = prompt_pattern

    async def send(self, cmd_list):
        pass

    async def read_until(self, pattern, re_flags, until_prompt=True):
        pass

Device type classes are particular classes for working with network devices.

class CiscoIOS():
    """ Abstract Device type"""

    def __init__ (device_stream, layer_manager):
        self._device_stream = device_stream
        self._layer_manager = layer_manager

    async def send_command(self, cmd_list, terminal_mode):
        """ Go to specific terminal mode and run list of commands in there"""
        self._layer_manager.switch_to_layer(terminal_mode)
        self.device_stream(cmd_list)

    async def send_config_set(self, cmd_list):
        """ Go to configuration mode and run list of commands in there"""
        self._layer_manager.switch_to_layer('config_mode')
        self.device_stream(cmd_list)
        self._layer_manager.switch_to_layer('privilege exec')

We have universal class LayerManager for working with terminal modes:

class LayerManager():

    def __init__(self, device_stream, checker_closure):
        self._device_stream = device_stream
        self._checker_closure = checker_closure
        self._current_layer = None

    def add_layer(layer):
        self._layers[layer.name] = layer
        return self

    def switch_to_layer(layer_name):
        if self._current_layer is None:
            self._current_layer = self.checker_closure()
        if self._current_layer == layer_name:
            return
        # switching to layer

    def commit_transaction():
        layer = get_layer(self._current_layer)
        if layer.transactional:
            layer.committer()

    def get_layer(layer_name):
        pass

Specific function for checking the cisco like terminal modes:


def cisco_checker(device_stream):
    if ')#':
        return 'config'
    elif '#':
        return 'privilege'
    elif '>':
        return 'user_exec'
    else:
        raise Exeption()

We have universal class Layer for working with particular layer/terminal mode:

class Layer():

    def __init__(name, device_stream, enter, exit, transactional=False, commiter=None):
        self._name = name 
        self._device_stream = device_stream
        self._enter_closure = enter
        self._exit_closure = exit
        self._committer_closure = committer
        self.transactional = transactional

    async def enter(cmd):
        pass

    async def exit(cmd):
        pass

    @atribute
    def get_name():
        return self._name

And cisco like layers:

class ConfigMode(Layer):

    def __init__(name, device_stream, enter, exit, transactional=False, commiter=None):
        super().__init()

    async def enter(cmd='conf t'):
        self._device_stream.send_command(cmd)

    async def exit(cmd='end'):
        self._device_stream.send_command(cmd)

    @atribute
    def get_name():
        return self._name