Netmiko扩展平台

本文主要是讲解netmiko的两方面扩展,一个是支持platform的扩展,另一个是连接驱动的扩展。以下就是锐捷的Red-Giant系列作为案例进行一一阐述。

1.平台扩展

对于锐捷的Red-Giant系列,netmiko默认是支持的,不支持方面包括:

  • telnet的登录无法兼容,由于采用老版本协议。

  • 交互命令和ruijie_os(默认)存在区别

实现方式:针对CLASS_MAPPER_BASE,CLASS_MAPPER的扩展

1.1.编码设计锐捷的扩展包

1.1.1. 项目说明

项目名称:netmiko_extend,

项目结构:

在这里插入图片描述

1.1.1. 源码说明

ruijie_rg.py
"""Ruijie RGOS Support"""
import sys
import time
from typing import TYPE_CHECKING

from netmiko.channel import Channel
from netmiko.no_enable import NoEnable
from netmiko.utilities import m_exec_time  # noqa

if TYPE_CHECKING:
    pass
from typing import Any

from netmiko.cisco_base_connection import CiscoBaseConnection


class RuijieRGBase(NoEnable,CiscoBaseConnection):
    def session_preparation(self) -> None:
        """Prepare the session after the connection has been established."""
        self._test_channel_read(pattern=r"[>#]")
        self.set_base_prompt()
        """Ruijie OS requires enable mode to set terminal width"""
        self.enable()
        self.set_terminal_width(command="terminal width 256", pattern="terminal")
        self.disable_paging(command="terminal length 0")
        # Clear the read buffer
        time.sleep(0.3 * self.global_delay_factor)
        self.clear_buffer()


    def save_config(self, cmd: str = "write", confirm: bool = False, confirm_response: str = "") -> str:
        """Save config: write"""
        return super().save_config(cmd=cmd, confirm=confirm, confirm_response=confirm_response)

    def establish_connection(self, width: int = 511, height: int = 1000) -> None:
        self.channel: Channel
        if self.protocol == "telnet":
            import pexpect
            import pexpect.popen_spawn
            from netmiko_extend.expect_channel import ExpectChannel

            # linux系统采用以下判断
            if sys.platform.startswith('linux'):
                self.remote_conn = pexpect.spawn('telnet {} {}'.format(self.host, self.port), timeout=self.timeout)
            # win系统采用以下判断
            elif sys.platform.startswith('win'):
                self.remote_conn = pexpect.popen_spawn.PopenSpawn(
                    "tools/plink.exe -telnet {} -P {}".format(self.host, self.port), timeout=self.timeout)                    
            # 忽略大小,在匹配的时候
            self.remote_conn.ignorecase = True
            # self.remote_conn.logfile_read = sys.stdout
            # Migrating communication to channel class
            self.channel = ExpectChannel(conn=self.remote_conn, encoding=self.encoding)
            self.telnet_login()
            return None
        else:
            return super().establish_connection(width=width, height=height)


class RuijieRGSSH(RuijieRGBase):
    pass


class RuijieRGTelnet(RuijieRGBase):
    def __init__(self, *args: Any, **kwargs: Any) -> None:
        default_enter = kwargs.get("default_enter")
        kwargs["default_enter"] = "\r\n" if default_enter is None else default_enter
        super().__init__(*args, **kwargs)
expect_channel.py

采用pexcept扩展支持telnet登录吗,属于驱动扩展,在第二部分详细讲解,此时先跳过

platform_register.py

平台注册类型两两个字典数据的扩展

from netmiko_extend.ruijie.ruijie_rg import RuijieRGSSH, RuijieRGTelnet

class_mapper_base_extend = {
    "ruijie_rg": RuijieRGSSH
}

class_mapper_extend = {
    "ruijie_rg_telnet": RuijieRGTelnet
}

1.2.修改netmiko源码

1.2.1.增加适配处理文件到源码库

增加适配处理文件: register_extend_adapt.py

文件路径:/{项目工程路径}/venv/lib/python3.10/site-packages/netmiko/register_extend_adapt.py

from netmiko_extend.platform_register import class_mapper_base_extend, class_mapper_extend


def extend_class_mapper_base(CLASS_MAPPER_BASE):
    for k, v in class_mapper_base_extend.items():
        CLASS_MAPPER_BASE[k] = v


def extend_class_mapper(CLASS_MAPPER):
    for k, v in class_mapper_extend.items():
        CLASS_MAPPER[k] = v

1.2.1.修改平台映射驱动的源码

修改源码文件: 增加适配处理文件: ssh_dispatcher.py

文件路径:{项目工程路径}/venv/lib/python3.10/site-packages/netmiko/ssh_dispatcher.py

# 增加引用
from netmiko.register_extend_adapt import extend_class_mapper_base,extend_class_mapper
。。。。。。
# 扩展CLASS_MAPPER_BASE,在CLASS_MAPPER_BASE初始设置后面(也就是“FILE_TRANSFER_MAP = {”该行之前)追加
extend_class_mapper_base(CLASS_MAPPER_BASE);
。。。。。。
# 扩展CLASS_MAPPER,在CLASS_MAPPER初始设置后面(也就是“platforms = list(CLASS_MAPPER.keys())”改行之前)追加以下一行代码,
extend_class_mapper(CLASS_MAPPER)

