⚠ This page is served via a proxy. Original site: https://github.com
This service does not collect credentials or authentication data.
Skip to content
This repository was archived by the owner on Nov 10, 2023. It is now read-only.
This repository was archived by the owner on Nov 10, 2023. It is now read-only.

Refactoring the code #34

@selfuryon

Description

@selfuryon

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 """
        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

Metadata

Metadata

Assignees

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions