-
Notifications
You must be signed in to change notification settings - Fork 46
Refactoring the code #34
Description
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:
- IOConnection as an interface for working for a particular connection
- As examples of a particular connection are SSHConnection and Telnet Connection
- DeviceStream adds some logic to basic IOConnection: it understands the end of the output: it recognizes the prompt
- LayerManager manages all levels of layers or terminal modes
- Layer is an entity for working with a particular layer or terminal mode
- Particular set of device type classes (like CiscoIOS, JuniperJunOS) for working with devices. They contain a set of strategy class instances
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 """
passIOConnection 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):
passDevice 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):
passSpecific 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._nameAnd 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