2.链接驱动扩展

由于锐捷Red-Giant系列在netmiko中的telent登录无法实现,所以在此扩展实现telnet登录实现。

经过技术选择采用pexpect交互包实现,但是该包在window的直接使用有一定的问题:

  • 采用pexpect调用telnet客户端实现等,直接子程序退出了

所以抛弃了telnet客户端,采用plink.exe作为expect的子进程处理,好处有很多

  • 不需要再window安装telnet客户端

  • 通过plink这个模块进行不同系统的适配

2.1.拷贝plink.exe 到项目工程中

路径:{项目工程路径}/tools/plink.exe

或者将plink.exe拷贝到在python的安装目录下

在这里插入图片描述

关于plink

  • plink是什么

PuTTY是一个Telnet、SSH、rlogin、纯TCP以及串行接口连接软件。

plink是可以独立使用的exe实现形式,可以让我们直接在命令行制定好命令,然后执行,完成后自动关闭session。

plink是多种通道协议(ssh,telnet)的一个实现。

  • 下载地址

官方主页 http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html

下载地址 https://the.earth.li/~sgtatham/putty/latest/w64/plink.exe

2.1.实现一个基于expect的连接驱动

文件问:expect_channel.py,路径在第一部分中已经说过。

import sys
import time
from typing import Optional

import pexpect
import pexpect.popen_spawn

if sys.platform.startswith('linux'):
    from pexpect import spawn as spawnmix
elif sys.platform.startswith('win'):
    from pexpect.popen_spawn import PopenSpawn as spawnmix
from netmiko.channel import Channel
from netmiko.exceptions import WriteException


class ExpectChannel(Channel):
    # def __init__(self, conn: Optional[spawn], encoding: str) -> None:
    def __init__(self, conn: Optional[spawnmix], encoding: str) -> None:
        """
        Placeholder __init__ method so that reading and writing can be moved to the
        channel class.
        """
        self.remote_conn = conn
        # FIX: move encoding to GlobalState object?
        self.encoding = encoding

    def write_channel(self, out_data: str) -> None:
        if self.remote_conn is None:
            raise WriteException(
                "Attempt to write data, but there is no active channel."
            )

        out_data = out_data.rstrip('\r\n')
        if sys.platform.startswith('linux'):
            # 在linux系统直接使用了pexpect,回车处理交给了pexpect
            self.remote_conn.sendline(out_data)
        elif sys.platform.startswith('win'):
            if "plink.exe" in str(self.remote_conn.proc):
                # 在windows系统虽然使用了pexpect,其他协议实现通道使用了plink,回车处理需要兼容plink
                self.remote_conn.send(out_data + "\r")
            else:
                self.remote_conn.sendline(out_data)

    def read_buffer(self) -> str:
        """Single read of available data."""
        raise NotImplementedError

    def read_channel(self, timeout=10) -> str:
        output_lines = ''

        spawn = self.remote_conn
        if timeout is not None:
            end_time = time.time() + timeout

        trytimes = 0
        try:
            while True:
                # No match at this point
                if (timeout is not None) and (timeout < 0):
                    return output_lines
                # Still have time left, so read more data
                if sys.platform.startswith('linux'):
                    # 在linux系统直接使用了pexpect,回车处理交给了pexpect
                    incoming = spawn.read_nonblocking(spawn.maxread, 0)
                elif sys.platform.startswith('win'):
                    # 在windows系统虽然使用了pexpect,其他协议实现通道使用了plink,回车处理需要兼容plink
                    incoming = spawn.read_nonblocking(spawn.maxread, 0.1)
                    if not incoming:
                        trytimes += 1
                        if trytimes == 3:
                            return output_lines
                else:
                    raise
                output_lines += incoming.decode(self.encoding, "ignore")
                if spawn.delayafterread is not None:
                    time.sleep(spawn.delayafterread)

                if timeout is not None:
                    timeout = end_time - time.time()
        except pexpect.EOF as e:
            return output_lines
        except pexpect.TIMEOUT as e:
            return output_lines
        except Exception as error:
            raise

2.2.platform建立连接重新实现

核心就是实现将指定platform的remot_conn 采用 ExpectChannel实现

代码详细见的ruijie_rg.py的establish_connection的方法实现

。。。。。。
    def establish_connection(self, width: int = 511, height: int = 1000) -> None:
        self.channel: Channel
        if self.protocol == "telnet":
            import pexpect
            import pexpect.popen_spawn
            from netmiko_extend.expect_channel import ExpectChannel

            # linux系统采用以下判断
            if sys.platform.startswith('linux'):
                self.remote_conn = pexpect.spawn('telnet {} {}'.format(self.host, self.port), timeout=self.timeout)
            # win系统采用以下判断
            elif sys.platform.startswith('win'):
                self.remote_conn = pexpect.popen_spawn.PopenSpawn(
                    "tools/plink.exe -telnet {} -P {}".format(self.host, self.port), timeout=self.timeout)                    
            # 忽略大小,在匹配的时候
            self.remote_conn.ignorecase = True
            # self.remote_conn.logfile_read = sys.stdout
            # Migrating communication to channel class
            self.channel = ExpectChannel(conn=self.remote_conn, encoding=self.encoding)
            self.telnet_login()
            return None
        else:
            return super().establish_connection(width=width, height=height)
。。。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值