Skip to content

Latest commit

 

History

History
1207 lines (906 loc) · 62.8 KB

File metadata and controls

1207 lines (906 loc) · 62.8 KB

三、API 和意图驱动的网络

第 2 章低层网络设备交互中,我们研究了使用 Pexpect 和 Paramiko 与网络设备交互的方法。这两种工具都使用一个持久会话,模拟用户键入命令,就像他们坐在终端前面一样。这在一定程度上是可行的。发送命令以便在设备上执行并捕获输出非常容易。然而,当输出超过几行字符时,计算机程序就很难解释输出。Pexpect 和 Paramiko 返回的输出是一系列供人阅读的字符。输出的结构由线条和空间组成,这些线条和空间对人友好,但很难被计算机程序理解

为了使我们的计算机程序自动化我们想要执行的许多任务,我们需要解释返回的结果,并根据返回的结果采取后续行动。当我们无法准确且可预测地解释返回的结果时,我们就无法自信地执行下一个命令

幸运的是,互联网社区解决了这个问题。想象一下,当电脑和人都在阅读网页时,他们之间的区别。人类可以看到由浏览器解释的文字、图片和空间;计算机可以看到原始 HTML 代码、Unicode 字符和二进制文件。当一个网站需要成为另一台计算机的 web 服务时会发生什么?同样的 web 资源需要同时容纳人工客户端和其他计算机程序。对于我们之前提出的问题,这个问题听起来不是很熟悉吗?答案是应用程序接口API)。根据维基百科的说法,重要的是要注意 API 是一个概念,而不是一个特定的技术或框架。

In computer programming, an Application Programming Interface (API) is a set of subroutine definitions, protocols, and tools for building application software. In general terms, it's a set of clearly defined methods of communication between various software components. A good API makes it easier to develop a computer program by providing all the building blocks, which are then put together by the programmer.

在我们的用例中,一组明确定义的通信方法将出现在 Python 程序和目标设备之间。来自我们网络设备的 API 为计算机程序提供了单独的接口。确切的 API 实现是特定于供应商的。一个供应商更喜欢 XML 而不是 JSON,一些供应商可能提供 HTTPS 作为底层传输协议,另一些供应商可能提供 Python 库作为包装器。尽管存在差异,API 的思想仍然是一样的:它是一种为其他计算机程序优化的单独通信方法

在本章中,我们将研究以下主题:

  • 将基础设施视为代码、意图驱动的网络和数据建模
  • Cisco NX-API 和以应用程序为中心的基础架构
  • JuniperNetconf 和 PyEZ
  • Arista eAPI 和 PyEAPI

作为代码的基础设施

在一个完美的世界中,设计和管理网络的网络工程师和架构师应该专注于他们希望网络实现的目标,而不是设备级的交互。在我作为本地 ISP 实习生的第一份工作中,我睁大了眼睛,兴奋不已,我的第一项任务是在客户的网站上安装路由器,打开他们的分数帧中继链路(还记得那些吗?)。我该怎么做?我问。我得到了一个打开帧中继链接的标准操作程序。我去了客户网站,盲目地输入命令,看着闪烁的绿灯,然后高兴地收拾行李,拍拍自己的后背,因为我做得很好。尽管第一份作业很令人兴奋,但我并没有完全理解我在做什么。我只是按照指示去做,没有考虑我输入的命令的含义。如果灯是红色而不是绿色,我将如何排除故障?我想我会打电话回办公室,哭着寻求帮助(眼泪是可选的)。

当然,网络工程不是将命令输入到设备中,而是建立一种方式,使服务能够在尽可能少的摩擦的情况下从一点传递到另一点。我们必须使用的命令和我们必须解释的输出仅仅是达到目的的手段。换句话说,我们应该关注我们对网络的意图。我们希望网络实现的功能比我们用来让设备完成我们希望它完成的任务的命令语法要重要得多。如果我们进一步提取将我们的意图描述为代码行的想法,我们就有可能将我们的整个基础设施描述为一个特定的状态。基础设施将用代码行描述,并使用必要的软件或框架强制执行该状态

意图驱动的网络

自本书第一版出版以来,“基于意图的网络”这一术语在主要网络供应商选择用它来描述他们的下一代设备后,使用率有所上升。在我看来,意图驱动的网络就是定义网络应该处于的状态,并使用软件代码来强制执行该状态。例如,如果我的目标是阻止端口 80 从外部访问,那么我应该将其声明为网络的意图。底层软件将负责了解在边界路由器上配置和应用必要访问列表的语法,以实现该目标。当然,意图驱动的网络是一个在具体实现上没有明确答案的想法。但是这个想法简单明了,我在此认为,我们应该关注网络的意图,并将自己从设备级交互中抽象出来

在使用 API 时,我认为它使我们更接近意图驱动的网络状态。简而言之,因为我们抽象了在目标设备上执行的特定命令的层,所以我们关注的是我们的意图,而不是特定的命令。例如,回到我们的block port 80访问列表示例,我们可以在 Cisco 上使用访问列表和访问组,在 Juniper 上使用过滤器列表。然而,在使用 API 时,我们的程序可以开始询问执行者的意图,同时掩盖他们正在与什么样的物理设备交谈。我们甚至可以使用更高级的声明性框架,如 Ansible,我们将在第 4 章Python 自动化框架–Ansible 基础中介绍。但现在,让我们关注网络 API

屏幕抓取与 API 结构化输出

设想一个常见场景,我们需要登录到网络设备,并确保设备上的所有接口都处于 up/up 状态(状态和协议都显示为up。对于进入 Cisco NX-OS 设备的人类网络工程师来说,在终端中发出show IP interface brief命令非常简单,可以从输出中轻松判断哪个接口启动:

 nx-osv-2# show ip int brief
    IP Interface Status for VRF "default"(1)
    Interface IP Address Interface Status
    Lo0 192.168.0.2 protocol-up/link-up/admin-up
    Eth2/1 10.0.0.6 protocol-up/link-up/admin-up
    nx-osv-2#

换行符、空格和列标题的第一行很容易与人眼区分开来。事实上,他们在那里帮助我们排列,比如说,从第一行到第二行和第三行的每个接口的 IP 地址。如果我们把自己放在计算机的位置上,所有这些空格和换行符只会让我们远离真正重要的输出,即:哪些接口处于 up/up 状态?为了说明这一点,我们可以查看相同操作的 Paramiko 输出:

 >>> new_connection.send('sh ip int briefn')
    16
    >>> output = new_connection.recv(5000)
    >>> print(output)
    b'sh ip int briefrrnIP Interface Status for VRF 
    "default"(1)rnInterface IP Address Interface 
    StatusrnLo0 192.168.0.2 protocol-up/link-up/admin-up 
    rnEth2/1 10.0.0.6 protocol-up/link-up/admin-up rnrnx-
    osv-2# '
    >>>

如果我们要解析这些数据,下面是我将以伪代码的方式做的事情(我将编写的代码的简化表示):

  1. 通过换行符拆分每一行。

  2. 我可能需要也可能不需要包含已执行的show ip interface brief命令的第一行。现在,我想我不需要它。

  3. 取出第二行上的所有内容直到 VRF,并将其保存在一个变量中,因为我们想知道输出显示的是哪个 VRF。

  4. 对于其余的行,因为我们不知道有多少个接口,所以我们将使用正则表达式语句来搜索该行是否以可能的接口开始,例如用于环回的lo和用于以太网接口的Eth

  5. 我们需要通过空格将这一行分成三个部分,每个部分包括接口的名称、IP 地址,然后是接口状态。

  6. 然后,将使用正斜杠(/)进一步拆分接口状态,为我们提供协议、链接和管理状态。

哇,这是一个很大的工作,只是为了一个人可以一目了然!您可能能够优化代码和行数,但一般来说,当我们需要筛选一些非结构化的内容时,我们需要这样做。这种方法有很多缺点,但我能看到的一些更大的问题如下:

  • 可伸缩性:我们花费了大量时间在细致的细节上,以解析每个命令的输出。很难想象,对于我们通常运行的数百个命令,我们如何做到这一点。
  • 可预测性:无法保证不同软件版本之间的输出保持不变。如果输出发生如此微小的变化,可能会使我们来之不易的信息收集战变得毫无用处。
  • 供应商和软件锁定:也许最大的问题是,一旦我们花了所有时间分析这个特定供应商和软件版本的输出,在本例中,思科 NX-OS,我们需要为我们选择的下一个供应商重复这个过程。我不知道你的情况,但是如果我要评估一个新的供应商,如果我不得不重新编写所有的屏幕废弃代码,那么这个新供应商将处于严重的入职劣势。

让我们将其与同一show IP interface brief命令的 NX-API 调用的输出进行比较。我们将在本章后面介绍从设备获取此输出的细节,但这里重要的是将以下输出与之前的屏幕抓取输出进行比较:

    {
     "ins_api":{
     "outputs":{
     "output":{
     "body":{
     "TABLE_intf":[
       {
       "ROW_intf":{
       "admin-state":"up",
       "intf-name":"Lo0",
       "iod":84,
       "ip-disabled":"FALSE",
       "link-state":"up",
       "prefix":"192.168.0.2",
       "proto-state":"up"
       }
       },
     {
     "ROW_intf":{
     "admin-state":"up",
     "intf-name":"Eth2/1",
     "iod":36,
     "ip-disabled":"FALSE",
     "link-state":"up",
     "prefix":"10.0.0.6",
     "proto-state":"up"
     }
     }
     ],
      "TABLE_vrf":[
      {
     "ROW_vrf":{
     "vrf-name-out":"default"
     }
     },
     {
     "ROW_vrf":{
     "vrf-name-out":"default"
     }
     }
     ]
     },
     "code":"200",
     "input":"show ip int brief",
     "msg":"Success"
     }
     },
     "sid":"eoc",
     "type":"cli_show",
     "version":"1.2"
     }
    }

NX-API 可以返回 XML 或 JSON 格式的输出,这就是我们正在查看的 JSON 输出。马上,您可以看到输出是结构化的,并且可以直接映射到 Python 字典数据结构。不需要解析,您只需选择键并检索与键关联的值。您还可以从输出中看到,输出中有各种元数据,例如命令的成功或失败。如果命令失败,将有一条消息告诉发送者失败的原因。您不再需要跟踪发出的命令,因为它已在input字段中返回给您。输出中还有其他有用的元数据,例如 NX-API 版本。

这种类型的交换使供应商和运营商的生活更加轻松。在供应商方面,他们可以轻松地传输配置和状态信息。当需要使用相同的数据结构公开额外的数据时,它们可以添加额外的字段。在运营商方面,他们可以轻松接收信息并围绕信息构建基础设施。人们普遍认为自动化是非常必要的,也是一件好事。问题通常集中在自动化的格式和结构上。正如您将在本章后面看到的,在 API 的保护伞下有许多相互竞争的技术。仅在传输方面,我们就有 RESTAPI、NETCONF 和 RESTCONF 等。最终,整个市场可能会决定未来的最终数据格式。同时,我们每个人都可以形成自己的观点,帮助推动行业向前发展。

作为代码的基础设施数据建模

根据维基百科(https://en.wikipedia.org/wiki/Data_model ),数据模型的定义如下:

A data model is an abstract model that organizes elements of data and standardizes how they relate to one another and to properties of the real-world entities. For instance, a data model may specify that the data element representing a car be composed of a number of other elements which, in turn, represent the color and size of the car and define its owner.

数据建模过程如下图所示:

Data modeling process 

当应用于网络时,我们可以将此概念作为描述网络的抽象模型来应用,无论是数据中心、校园还是全球广域网。如果我们仔细看一看物理数据中心,第 2 层以太网交换机可以被认为是一个包含映射到每个端口的 MAC 地址表的设备。我们的交换机数据模型描述了 MAC 地址应该如何保存在一个表中,其中包括密钥、附加特性(想想 VLAN 和专用 VLAN)等等。类似地,我们可以超越设备,在模型中映射整个数据中心。我们可以从每个访问层、分发层和核心层中的设备数量、它们的连接方式以及它们在生产环境中的行为方式开始。例如,如果我们有一个胖树网络,每个脊椎路由器应该有多少个链接,它们应该包含多少个路由,每个前缀应该有多少个下一跳?这些特征可以映射成一种可以参照理想状态的格式,我们应该始终对照理想状态进行检查。

是另一种新一代)的网络数据建模语言(尽管人们普遍认为,IETF 的一些工作组确实有幽默感),这一语言正在受到关注。它于 2010 年首次在 RFC6020 中发布,此后在供应商和运营商中获得了吸引力。在撰写本文时,对杨的支持因供应商和平台而异。因此,生产中的适应率相对较低。然而,这是一项值得关注的技术。

Cisco API 和 ACI

思科系统,这个 800 磅重的网络大猩猩,并没有错过网络自动化的趋势。在推动网络自动化的过程中,他们进行了各种内部开发、产品增强、合作以及许多外部收购。然而,随着产品线跨越路由器、交换机、防火墙、服务器(统一计算)、无线、协作软件和硬件以及分析软件等,很难知道从哪里开始。

由于本书重点介绍 Python 和网络,因此我们将在本节中介绍主要的网络产品。我们将特别介绍以下内容:

  • 使用 NX-API 实现 Nexus 产品自动化
  • Cisco NETCONF 和 YANG 示例
  • 用于数据中心的 Cisco 以应用程序为中心的基础架构
  • 面向企业的以 Cisco 应用程序为中心的基础架构

对于这里的 NX-API 和 NETCONF 示例,我们可以在实验室设备上始终使用 Cisco DevNet,也可以在本地运行 Cisco VIRL。由于 ACI 是一个单独的产品,并且在以下 ACI 示例中使用了物理交换机,因此我建议使用 DevNet 实验室来了解这些工具。如果你是幸运的工程师之一,拥有一个私人的 ACI 实验室,你可以使用它,请随意使用它的相关例子。

我们将使用与第 2 章低层网络设备交互中类似的实验室拓扑,但运行 nx osv 的一个设备除外:

Lab topology

让我们来看看 NX-API。

思科 NX-API

Nexus 是思科数据中心交换机的主要产品线。NX-API(http://www.cisco.com/c/en/us/td/docs/switches/datacenter/nexus9000/sw/6-x/programmability/guide/b_Cisco_Nexus_9000_Ser ies_NX-OS_ 可编程性 _ 指南/b_Cisco_Nexus_9000_ 系列 NX-OS 可编程性 _ 指南 _ 章节 _011.html允许工程师通过各种传输(包括 SSH、HTTP 和 HTTPS)与设备外部的交换机进行交互。

实验室软件安装和设备准备

下面是我们将要安装的 Ubuntu 软件包。您可能已经有了一些软件包,如pipgit

$ sudo apt-get install -y python3-dev libxml2-dev libxslt1-dev libffi-dev libssl-dev zlib1g-dev python3-pip git python3-requests

If you are using Python 2, use the following packages instead: sudo apt-get install -y python-dev libxml2-dev libxslt1-dev libffi-dev libssl-dev zlib1g-dev python-pip git python-requests.

ncclienthttps://github.com/ncclient/ncclient 库是 NETCONF 客户端的 Python 库。我们将从 GitHub 存储库安装此文件,以便安装最新版本:

$ git clone https://github.com/ncclient/ncclient
$ cd ncclient/
$ sudo python3 setup.py install
$ sudo python setup.py install #for Python 2

默认情况下,Nexus 设备上的 NX-API 处于关闭状态,因此我们需要将其打开。我们可以使用已经创建的用户(如果您使用的是 VIRL auto-config),也可以为 NETCONF 过程创建一个新用户:

feature nxapi
username cisco password 5 $1$Nk7ZkwH0$fyiRmMMfIheqE3BqvcL0C1 role network-operator
username cisco role network-admin
username cisco passphrase lifetime 99999 warntime 14 gracetime 3

对于我们的实验室,我们将同时启用 HTTP 和沙盒配置,因为它们应该在生产中关闭:

nx-osv-2(config)# nxapi http port 80
nx-osv-2(config)# nxapi sandbox

现在我们准备看一下我们的第一个 NX-API 示例。

NX-API 示例

NX-API 沙盒是一种很好的方式,可以使用各种命令、数据格式,甚至可以直接从网页复制 Python 脚本。在最后一步中,我们为了学习目的打开了它。它应该在生产中关闭。让我们启动一个 web 浏览器,看看基于我们已经熟悉的 CLI 命令的各种消息格式、请求和响应:

在下面的例子中,我为show version命令选择了JSON-RPCCLI命令类型:

如果您不确定消息格式的可支持性,或者您对要在代码中检索的值的响应数据字段键有疑问,那么沙箱就很有用。

在我们的第一个示例中,我们将连接到 Nexus 设备,并打印出首次建立连接时交换的功能:

    #!/usr/bin/env python3
    from ncclient import manager
    conn = manager.connect(
            host='172.16.1.90',
            port=22,
            username='cisco',
            password='cisco',
            hostkey_verify=False,
            device_params={'name': 'nexus'},
            look_for_keys=False)
    for value in conn.server_capabilities:
        print(value)
    conn.close_session()

主机、端口、用户名和密码的连接参数都是不言自明的。device 参数指定客户端连接到的设备类型。在使用 ncclient 库时,我们将在 junipernetconf 部分看到不同的响应。hostkey_verify绕过 SSH 的known_host需求;如果没有,则需要在~/.ssh/known_hosts文件中列出主机。look_for_keys选项禁用公钥-私钥身份验证,但使用用户名和密码进行身份验证。

If you run into an issue with paramiko/paramiko#748 with Python 3 and Paramiko, please feel free to use Python 2. Hopefully, by the time you read this section, the issue is already fixed. 

输出将显示此版本 NX-OS 支持的 XML 和 NETCONF 功能:

$ python cisco_nxapi_1.py
urn:ietf:params:netconf:capability:writable-running:1.0
urn:ietf:params:netconf:capability:rollback-on-error:1.0
urn:ietf:params:netconf:capability:validate:1.0
urn:ietf:params:netconf:capability:url:1.0?scheme=file
urn:ietf:params:netconf:base:1.0
urn:ietf:params:netconf:capability:candidate:1.0
urn:ietf:params:netconf:capability:confirmed-commit:1.0
urn:ietf:params:xml:ns:netconf:base:1.0

通过 SSH 使用 ncclient 和 NETCONF 非常好,因为它使我们更接近本机实现和语法。我们将在本书后面使用同一个库。对于 NX-API,处理 HTTPS 和 JSON-RPC 可能更容易。在 NX-API 开发人员沙盒的早期屏幕截图中,如果您注意到,在请求框中,有一个标记为 Python 的框。如果单击它,您将能够根据请求库获得一个自动转换的 Python 脚本。

The following script uses an external Python library named requests. requests is a very popular, self-proclaimed HTTP for the human library used by companies like Amazon, Google, NSA, and more. You can find more information about it on the official site (http://docs.python-requests.org/en/master/).

对于show version示例,将自动为您生成以下 Python 脚本。我在输出中粘贴而不做任何修改:

    """
     NX-API-BOT 
    """
    import requests
    import json

    """
    Modify these please
    """
    url='http://YOURIP/ins'
    switchuser='USERID'
    switchpassword='PASSWORD'

    myheaders={'content-type':'application/json-rpc'}
    payload=[
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "show version",
          "version": 1.2
        },
        "id": 1
      }
    ]
    response = requests.post(url,data=json.dumps(payload), 
    headers=myheaders,auth=(switchuser,switchpassword)).json()

cisco_nxapi_2.py脚本中,您将看到我只修改了前面文件的 URL、用户名和密码。输出被解析为仅包含软件版本。以下是输出:

$ python3 cisco_nxapi_2.py
7.2(0)D1(1) [build 7.2(0)ZD(0.120)]

使用此方法的最佳之处在于,相同的总体语法结构可用于配置命令和显示命令。这在cisco_nxapi_3.py文件中有说明。对于多行配置,可以使用 ID 字段指定操作顺序。在cisco_nxapi_4.py中,列出了以下有效载荷,用于在接口配置模式下更改接口以太网 2/12 的描述:

      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "interface ethernet 2/12",
          "version": 1.2
        },
        "id": 1
      },
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "description foo-bar",
          "version": 1.2
        },
        "id": 2
      },
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "end",
          "version": 1.2
        },
        "id": 3
      },
      {
        "jsonrpc": "2.0",
        "method": "cli",
        "params": {
          "cmd": "copy run start",
          "version": 1.2
        },
        "id": 4
      }
    ]

我们可以通过查看 Nexus 设备的运行配置来验证前面配置脚本的结果:

hostname nx-osv-1-new
...
interface Ethernet2/12
description foo-bar
shutdown
no switchport
mac-address 0000.0000.002f 

在下一节中,我们将介绍 Cisco NETCONF 和 YANG 模型的一些示例。

思科和杨致远模式

在本章前面,我们研究了使用数据建模语言 YANG 表达网络的可能性。让我们通过一些例子来进一步了解它。

首先,我们应该知道 YANG 模型只定义通过 NETCONF 协议发送的数据类型,而没有规定数据应该是什么。其次,值得指出的是,正如我们在 NX-API 一节中看到的那样,NETCONF 是作为一个独立的协议存在的。YANG 相对较新,在供应商和产品线中的支持能力参差不齐。例如,如果我们运行之前用于运行 IOS-XE 的 Cisco 1000v 的相同功能交换脚本,我们将看到:

 urn:cisco:params:xml:ns:yang:cisco-virtual-service?module=cisco-
 virtual-service&revision=2015-04-09
 http://tail-f.com/ns/mibs/SNMP-NOTIFICATION-MIB/200210140000Z?
 module=SNMP-NOTIFICATION-MIB&revision=2002-10-14
 urn:ietf:params:xml:ns:yang:iana-crypt-hash?module=iana-crypt-
 hash&revision=2014-04-04&features=crypt-hash-sha-512,crypt-hash-
 sha-256,crypt-hash-md5
 urn:ietf:params:xml:ns:yang:smiv2:TUNNEL-MIB?module=TUNNEL-
 MIB&revision=2005-05-16
 urn:ietf:params:xml:ns:yang:smiv2:CISCO-IP-URPF-MIB?module=CISCO-
 IP-URPF-MIB&revision=2011-12-29
 urn:ietf:params:xml:ns:yang:smiv2:ENTITY-STATE-MIB?module=ENTITY-
 STATE-MIB&revision=2005-11-22
 urn:ietf:params:xml:ns:yang:smiv2:IANAifType-MIB?module=IANAifType-
 MIB&revision=2006-03-31
 <omitted>

将其与我们在 NX-OS 中看到的输出进行比较。显然,IOS-XE 支持 YANG 机型的功能远远多于 NX-OS。行业范围内的网络数据建模(如果得到支持)显然可以在您的设备上使用,这有利于网络自动化。然而,考虑到供应商和产品的支持不均衡,我认为它还不够成熟,无法专门用于生产网络。在这本书中,我加入了一个名为cisco_yang_1.py的脚本,它展示了如何使用名为urn:ietf:params:xml:ns:yang:ietf-interfaces的过滤器解析 NETCONF XML 输出,作为查看现有标记覆盖的起点。

You can check the latest vendor support on the YANG GitHub project page (https://github.com/YangModels/yang/tree/master/vendor).

思科 ACI

Cisco以应用程序为中心的基础设施ACI)旨在为所有网络组件提供一种集中的方法。在数据中心环境中,这意味着集中式控制器了解并管理 spine、leaf 和 top of rack 交换机以及所有网络服务功能。这可以通过 GUI、CLI 或 API 完成。有些人可能会争辩说,ACI 是思科对更广泛的基于控制器的软件定义网络的回答。

ACI 有点令人困惑的一点是 ACI 和 APIC-EM 之间的区别。简言之,ACI 专注于数据中心运营,而 APIC-EM 专注于企业模块。两者都提供了网络组件的集中视图和控制,但每个组件都有自己的焦点和共享的工具集。例如,很少有大型数据中心部署面向客户的无线基础设施,但无线网络是当今企业的关键组成部分。另一个例子是网络安全的不同方法。虽然安全性在任何网络中都很重要,但在数据中心环境中,许多安全策略被推送到服务器上的边缘节点以实现可扩展性。在企业安全中,策略在一定程度上在网络设备和服务器之间共享。

与 NETCONF-RPC 不同,ACI-API 遵循 REST 模型,使用 HTTP 动词(GETPOSTDELETE)来指定预期的操作。

We can look at the cisco_apic_em_1.py file, which is a modified version of the Cisco sample code on lab2-1-get-network-device-list.py (https://github.com/CiscoDevNet/apicem-1.3-LL-sample-codes/blob/master/basic-labs/lab2-1-get-network-device-list.py).

以下部分列出了没有注释和空格的缩写版本。

第一个名为getTicket()的函数在控制器上使用 HTTPSPOST,路径为/api/v1/ticket,头中嵌入了用户名和密码。此函数将返回仅在有限时间内有效的票证的解析响应:

  def getTicket():
      url = "https://" + controller + "/api/v1/ticket"
      payload = {"username":"usernae","password":"password"}
      header = {"content-type": "application/json"}
      response= requests.post(url,data=json.dumps(payload), headers=header, verify=False)
      r_json=response.json()
      ticket = r_json["response"]["serviceTicket"]
      return ticket

然后,第二个函数调用另一个名为/api/v1/network-devices的路径,将新获取的票证嵌入到报头中,然后解析结果:

url = "https://" + controller + "/api/v1/network-device"
header = {"content-type": "application/json", "X-Auth-Token":ticket}

这是一个非常常见的 API 交互工作流。客户端将在第一个请求中向服务器进行身份验证,并接收基于时间的令牌。此令牌将在后续请求中使用,并将用作身份验证的证明

输出既显示原始 JSON 响应输出,也显示已解析的表。针对 DevNet lab 控制器执行时的部分输出如下所示:

    Network Devices =
    {
     "version": "1.0",
     "response": [
     {
     "reachabilityStatus": "Unreachable",
     "id": "8dbd8068-1091-4cde-8cf5-d1b58dc5c9c7",
     "platformId": "WS-C2960C-8PC-L",
    <omitted>
     "lineCardId": null,
     "family": "Wireless Controller",
     "interfaceCount": "12",
     "upTime": "497 days, 2:27:52.95"
     }
    ]
    }
    8dbd8068-1091-4cde-8cf5-d1b58dc5c9c7 Cisco Catalyst 2960-C Series
     Switches
    cd6d9b24-839b-4d58-adfe-3fdf781e1782 Cisco 3500I Series Unified
    Access Points
    <omitted>
    55450140-de19-47b5-ae80-bfd741b23fd9 Cisco 4400 Series Integrated 
    Services Routers
    ae19cd21-1b26-4f58-8ccd-d265deabb6c3 Cisco 5500 Series Wireless LAN 
    Controllers

如您所见,我们只查询单个控制器设备,但我们能够获得控制器知道的所有网络设备的高级视图。在我们的输出中,Catalyst 2960-C 交换机、3500 接入点、4400 ISR 路由器和 5500 无线控制器都可以进一步开发。当然,缺点是 ACI 控制器目前只支持 Cisco 设备。

用于 Juniper 网络的 pythonapi

Juniper networks 一直是服务提供商的最爱。如果我们退后一步,看看服务提供商的垂直需求,自动化网络设备是他们的首要需求,这是有道理的。在云级数据中心出现之前,网络设备最多的是服务提供商。典型的企业网络可能在公司总部有一些冗余的 internet 连接,一些中心辐射式远程站点使用服务提供商的专用 MPLS 网络连接回总部。对于服务提供商来说,他们需要构建、提供、管理连接和底层网络,并对其进行故障排除。他们通过出售带宽和增值托管服务来赚钱。服务提供商在自动化方面进行投资,以使用最少的工程时间来保持网络正常运行,这是有意义的。在他们的使用案例中,网络自动化是他们竞争优势的关键。

在我看来,服务提供商的网络需求与云数据中心的不同之处在于,传统上,服务提供商将更多的服务聚合到单个设备中。一个很好的例子是几乎所有主要服务提供商都提供的多协议标签交换MPLS),但很少在企业或数据中心网络中使用。Juniper 已经非常成功地发现了这一需求,并且擅长于满足服务提供商的自动化需求。让我们来看看 Juniper 的自动化 API。

Juniper 和 NETCONF

网络配置协议****网络配置协议是 IETF 标准,于 2006 年首次作为RFC 4741发布,之后在RFC 6241中修订。Juniper networks 对这两种 RFC 标准都做出了重大贡献。事实上,Juniper 是 RFC4741 的唯一作者。Juniper 设备完全支持 NETCONF 是有道理的,它是其大多数自动化工具和框架的底层。NETCONF 的一些主要特征包括:

  1. 使用可扩展标记语言XML进行数据编码。
  2. 它使用远程过程调用RPC),因此在 HTTP(s)作为传输的情况下,URL 端点是相同的,而请求主体中指定了预期的操作。
  3. 它在概念上是基于从上到下的层。这些层包括内容、操作、消息和传输:

NETCONF model 

Juniper networks 提供了广泛的 NETCONF XML 管理协议开发人员指南(https://www.juniper.net/techpubs/en_US/junos13.2/information-products/pathway-pages/netconf-guide/netconf.html#overview )在其技术库中。让我们看看它的用法。

设备准备

为了开始使用 NETCONF,让我们创建一个单独的用户并打开所需的服务:

 set system login user netconf uid 2001
 set system login user netconf class super-user
 set system login user netconf authentication encrypted-password
 "$1$0EkA.XVf$cm80A0GC2dgSWJIYWv7Pt1"
 set system services ssh
 set system services telnet
 set system services netconf ssh port 830

For the Juniper device lab, I am using an older, unsupported platform called Juniper Olive. It is solely used for lab purposes. You can use your favorite search engine to find out some interesting facts and history about Juniper Olive.

在 Juniper 设备上,您始终可以查看平面文件或 XML 格式的配置。当您需要指定一个单行命令来进行配置更改时,flat文件非常方便:

 netconf@foo> show configuration | display set
 set version 12.1R1.9
 set system host-name foo
 set system domain-name bar
 <omitted>

当您需要查看配置的 XML 结构时,XML 格式非常方便:

 netconf@foo> show configuration | display xml
 <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos">
 <configuration junos:commit-seconds="1485561328" junos:commit-
 localtime="2017-01-27 23:55:28 UTC" junos:commit-user="netconf">
 <version>12.1R1.9</version>
 <system>
 <host-name>foo</host-name>
 <domain-name>bar</domain-name>

We have installed the necessary Linux libraries and the ncclient Python library in the Cisco section. If you have not done so, refer back to that section and install the necessary packages.

现在我们准备看我们的第一个 JUniterNetconf 示例。

junipernetconf 示例

我们将使用一个非常简单的示例来执行show version。我们将此文件命名为junos_netconf_1.py

  #!/usr/bin/env python3

  from ncclient import manager

  conn = manager.connect(
      host='192.168.24.252',
      port='830',
      username='netconf',
      password='juniper!',
      timeout=10,
      device_params={'name':'junos'},
      hostkey_verify=False)

  result = conn.command('show version', format='text')
  print(result)
  conn.close_session()

除了device_params之外,脚本中的所有字段都应该是不言自明的。从 ncclient 0.4.1 开始,添加了设备处理程序以指定不同的供应商或平台。例如,名称可以是 juniper、CSR、Nexus 或 Huawei。我们还添加了hostkey_verify=False,因为我们使用的是来自 Juniper 设备的自签名证书。

返回的输出是用 XML 编码的rpc-reply,带有output元素:

    <rpc-reply message-id="urn:uuid:7d9280eb-1384-45fe-be48-
    b7cd14ccf2b7">
    <output>
    Hostname: foo
 Model: olive
 JUNOS Base OS boot [12.1R1.9]
 JUNOS Base OS Software Suite [12.1R1.9]
 <omitted>
 JUNOS Runtime Software Suite [12.1R1.9]
 JUNOS Routing Software Suite [12.1R1.9]
    </output>
    </rpc-reply>

我们可以解析 XML 输出,只包含输出文本:

      print(result.xpath('output')[0].text)

junos_netconf_2.py中,我们将对设备进行配置更改。我们将从一些新的导入开始,以构建新的 XML 元素和 connection manager 对象:

      #!/usr/bin/env python3

      from ncclient import manager
      from ncclient.xml_ import new_ele, sub_ele

      conn = manager.connect(host='192.168.24.252', port='830', 
    username='netconf' , password='juniper!', timeout=10, 
    device_params={'name':'junos'}, hostkey_v erify=False)

我们将锁定配置并进行配置更改:

      # lock configuration and make configuration changes
      conn.lock()

      # build configuration
      config = new_ele('system')
      sub_ele(config, 'host-name').text = 'master'
      sub_ele(config, 'domain-name').text = 'python'

在 build configuration 部分,我们创建了一个新元素system,其中包含子元素host-namredomain-name。如果您想知道层次结构,您可以从 XML 显示中看到,带有system的节点结构是host-namedomain-name的父节点结构:

     <system>
        <host-name>foo</host-name>
        <domain-name>bar</domain-name>
    ...
    </system>

构建配置后,脚本将推送配置并提交配置更改。以下是 Juniper 配置更改的常规最佳实践步骤(锁定、配置、解锁、提交):

      # send, validate, and commit config
      conn.load_configuration(config=config)
      conn.validate()
      commit_config = conn.commit()
      print(commit_config.tostring)

      # unlock config
      conn.unlock()

      # close session
      conn.close_session()

总的来说,NETCONF 步骤与您在 CLI 步骤中所做的非常吻合。请看一下junos_netconf_3.py脚本,以获得更可重用的代码。以下示例将逐步示例与一些 Python 函数结合起来:

# make a connection object
def connect(host, port, user, password):
    connection = manager.connect(host=host, port=port, username=user,
            password=password, timeout=10, device_params={'name':'junos'},
            hostkey_verify=False)
    return connection

# execute show commands
def show_cmds(conn, cmd):
    result = conn.command(cmd, format='text')
    return result

# push out configuration
def config_cmds(conn, config):
    conn.lock()
    conn.load_configuration(config=config)
    commit_config = conn.commit()
    return commit_config.tostring

该文件可以自己执行,也可以导入以供其他 Python 脚本使用

Juniper 还提供了一个 Python 库,用于他们的设备,称为 PyEZ。在下一节中,我们将看几个使用该库的示例

面向开发人员的 juniperpyez

PyEZ是一个高级 Python 实现,它可以更好地与现有 Python 代码集成。通过利用 pythonapi,您可以执行常见的操作和配置任务,而无需对 junoscli 有深入的了解。

Juniper maintains a comprehensive Junos PyEZ developer guide at https://www.juniper.net/techpubs/en_US/junos-pyez1.0/information-products/pathway-pages/junos-pyez-developer-guide.html#configuration on their technical library. If you are interested in using PyEZ, I would highly recommend at least a glance through the various topics in the guide.

安装和准备

每个操作系统的安装说明可在安装 Junos PyEZ上找到 https://www.juniper.net/techpubs/en_US/junos-pyez1.0/topics/task/installation/junos-pyez-server-installing.html 第页。我们将展示 Ubuntu 16.04 的安装说明。

以下是一些依赖项包,其中许多包应该已经在运行前面示例的主机上:

$ sudo apt-get install -y python3-pip python3-dev libxml2-dev libxslt1-dev libssl-dev libffi-dev

PyEZ可通过 pip 安装软件包。在这里,我为 Python 3 和 Python 2 安装了:

$ sudo pip3 install junos-eznc
$ sudo pip install junos-eznc

在 Juniper 设备上,需要将 NETCONF 配置为 PyEZ 的底层 XML API:

set system services netconf ssh port 830

对于用户身份验证,我们可以使用密码身份验证或 SSH 密钥对。创建本地用户非常简单:

set system login user netconf uid 2001
set system login user netconf class super-user
set system login user netconf authentication encrypted-password "$1$0EkA.XVf$cm80A0GC2dgSWJIYWv7Pt1"

对于ssh密钥认证,首先在您的主机上生成密钥对:

$ ssh-keygen -t rsa

默认情况下,公钥在~/.ssh/下被称为id_rsa.pub,私钥在同一目录下被称为id_rsa。将私钥视为永远不会共享的密码。公钥可以自由分发。在我们的用例中,我们将把公钥移动到/tmp目录,并使 Python 3 HTTP 服务器模块能够创建一个可访问的 URL:

$ mv ~/.ssh/id_rsa.pub /tmp
$ cd /tmp
$ python3 -m http.server
Serving HTTP on 0.0.0.0 port 8000 ...

For Python 2, use python -m SimpleHTTPServer instead.

从 Juniper 设备,我们可以通过从 Python 3 web 服务器下载公钥来创建用户并关联公钥:

netconf@foo# set system login user echou class super-user authentication load-key-file http://192.168.24.164:8000/id_rsa.pub
/var/home/netconf/...transferring.file........100% of 394 B 2482 kBps

现在,如果我们尝试使用来自管理站的私钥ssh,用户将自动进行身份验证:

$ ssh -i ~/.ssh/id_rsa 192.168.24.252
--- JUNOS 12.1R1.9 built 2012-03-24 12:52:33 UTC
echou@foo>

让我们确保这两种身份验证方法都与 PyEZ 一起工作。让我们尝试用户名和密码的组合:

Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from jnpr.junos import Device
>>> dev = Device(host='192.168.24.252', user='netconf', password='juniper!')
>>> dev.open()
Device(192.168.24.252)
>>> dev.facts
{'serialnumber': '', 'personality': 'UNKNOWN', 'model': 'olive', 'ifd_style': 'CLASSIC', '2RE': False, 'HOME': '/var/home/juniper', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9), 'switch_style': 'NONE', 'fqdn': 'foo.bar', 'hostname': 'foo', 'version': '12.1R1.9', 'domain': 'bar', 'vc_capable': False}
>>> dev.close()

我们还可以尝试使用 SSH 密钥身份验证:

>>> from jnpr.junos import Device
>>> dev1 = Device(host='192.168.24.252', user='echou', ssh_private_key_file='/home/echou/.ssh/id_rsa')
>>> dev1.open()
Device(192.168.24.252)
>>> dev1.facts
{'HOME': '/var/home/echou', 'model': 'olive', 'hostname': 'foo', 'switch_style': 'NONE', 'personality': 'UNKNOWN', '2RE': False, 'domain': 'bar', 'vc_capable': False, 'version': '12.1R1.9', 'serialnumber': '', 'fqdn': 'foo.bar', 'ifd_style': 'CLASSIC', 'version_info': junos.version_info(major=(12, 1), type=R, minor=1, build=9)}
>>> dev1.close()

伟大的我们现在准备看一些 PyEZ 的例子。

皮耶兹例子

在前面的交互式提示中,我们已经看到,当设备连接时,对象会自动检索有关设备的一些事实。在我们的第一个示例junos_pyez_1.py中,我们连接到设备并对show interface em1执行 RPC 调用:

      #!/usr/bin/env python3
      from jnpr.junos import Device
      import xml.etree.ElementTree as ET
      import pprint

      dev = Device(host='192.168.24.252', user='juniper', passwd='juniper!')

      try:
          dev.open()
      except Exception as err:
          print(err)
          sys.exit(1)

      result = 
    dev.rpc.get_interface_information(interface_name='em1', terse=True)
      pprint.pprint(ET.tostring(result))

      dev.close()

设备类有一个包含所有操作命令的rpc属性。这是非常棒的,因为我们在 CLI 和 API 中可以做的事情之间没有任何差别。关键是我们需要找出xml rpc元素标签。在我们的第一个例子中,我们如何知道show interface em1等同于get_interface_information?我们有三种方法来了解这些信息:

  1. 我们可以参考JunosXMLAPI 操作开发者参考

  2. 我们可以使用 CLI 显示 XML RPC 等价物,并用下划线(_替换单词之间的破折号(-

  3. 我们也可以通过使用 PyEZ 库以编程方式实现这一点

我通常使用第二个选项直接获取输出:

 netconf@foo> show interfaces em1 | display xml rpc
 <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1R1/junos">
 <rpc>
 <get-interface-information>
 <interface-name>em1</interface-name>
 </get-interface-information>
 </rpc>
 <cli>
 <banner></banner>
 </cli>
 </rpc-reply>

以下是以编程方式使用 PyEZ 的示例(第三个选项):

 >>> dev1.display_xml_rpc('show interfaces em1', format='text')
 '<get-interface-information>n <interface-name>em1</interface-
 name>n</get-interface-information>n'

当然,我们还需要进行配置更改。在junos_pyez_2.py配置示例中,我们将从 PyEZ 导入一个额外的Config()方法:

      #!/usr/bin/env python3
      from jnpr.junos import Device
      from jnpr.junos.utils.config import Config

我们将使用相同的块连接到设备:

      dev = Device(host='192.168.24.252', user='juniper', 
    passwd='juniper!')

      try:
          dev.open()
      except Exception as err:
          print(err)
          sys.exit(1)

new Config()方法将加载 XML 数据并进行配置更改:

      config_change = """
      <system>
        <host-name>master</host-name>
        <domain-name>python</domain-name>
      </system>
      """

      cu = Config(dev)
      cu.lock()
      cu.load(config_change)
      cu.commit()
      cu.unlock()

      dev.close()

PyEZ 示例设计简单。希望它们能展示如何利用 PyEZ 满足 Junos 自动化需求。

Arista Python API

Arista Networks一直专注于大型数据中心网络。在其公司简介页面(https://www.arista.com/en/company/company-overview ),声明如下:

"Arista Networks was founded to pioneer and deliver software-driven cloud networking solutions for large data center storage and computing environments."

请注意,该声明特别提到了大型数据中心,我们已经知道,这些数据中心的服务器、数据库以及网络设备数量激增。自动化一直是 Arista 的主要功能之一,这是有道理的。事实上,他们的操作系统背后有一个 Linux 支撑,允许许多额外的好处,如 Linux 命令和内置 Python 解释器。

与其他供应商一样,您可以通过 eAPI 直接与 Arista 设备交互,也可以选择利用其Python库。我们将看到这两个例子。在后面的章节中,我们还将介绍 Arista 与 Ansible 框架的集成。

阿里斯塔 eAPI 管理

Arista 的 eAPI 是几年前在 EOS 4.12 中首次引入的。它通过 HTTP 或 HTTPS 传输显示或配置命令列表,并以 JSON 进行响应。一个重要的区别是,它是一个远程过程调用RPCJSON-RPC,而不是通过 HTTP 或 HTTPS 提供服务的纯 RESTFul API。出于我们的意图和目的,不同之处在于我们使用相同的 HTTP 方法(POST向相同的 URL 端点发出请求。我们不使用 HTTP 动词(GETPOSTPUTDELETE)来表达我们的行为,而只是在请求主体中陈述我们的预期行为。在 eAPI 的情况下,我们将指定一个带有runCmds值的method键作为我们的目的。

对于以下示例,我使用运行 EOS 4.16 的物理 Arista 交换机。

eAPI 准备

Arista 设备上的 eAPI 代理在默认情况下是禁用的,因此我们需要在设备上启用它,然后才能使用它:

arista1(config)#management api http-commands
arista1(config-mgmt-api-http-cmds)#no shut
arista1(config-mgmt-api-http-cmds)#protocol https port 443
arista1(config-mgmt-api-http-cmds)#no protocol http
arista1(config-mgmt-api-http-cmds)#vrf management

如您所见,我们已关闭 HTTP 服务器,并使用 HTTPS 作为唯一传输。从几个 EOS 版本开始,默认情况下,管理接口驻留在名为**管理的 VRF 中。**在我的拓扑中,我通过管理接口访问设备;因此,我为 eAPI 管理指定了 VRF。您可以通过“显示管理 API http 命令”命令检查 API 管理状态:

arista1#sh management api http-commands
Enabled: Yes
HTTPS server: running, set to use port 443
HTTP server: shutdown, set to use port 80
Local HTTP server: shutdown, no authentication, set to use port 8080
Unix Socket server: shutdown, no authentication
VRF: management
Hits: 64
Last hit: 33 seconds ago
Bytes in: 8250
Bytes out: 29862
Requests: 23
Commands: 42
Duration: 7.086 seconds
SSL Profile: none
QoS DSCP: 0
 User Requests Bytes in Bytes out Last hit
----------- -------------- -------------- --------------- --------------
 admin 23 8250 29862 33 seconds ago

URLs
-----------------------------------------
Management1 : https://192.168.199.158:443

arista1#

启用代理后,您将能够通过访问设备的 IP 地址访问 eAPI 的探索页面。如果更改了访问的默认端口,只需将其附加到末尾即可。身份验证绑定到交换机上的身份验证方法中。我们将使用设备上本地配置的用户名和密码。默认情况下,将使用自签名证书:

Arista EOS explorer

您将进入一个资源管理器页面,在该页面中,您可以键入 CLI 命令并获得请求正文的良好输出。例如,如果我想了解如何为show version创建请求正文,这是我将从 explorer 中看到的输出:

Arista EOS explorer viewer

概述链接将带您进入示例使用和背景信息,而命令文档将用作显示命令的参考点。每个命令引用将包含返回值字段名称、类型和简要说明。Arista 的在线参考脚本使用 jsonrpclib(https://github.com/joshmarshall/jsonrpclib/ ),这是我们将使用的。然而,在撰写本书时,它还依赖于 Python 2.6+,还没有移植到 Python 3;因此,我们将在这些示例中使用 Python2.7。

By the time you read this book, there might be an updated status. Please read the GitHub pull request (joshmarshall/jsonrpclib#38) and the GitHub README (https://github.com/joshmarshall/jsonrpclib/) for the latest status.

使用easy_installpip直接安装:

$ sudo easy_install jsonrpclib
$ sudo pip install jsonrpclib

eAPI 示例

然后我们可以编写一个名为eapi_1.py的简单程序来查看响应文本:

      #!/usr/bin/python2

      from __future__ import print_function
      from jsonrpclib import Server
      import ssl

      ssl._create_default_https_context = ssl._create_unverified_context

      switch = Server("https://admin:arista@192.168.199.158/command-api")

      response = switch.runCmds( 1, [ "show version" ] )
      print('Serial Number: ' + response[0]['serialNumber'])

Note that, since this is Python 2, in the script, I used the from __future__ import print_function to make future migration easier. The ssl-related lines are for Python version > 2.7.9. For more information, please see https://www.python.org/dev/peps/pep-0476/.

这是我从前面的runCms()方法中得到的响应:

 [{u'memTotal': 3978148, u'internalVersion': u'4.16.6M-
 3205780.4166M', u'serialNumber': u'<omitted>', u'systemMacAddress':
 u'<omitted>', u'bootupTimestamp': 1465964219.71, u'memFree': 
 277832, u'version': u'4.16.6M', u'modelName': u'DCS-7050QX-32-F', 
 u'isIntlVersion': False, u'internalBuildId': u'373dbd3c-60a7-4736-
 8d9e-bf5e7d207689', u'hardwareRevision': u'00.00', u'architecture': 
 u'i386'}]

如您所见,结果是一个包含一个字典项的列表。如果我们需要获取序列号,我们只需参考项目编号和钥匙:

     print('Serial Number: ' + response[0]['serialNumber'])

输出将仅包含序列号:

$ python eapi_1.py
Serial Number: <omitted>

为了更熟悉命令参考,我建议您单击 eAPI 页面上的命令文档链接,并将您的输出与文档中的 show version 输出进行比较。

如前所述,与 REST 不同,JSON-RPC 客户端使用相同的 URL 端点调用服务器资源。您可以从前面的示例中看到,runCmds()方法包含一个命令列表。对于配置命令的执行,您可以遵循相同的框架,并通过命令列表配置设备。

下面是一个名为eapi_2.py的配置命令示例。在我们的示例中,我们编写了一个函数,该函数将 switch 对象和命令列表作为属性:

      #!/usr/bin/python2

      from __future__ import print_function
      from jsonrpclib import Server
      import ssl, pprint

      ssl._create_default_https_context = ssl._create_unverified_context

      # Run Arista commands thru eAPI
      def runAristaCommands(switch_object, list_of_commands):
          response = switch_object.runCmds(1, list_of_commands)
          return response

      switch = Server("https://admin:arista@192.168.199.158/command-
    api")

      commands = ["enable", "configure", "interface ethernet 1/3", 
    "switchport acc ess vlan 100", "end", "write memory"]

     response = runAristaCommands(switch, commands)
     pprint.pprint(response)

以下是命令执行的输出:

$ python2 eapi_2.py
[{}, {}, {}, {}, {}, {u'messages': [u'Copy completed successfully.']}]

现在,快速检查switch以验证命令的执行:

arista1#sh run int eth 1/3
interface Ethernet1/3
    switchport access vlan 100
arista1# 

总的来说,eAPI 是相当直接和简单的使用。大多数编程语言都有类似于jsonrpclib的库,它抽象出 JSON-RPC 内部。只需几个命令,您就可以开始将 Arista EOS automation 集成到您的网络中。

阿里斯塔·皮亚皮图书馆

Python 客户端 Pyeapi(http://pyeapi.readthedocs.io/en/master/index.html 库是围绕 eAPI 的本机 Python 库包装器。它提供了一组绑定来配置 Arista EOS 节点。既然我们已经有了 eAPI,为什么还需要 Pyeapi?如果您在 Python 环境中,在 Pyeapi 和 eAPI 之间进行选择主要是一种判断

但是,如果您处于非 Python 环境中,eAPI 可能是一种可行的方法。从我们的示例中,您可以看到 eAPI 的唯一要求是支持 JSON-RPC 的客户端。因此,它与大多数编程语言兼容。当我第一次涉足该领域时,Perl 是脚本编写和网络自动化的主导语言。仍然有许多企业依赖 Perl 脚本作为主要的自动化工具。如果公司已经投入了大量资源,并且代码库使用的语言不是 Python,那么 eAPI 和 JSON-RPC 将是一个不错的选择。

然而,对于我们这些喜欢用 Python 编写代码的人来说,本地的Python库意味着在编写代码时有一种更自然的感觉。这无疑使扩展 Python 程序以支持 EOS 节点变得更容易。它还使跟上 Python 的最新变化变得更容易。例如,我们可以将 Python3 与 Pyeapi 一起使用!

At the time of writing this book, Python 3 (3.4+) support is officially a work-in-progress, as stated in the documentation (http://pyeapi.readthedocs.io/en/master/requirements.html). Please check the documentation for more details.

Pyeapi 安装

pip 的安装非常简单:

$ sudo pip install pyeapi
$ sudo pip3 install pyeapi

Note that pip will also install the netaddr library as it is part of the stated requirements (http://pyeapi.readthedocs.io/en/master/requirements.html) for Pyeapi.

默认情况下,Pyeapi 客户端将在主目录中查找名为eapi.conf的 INI 样式隐藏文件(前面有一个句点)。您可以通过指定eapi.conf文件路径来覆盖此行为,但通常最好将连接凭据与脚本本身分离并锁定。您可以查看 Arista Pyeapi 文档(http://pyeapi.readthedocs.io/en/master/configfile.html#configfile ),用于文件中包含的字段。以下是我在实验室使用的文件:

cat ~/.eapi.conf
[connection:Arista1]
host: 192.168.199.158
username: admin
password: arista
transport: https

第一行,[connection:Arista1]包含我们将在 Pyeapi 连接中使用的名称;其余的字段应该是不言自明的。您可以为使用此文件的用户将文件锁定为只读:

$ chmod 400 ~/.eapi.conf
$ ls -l ~/.eapi.conf
-r-------- 1 echou echou 94 Jan 27 18:15 /home/echou/.eapi.conf

Pyeapi 示例

现在,我们准备好了解一下用法。让我们首先通过在交互式 Python shell 中创建一个对象连接到 EOS 节点:

Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyeapi
>>> arista1 = pyeapi.connect_to('Arista1')

我们可以向节点执行 show 命令并接收输出:

>>> import pprint
>>> pprint.pprint(arista1.enable('show hostname'))
[{'command': 'show hostname',
 'encoding': 'json',
 'result': {'fqdn': 'arista1', 'hostname': 'arista1'}}]

配置字段可以是单个命令,也可以是使用config()方法的命令列表:

>>> arista1.config('hostname arista1-new')
[{}]
>>> pprint.pprint(arista1.enable('show hostname'))
[{'command': 'show hostname',
 'encoding': 'json',
 'result': {'fqdn': 'arista1-new', 'hostname': 'arista1-new'}}]
>>> arista1.config(['interface ethernet 1/3', 'description my_link'])
[{}, {}]

请注意,命令缩写(show runshow running-config和一些扩展将不起作用:

>>> pprint.pprint(arista1.enable('show run'))
Traceback (most recent call last):
...
 File "/usr/local/lib/python3.5/dist-packages/pyeapi/eapilib.py", line 396, in send
 raise CommandError(code, msg, command_error=err, output=out)
pyeapi.eapilib.CommandError: Error [1002]: CLI command 2 of 2 'show run' failed: invalid command [incomplete token (at token 1: 'run')]
>>>
>>> pprint.pprint(arista1.enable('show running-config interface ethernet 1/3'))
Traceback (most recent call last):
...
pyeapi.eapilib.CommandError: Error [1002]: CLI command 2 of 2 'show running-config interface ethernet 1/3' failed: invalid command [incomplete token (at token 2: 'interface')]

但是,您始终可以捕获结果并获得所需的值:

>>> result = arista1.enable('show running-config')
>>> pprint.pprint(result[0]['result']['cmds']['interface Ethernet1/3'])
{'cmds': {'description my_link': None, 'switchport access vlan 100': None}, 'comments': []}

到目前为止,我们一直在使用 eAPI 执行 show 和 configuration 命令。Pyeapi 提供了各种 api,使生活更轻松。在下面的示例中,我们将连接到节点,调用 VLAN API,并开始对设备的 VLAN 参数进行操作。让我们来看一看:

>>> import pyeapi
>>> node = pyeapi.connect_to('Arista1')
>>> vlans = node.api('vlans')
>>> type(vlans)
<class 'pyeapi.api.vlans.Vlans'>
>>> dir(vlans)
[...'command_builder', 'config', 'configure', 'configure_interface', 'configure_vlan', 'create', 'default', 'delete', 'error', 'get', 'get_block', 'getall', 'items', 'keys', 'node', 'remove_trunk_group', 'set_name', 'set_state', 'set_trunk_groups', 'values']
>>> vlans.getall()
{'1': {'vlan_id': '1', 'trunk_groups': [], 'state': 'active', 'name': 'default'}}
>>> vlans.get(1)
{'vlan_id': 1, 'trunk_groups': [], 'state': 'active', 'name': 'default'}
>>> vlans.create(10)
True
>>> vlans.getall()
{'1': {'vlan_id': '1', 'trunk_groups': [], 'state': 'active', 'name': 'default'}, '10': {'vlan_id': '10', 'trunk_groups': [], 'state': 'active', 'name': 'VLAN0010'}}
>>> vlans.set_name(10, 'my_vlan_10')
True

让我们验证 VLAN 10 是否已在设备上创建:

arista1#sh vlan
VLAN Name Status Ports
----- -------------------------------- --------- -------------------------------
1 default active
10 my_vlan_10 active

如您所见,EOS 对象上的 Python 本机 API 确实是 Pyeapi 超越 eAPI 的地方。它将较低级别的属性抽象到设备对象中,使代码更清晰、更易于阅读。

For a full list of ever increasing Pyeapi APIs, check the official documentation (http://pyeapi.readthedocs.io/en/master/api_modules/_list_of_modules.html).

为了总结这一章,我们假设重复前面的步骤足够多的时间,以便编写另一个 Python 类来节省一些工作。pyeapi_1.py脚本如下所示:

      #!/usr/bin/env python3

      import pyeapi

      class my_switch():

          def __init__(self, config_file_location, device):
               # loads the config file
               pyeapi.client.load_config(config_file_location)
               self.node = pyeapi.connect_to(device)
               self.hostname = self.node.enable('show hostname')[0]
    ['result']['host name']
              self.running_config = self.node.enable('show running-
    config')

           def create_vlan(self, vlan_number, vlan_name):
               vlans = self.node.api('vlans')
               vlans.create(vlan_number)
               vlans.set_name(vlan_number, vlan_name) 

从脚本中可以看到,我们自动连接到节点,并在连接时设置主机名和running_config。我们还使用VLANAPI 为创建 VLAN 的类创建了一个方法。让我们在交互式 shell 中试用该脚本:

Python 3.5.2 (default, Nov 17 2016, 17:05:23)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyeapi_1
>>> s1 = pyeapi_1.my_switch('/tmp/.eapi.conf', 'Arista1')
>>> s1.hostname
'arista1'
>>> s1.running_config
[{'encoding': 'json', 'result': {'cmds': {'interface Ethernet27': {'cmds': {}, 'comments': []}, 'ip routing': None, 'interface face Ethernet29': {'cmds': {}, 'comments': []}, 'interface Ethernet26': {'cmds': {}, 'comments': []}, 'interface Ethernet24/4': h.': 
<omitted>
'interface Ethernet3/1': {'cmds': {}, 'comments': []}}, 'comments': [], 'header': ['! device: arista1 (DCS-7050QX-32, EOS-4.16.6M)n!n']}, 'command': 'show running-config'}]
>>> s1.create_vlan(11, 'my_vlan_11')
>>> s1.node.api('vlans').getall()
{'11': {'name': 'my_vlan_11', 'vlan_id': '11', 'trunk_groups': [], 'state': 'active'}, '10': {'name': 'my_vlan_10', 'vlan_id': '10', 'trunk_groups': [], 'state': 'active'}, '1': {'name': 'default', 'vlan_id': '1', 'trunk_groups': [], 'state': 'active'}}
>>>

供应商中立的库

供应商中立的库(如 Netmiko)()有一些出色的成果 https://github.com/ktbyers/netmiko 和凝固汽油弹(https://github.com/napalm-automation/napalm )。由于这些库不是由设备供应商提供的,因此它们有时在支持最新平台或功能时会慢一步。但是,由于这些库与供应商无关,如果您不喜欢工具的供应商锁定,那么这些库是一个不错的选择。使用这些库的另一个好处是,它们通常是开源的,因此您可以为新特性和错误修复向上游提供帮助

另一方面,因为这些库是社区支持的,所以如果您需要依赖其他人来修复 bug 或实现新功能,它们不一定是理想的选择。如果您有一个相对较小的团队,仍然需要遵守工具的某些服务级别保证,那么最好使用供应商支持的库

总结

在本章中,我们介绍了 Cisco、Juniper 和 Arista 的各种通信和管理网络设备的方法。我们研究了与 NETCONF 和 REST 等的直接通信,以及使用供应商提供的库,如 PyEZ 和 Pyeapi。这些是不同的抽象层,旨在提供一种无需人工干预即可以编程方式管理网络设备的方法。

第 4 章Python 自动化框架–Ansible 基础中,我们将了解一个更高级别的与供应商无关的抽象框架,称为Ansible。Ansible 是一个用 Python 编写的开源通用自动化工具。它可以用于自动化服务器、网络设备、负载平衡器等。当然,出于我们的目的,我们将着重于为网络设备使用这个自动化框架。