NVUE API

当您升级到 Cumulus Linux 5.6 或更高版本时,交换机将覆盖您在 Cumulus Linux 5.5 或更早版本中通过编辑文件执行的任何手动配置,例如配置侦听地址、端口、TLS 或证书。

除了 CLI 之外,NVUE 还支持 REST API。除了使用 SSH 访问 Cumulus Linux 之外,您还可以使用 HTTP 客户端(例如 cURL 或 Web 浏览器)与交换机进行交互。

nvued 服务提供对 NVUE REST API 的访问。Cumulus Linux 在内部公开 HTTP 端点,这使得 NVUE REST API 可以在 Cumulus Linux 交换机本地访问。NVUE CLI 也使用内部 API 与 nvued 服务通信。为了提供对 NVUE REST API 的外部访问,Cumulus Linux 使用 HTTP 反向代理服务器,并支持来自外部 REST API 客户端的 HTTPS 和 TLS 连接。

下图显示了 NVUE REST API 架构,并说明了 Cumulus Linux 如何在内部转发请求。

支持的 HTTP 方法

NVUE REST API 支持以下方法

  • GET 方法显示配置和操作数据,并且等效于 nv show 命令。
  • POST 方法创建和提交操作。您通常将此方法用于 nv action 命令以及用于创建修订版本的 nv config 命令。
  • PATCH 方法替换或取消设置配置。您将此方法用于 nv setnv config apply 命令。您可以执行以下任一操作
    • 目标配置补丁以进行配置更改,您可以在其中运行针对特定 OpenAPI 端点 URI 的特定 NVUE REST API。根据 NVUE 架构定义,您需要将 PATCH REST API 请求定向到特定端点(例如,/nvue_v1/vrf/<vrf-id>/router/bgp),并提供符合架构的有效负载。使用目标配置补丁,您可以控制各个资源。
    • 补丁,您可以在其中在架构的根节点上运行 NVUE PATCH API,以便单个 PATCH 操作可以在单个有效负载中更改一个、某些或整个配置。PATCH 方法的有效负载必须了解整个 NVUE 对象模型架构,因为您相对于根节点 /nvue_v1 进行配置更改。您通常执行根补丁以批量将所有配置推送到交换机;例如,如果您使用 SDN 控制器或网络管理系统,则每次需要进行更改时都推送整个交换机配置,无论更改大小。根补丁还可以通过更少的往返交换机次数进行配置更改。
    • PATCH 请求中的输入有效负载可以具有同一资源的 setunset json 对象,但不能同时具有两者。API 执行 setunset 对象的顺序是不确定的且不受支持。
  • DELETE 方法删除配置,并且等效于 nv unset 命令。

在 Cumulus Linux 5.9 及更早版本中,REST API PATCH 响应返回 NVUE 系统的完整状态(您的配置更改以及交换机上的所有其他 NVUE 配置),这对于大规模配置来说可能效率低下,因为系统状态会随着配置的增长而增长。在 Cumulus Linux 5.10 及更高版本中,REST API PATCH 响应仅返回生成的配置更改。响应通常等于请求有效负载;但是,在某些情况下,响应会返回 NVUE 服务器自动修补的其他更改。例如,当使用像 swp1 这样的命名良好的接口名称时,NVUE 会自动配置 type

PATCH request: {'interface': {'swp1': {}}
PATCH Response: {'interface': {'swp1': {'type': 'swp'}},
...

在 Cumulus Linux 5.10 及更高版本中,DELETE 响应返回 204(No Content) 状态代码。在 Cumulus Linux 5.9 及更早版本中,DELETE 响应返回 200 和一个空的 json 正文 ({})。

保护 API 安全

NVUE REST API 支持 HTTP 基本身份验证,以及 NVUE CLI 支持的相同的用户名和密码的底层身份验证方法。用户帐户在 API 和 CLI 上的工作方式相同。

证书

Cumulus Linux 包含自签名证书和私钥,以便在服务器上使用,从而使其开箱即可用。交换机在首次启动时生成自签名证书和私钥。带有公钥的 X.509 证书位于 /etc/ssl/certs/cumulus.pem 中,相应的私钥位于 /etc/ssl/private/cumulus.key 中。

NVIDIA 建议您使用自己的证书和密钥。有关生成自签名证书和密钥的步骤,请参阅 Ubuntu 证书和安全文档

Cumulus Linux 允许您管理 CA 证书(例如 DigiCert 或 Verisign)和实体(端点)证书。CA 证书和实体证书都可以包含证书链。

您可以将证书导入到交换机上(从外部源获取证书)、设置要用于 NVUE REST API 的证书,并显示有关证书的信息,例如序列号以及证书有效的日期和时间。

导入证书

  • 您最多可以导入 25 个实体证书和最多 25 个 CA 捆绑包。每个 CA 捆绑包文件最多支持 100 个 CA 证书。
  • 您导入的证书包含敏感的私钥信息。NVIDIA 建议您使用安全传输,例如 SFTP、SCP 或 HTTPS。

  • 要导入实体证书,请运行 nv action import system security certificate <cert-id> 命令。
  • 要导入 CA 证书捆绑包文件,请运行 nv action import system security ca-certificate <cert-id> 命令。

如果证书受密码保护,则需要包含密码。

您必须提供证书 ID (<cert-id>) 以唯一标识您导入的证书。

以下示例导入带有公钥的 CA 证书捆绑包,并将证书称为 tls-cert-1。证书受密码 mypassphrase 保护。公钥是 Base64 ASCII 编码的 PEM 字符串。

  • 您必须使用三个双引号 ("""<public-key>""") 将公钥括在 NVUE 命令中。
  • 使用 REST API 时,您必须使用一个双引号 ("<public-key>") 将公钥括起来。

cumulus@switch:~$ nv action import system security ca-certificate tls-cert-1 passphrase mypassphrase data """<public-key>""" 

以下示例导入实体证书,并将证书称为 tls-cert-1。证书受密码 mypassphrase 保护。

证书捆绑包必须为 .PFX 或 .P12 格式。

cumulus@switch:~$ nv action import system security certificate tls-cert-1 passphrase mypassphrase uri-bundle scp://user@pass:1.2.3.4:/opt/certs/cert.p12 

以下示例导入带有公钥 URI scp://user@pass:1.2.3.4 和私钥 URI scp://user@pass:1.2.3.4 的实体证书,并将证书称为 tls-cert-1。证书不受密码保护。

CA 证书必须为 .pem、.p7a 或 .p7c 格式。

cumulus@switch:~$ nv action import system security certificate tls-cert-1 uri-public-key scp://user@pass:1.2.3.4 uri-private-key scp://user@pass:1.2.3.4

设置要使用的证书

您可以配置 NVUE REST API 以使用特定证书。

以下示例配置 API 以使用名为 tls-cert-1 的证书或 CA 捆绑包

cumulus@switch:~$ nv set system api certificate tls-cert-1
cumulus@switch:~$ nv config apply

以下示例配置 API 以使用自签名证书

cumulus@switch:~$ nv set system api certificate self-signed
cumulus@switch:~$ nv config apply

要取消设置要与 NVUE REST API 一起使用的证书

cumulus@switch:~$ nv unset system api certificate tls-cert-1

要配置用于 mTLS 的相互身份验证的证书

cumulus@switch:~$ nv set system api mtls ca-certificate tls-cert-1

删除证书

  • 要删除实体证书和交换机上存储的密钥数据,请运行 nv action delete system security certificate <cert-id> 命令。
  • 要删除 CA 证书和交换机上存储的密钥数据,请运行 nv action delete system security ca-certificate <cert-id> 命令。

以下命令删除证书 tls-cert-1

cumulus@switch:~$ nv action delete system security certificate tls-cert-1 

显示证书信息

  • 要显示交换机上的所有实体证书,请运行 nv show system security certificate 命令。
  • 要显示交换机上的所有 CA 证书,请运行 nv show system security ca-certificate 命令。

以下示例显示交换机上的所有实体证书

cumulus@switch:~$ nv show system security certificate
  • 要显示正在使用特定实体证书的应用程序,请运行 nv show system security certificate <cert-id> installed 命令。
  • 要显示正在使用特定 CA 证书的应用程序,请运行 nv show system security ca-certificate <cert-id> installed 命令。

以下示例显示正在使用特定实体证书的应用程序。

cumulus@switch:~$ nv show system security certificate tls-cert-1 installed
  • 要显示有关特定实体证书的详细信息,请运行 nv show system security certificate <cert-id> dump 命令。
  • 要显示有关特定 CA 证书的详细信息,请运行 nv show system security ca-certificate <cert-id> dump 命令。

以下示例显示有关 CA 证书 tls-cert-1 的详细信息

cumulus@switch:~$ nv show system security ca-certificate tls-cert-1 dump

仅 API 用户

要创建没有 SSH 权限的仅 API 用户,请使用 Linux 组权限。您可以在 ZTP 脚本中创建仅 API 用户。

# Create the dedicated automation user 
adduser --disabled-password --gecos "Automation User,,,," --shell /usr/bin/nologin automation

# Set the password
echo 'automation:password!' | chpasswd

# Add the user to nvapply group to make NVUE config changes
adduser automation nvapply

控制平面 ACL

您可以通过配置以下内容来保护 API 安全

此示例显示了如何创建 ACL 以允许来自管理子网和本地交换机的用户使用 REST API 与交换机通信,并限制所有其他访问。

cumulus@switch:~$ nv set acl API-PROTECT type ipv4 
cumulus@switch:~$ nv set acl API-PROTECT rule 10 action permit
cumulus@switch:~$ nv set acl API-PROTECT rule 10 match ip .protocol tcp .dest-port 8765 .source-ip 192.168.200.0/24
cumulus@switch:~$ nv set acl API-PROTECT rule 10 remark "Allow the Management Subnet to talk to API"

cumulus@switch:~$ nv set acl API-PROTECT rule 20 action permit
cumulus@switch:~$ nv set acl API-PROTECT rule 20 match ip .protocol tcp .dest-port 8765 .source-ip 127.0.0.1
cumulus@switch:~$ nv set acl API-PROTECT rule 20 remark "Allow the local switch to talk to the API"

cumulus@switch:~$ nv set acl API-PROTECT rule 30 action deny
cumulus@switch:~$ nv set acl API-PROTECT rule 30 match ip .protocol tcp .dest-port 8765
cumulus@switch:~$ nv set acl API-PROTECT rule 30 remark "Block everyone else from talking to the API"

cumulus@switch:~$ nv set system control-plane acl API-PROTECT inbound

支持的对象

NVUE 对象模型支持 Cumulus Linux 交换机上的大多数功能。以下列表显示了支持的对象。NVUE API 在这些对象中的每个对象内支持更多对象。要查看受支持的 API 端点的完整列表,请参阅 Cumulus Linux 的 NVUE OpenAPI 规范。

高级对象描述
acl访问控制列表。
bridge桥域配置。
evpnEVPN 配置。
interface接口配置。
mlagMLAG 配置。
nve网络虚拟化配置,例如 VXLAN 特定 MLAG 配置和 VXLAN 洪泛。
platform平台配置,例如硬件和软件组件。
qosQoS RoCE 配置。
router路由器配置,例如路由器策略、全局 BGP 和 OSPF 配置、PBR、PIM、IGMP、VRR 和 VRRP 配置。
serviceDHCP 中继和服务器、NTP、PTP、LLDP 和 syslog 配置。
system全局系统设置,例如 PBR 的保留路由表范围和第 3 层 VNI 的保留 VLAN 范围、系统登录消息和交换机重启历史记录。
vrfVRF 配置。

使用 API

NVUE CLI 和 REST API 在功能上是等效的;您可以从 REST API 或 CLI 运行所有管理操作。NVUE 对象模型驱动 REST API 和 CLI 管理操作。所有操作都是一致的;例如,CLI nv show commands 反映您通过 REST API 运行的任何 PATCH 操作(创建和更新)。

NVUE 遵循声明式模型,删除上下文特定的命令和设置。NVUE 的结构就像一棵大树,代表 Cumulus Linux 实例的整个状态。树的底部是代表对象的高级分支,例如路由器和接口。在每个分支下都有更多分支。当您浏览树时,您会获得更具体的上下文。在树的叶子处是实际属性,表示为键值对。通过树的路径类似于文件系统路径。

默认情况下,Cumulus Linux 启用 NVUE REST API。要禁用 NVUE REST API,请运行 nv set system api state disabled 命令。

要在 Cumulus Linux 5.6 及更高版本中使用 NVUE REST API,您必须更改 cumulus 用户的密码;否则,当您运行命令时,您会看到 403 响应。

API 端口和侦听地址

本节介绍如何

  • 设置 NVUE REST API 端口。如果您未设置端口,则 Cumulus Linux 使用默认端口 8765。
  • 指定 NVUE REST API 侦听地址;您可以指定 IPv4 地址、IPv6 地址或 localhost。如果您未指定侦听地址,则 NGINX 将侦听目标端口的所有地址。

以下示例将端口设置为 8888

cumulus@switch:~$ nv set system api port 8888
cumulus@switch:~$ nv config apply

您可以通过指定不同的侦听地址来侦听多个接口

cumulus@switch:~$ nv set system api listening-address 10.10.10.1
cumulus@switch:~$ nv set system api listening-address 10.10.20.1
cumulus@switch:~$ nv config apply

以下示例配置 eth0 上的侦听地址,eth0 具有 IP 地址 172.0.24.0,并且默认使用管理 VRF

cumulus@switch:~$ nv set system api listening-address 172.0.24.0
cumulus@switch:~$ nv config apply

以下示例在 swp1 上配置 VRF BLUE,swp1 具有 IP 地址 10.10.20.1,然后将 API 侦听地址设置为 swp1 的 IP 地址(为 VRF BLUE 配置)。

cumulus@switch:~$ nv set interface swp1 ip address 10.10.10.1/24
cumulus@switch:~$ nv set interface swp1 ip vrf BLUE
cumulus@switch:~$ nv config apply

cumulus@switch:~$ nv set system api listening-address 10.10.10.1
cumulus@switch:~$ nv config apply

以下示例将端口设置为 8888

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request PATCH https://127.0.0.1:8765/nvue_v1/system/api?rev=2 -H 'Content-Type:application/json' -d '{"port": 8888 }'

您可以通过指定不同的侦听地址来侦听多个接口。以下示例将 localhost、接口地址 10.10.10.1 和 10.10.20.1 设置为 listen-addresses。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request PATCH https://127.0.0.1:8765/nvue_v1/system/api/listening-address?rev=2 -H 'Content-Type:application/json' -d '{ "localhost": {}, "10.10.10.1": {}, "10.10.20.1": {}}'

以下示例配置 eth0 上的侦听地址,eth0 具有 IP 地址 172.0.24.0,并且默认使用管理 VRF

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request PATCH https://127.0.0.1:8765/nvue_v1/system/api/listening-address?rev=2 -H 'Content-Type:application/json' -d '{"172.0.24.0": {}}'

显示 NVUE REST API 信息

要显示 REST API 端口配置、状态(已启用或已禁用)、证书、侦听地址和连接信息

运行 nv show system api 命令

cumulus@switch:~$ nv show system api
                  operational     applied
--------------    -----------     -------
port                 8888         8888     
state                enabled      enabled
certificate          self-signed  self-signed  
[listening-address]  localhost    localhost
connections
  accepted        31
  active          1
  handled         33
  reading         0
  requests        28
  waiting         0
  writing         1

要仅显示连接信息,请运行 nv show system api connections 命令

cumulus@switch:~$ nv show system api connections
          operational  applied
--------  -----------  -------
accepted  31                  
active    1                   
handled   33                  
reading   0                   
requests  28                   
waiting   0                   
writing   1     

要显示配置的侦听地址,请运行 nv show system api listening-address 命令

cumulus@switch:~$ nv show system api listening-address
---------
localhost

要显示交换机上安装的所有证书,请运行 nv show system security certificate 命令。要显示有关特定证书的信息(例如序列号以及证书的有效期),请运行 nv show system security certificate <certificate> 命令

cumulus@switch:~$ nv show system security certificate tls-cert-1 
               operational                applied  pending 
-------------  -------------------------  -------  ------- 
installed      
 app           TLS 
serial-number  67:03:3B:B4:6E:35:D3 
valid-from     2023-02-14T00:35:18+00:00 
valid-to       2033-02-11T00:35:18+00:00 
cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request GET https://127.0.0.1:8765/nvue_v1/system/api?rev=2 -H "accept: application/json"
{
  "certificate": "self-signed",
  "listening-address": {
    "10.10.10.1": {},
    "10.10.20.1": {},
    "172.0.24.0": {},
    "localhost": {}
  },
  "port": 8888,
  "state": "enabled"
}

要显示配置的侦听地址

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request GET https://127.0.0.1:8765/nvue_v1/system/api/listening-address?rev=2 -H "accept: application/json"
{
  "10.10.10.1": {},
  "10.10.20.1": {}
}

要显示交换机上的证书

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request GET https://127.0.0.1:8765/nvue_v1/system/api/certificate?rev=2 -H "accept: application/json"
{
  "tls-cert-1": {},
  "tls-cert-2": {}
}

要显示有关特定证书的信息(例如序列号以及证书的有效期)

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k --request GET https://127.0.0.1:8765/nvue_v1/system/api/certificate/tls-cert-1?rev=2 -H "accept: application/json"
{
  "serial-number": "67:03:3B:B4:6E:35:D3",
  "valid-from": "2023-02-14T00:35:18+00:00",
  "valid-to": "2033-02-11T00:35:18+00:00"
}

运行 cURL 命令

您可以从命令行运行 cURL 命令。使用交换机的用户名和密码。例如

cumulus@switch:~$ curl  -u 'cumulus:cumulus' --insecure https://127.0.0.1:8765/nvue_v1/interface
{
  "eth0": {
    "ip": {
      "address": {
        "192.168.200.12/24": {}
      }
    },
    "link": {
      "mtu": 1500,
      "state": {
        "up": {}
      },
      "stats": {
        "carrier-transitions": 2,
        "in-bytes": 184151,
        "in-drops": 0,
        "in-errors": 0,
        "in-pkts": 2371,
        "out-bytes": 117506,
        "out-drops": 0,
        "out-errors": 0,
        "out-pkts": 762
      }
...

API 用例

以下示例显示了主要的 API 用例。

查看配置

使用以下示例获取交换机上当前应用的配置。更改 rev 参数以查看任何修订版本。rev 参数的可能选项包括 startuppendingoperationalapplied

cumulus@switch:~$ curl -k -u cumulus:cumulus -X GET "https://127.0.0.1:8765/nvue_v1/?rev=applied&filled=false"
"acl": {}, 
  "bridge": { 
    "domain": { 
      "br_default": { 
        "encap": "802.1Q", 
        "mac-address": "auto", 
        "multicast": { 
          "snooping": { 
            "enable": "off" 
          } 
        }, 
        "stp": { 
          "priority": 32768, 
          "state": { 
            "up": {} 
          } 
        }, 
        "type": "vlan-aware", 
        "untagged": 1, 
        "vlan": { 
          "10": { 
            "multicast": { 
...  
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

if __name__ == "__main__":
    r = requests.get(url=nvue_end_point + "/?rev=applied&filled=false",
                     auth=auth,
                     verify=False)
    print("=======Current Applied Revision=======")
    print(json.dumps(r.json(), indent=2))
cumulus@switch:~$ nv config show
- set: 
    bridge: 
      domain: 
        br_default: 
          type: vlan-aware 
          vlan: 
            '10': 
              vni: 
                '10': {} 
            '20': 
              vni: 
                '20': {} 
            '30': 
              vni: 
                '30': {} 
    evpn: 
      enable: on 
    mlag: 
      backup: 
        10.10.10.2: {} 
      enable: on 
      init-delay: 10 
      mac-address: 44:38:39:BE:EF:AA 
... 

替换整个配置

要替换整个配置

  1. 使用 POST 创建新的修订版本 ID

    cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure -X POST https://127.0.0.1:8765/nvue_v1/revision
    {
     "1": {
       "state": "pending",
       "transition": {
         "issue": {},
         "progress": ""
       }
     }
    }
    
  2. 记录修订版本 ID。在上面的示例中,修订版本 ID 为 "1"

  3. 执行根补丁以删除整个配置。

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{}' -H 'Content-Type: application/json' -k -X DELETE https://127.0.0.1:8765/nvue_v1/?rev=1
    {}
    
  4. 执行根补丁以使用新配置更新交换机。

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{
       "system": {
         "hostname": "switch01"
       },
       "bridge": {
         "domain": {
           "br_default": {
             "type": "vlan-aware",
             "vlan": {
               "10": {
                 "vni": {
                   "10": {}
                   }
                 },
               "20": {
                 "vni": {
                   "20": {}
                 }
               },
               "30": {
                 "vni": {
                   "30": {}
                 }
               }
             }
           }
         }
       },
       "interface": {
         "eth0": {
           "ip": {
             "address": {
               "192.168.200.6/24": {}
             },
             "vrf": "mgmt"
           },
           "type": "eth"
         },
         "lo": {
           "ip": {
             "address": {
               "10.10.10.1/32": {}
             }
           },
           "type": "loopback"
         },
         "swp51": {
           "link": {
             "state": {
               "up": {}
             }
           },
           "type": "swp"
         },
         "swp52": {
           "link": {
             "state": {
               "up": {}
             }
           },
           "type": "swp"
         },
         "swp53": {
           "link": {
             "state": {
               "up": {}
             }
           },
           "type": "swp"
         },
         "swp54": {
           "link": {
             "state": {
               "up": {}
             }
           },
           "type": "swp"
         }
       },
       "mlag": {
         "backup": {
           "10.10.10.2": {}
         },
         "enable": "on",
         "init-delay": 10,
         "mac-address": "44:38:39:BE:EF:AA",
         "peer-ip": "linklocal",
         "priority": 1000
       }
       "router": {
         "bgp": {
           "enable": "on"
         },
         "vrr": {
           "enable": "on"
         }
       },
       "service": {},
       "vrf": {
         "mgmt": {
           "router": {
             "static": {
               "0.0.0.0/0": {
                 "address-family": "ipv4-unicast",
                 "via": {
                   "192.168.200.1": {
                     "type": "ipv4-address"
                   }
                 }
               }
             }
           }
         }
       }
     }' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/?rev=1
    {}
    
  5. 使用 PATCH 将更改应用于修订版本变更集。

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -H 'Content-Type:application/json' -d '{"state": "apply", "auto-prompt": {"ays": "ays_yes"}}' -k -X PATCH https://127.0.0.1:8765/nvue_v1/revision/1
    {
      "state": "apply",
      "transition": {
        "issue": {},
        "progress": ""
      }
    }
    
    cumulus@switch:~$ nv config apply
    
  6. 查看应用状态和配置

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -X GET https://127.0.0.1:8765/nvue_v1/revision/1
    {
      "state": "applied",
      "transition": {
        "issue": {},
        "progress": ""
      }
    }
    
    cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure https://127.0.0.1:8765/nvue_v1/system
    {
     "build": "Cumulus Linux 5.4.0",
     "hostname": "switch01",
     "timezone": "Etc/UTC",
     "uptime": 763
    }
    cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure https://127.0.0.1:8765/nvue_v1/bridge/domain/br_default/vlan/10
    {
     "multicast": {
       "snooping": {
         "querier": {
           "source-ip": "0.0.0.0"
         }
       }
     },
     "ptp": {
       "enable": "off"
     },
     "vni": {
       "10": {
         "flooding": {
           "enable": "auto"
         },
         "mac-learning": "off"
       }
     }
    
    #!/usr/bin/env python3
    
    import requests
    from requests.auth import HTTPBasicAuth
    import json
    import time
    
    auth = HTTPBasicAuth(username="cumulus", password="password")
    nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
    mime_header = {"Content-Type": "application/json"}
    
    DUMMY_SLEEP = 5  # In seconds
    POLL_APPLIED = 1  # in seconds
    RETRIES = 10
    
    def print_request(r: requests.Request):
        print("=======Request=======")
        print("URL:", r.url)
        print("Headers:", r.headers)
        print("Body:", r.body)
    
    def print_response(r: requests.Response):
        print("=======Response=======")
        print("Headers:", r.headers)
        print("Body:", json.dumps(r.json(), indent=2))
    
    def create_nvue_changest():
        r = requests.post(url=nvue_end_point + "/revision",
                          auth=auth,
                          verify=False)
        print_request(r.request)
        print_response(r)
        response = r.json()
        changeset = response.popitem()[0]
        return changeset
    
    def apply_nvue_changeset(changeset):
        apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
        url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                                   safe="")
        r = requests.patch(url=url,
                           auth=auth,
                           verify=False,
                           data=json.dumps(apply_payload),
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
    def is_config_applied(changeset) -> bool:
        # Check if the configuration was indeed applied
        global RETRIES
        global POLL_APPLIED
        retries = RETRIES
        while retries > 0:
            r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                             auth=auth,
                             verify=False)
            response = r.json()
            print(response)
            if response["state"] == "applied":
                return True
            retries -= 1
            time.sleep(POLL_APPLIED)
    
        return False
    
    def apply_new_config(path,payload):
        # Create a new revision ID
        changeset = create_nvue_changest()
        print("Using NVUE Changeset: '{}'".format(changeset))
    
        # Delete existing configuration
        query_string = {"rev": changeset}
        r = requests.delete(url=nvue_end_point + path,
                           auth=auth,
                           verify=False,
                           params=query_string,
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
        # Patch the new configuration
        
        query_string = {"rev": changeset}
        r = requests.patch(url=nvue_end_point + path,
                           auth=auth,
                           verify=False,
                           data=json.dumps(payload),
                           params=query_string,
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
        # Apply the changes to the new revision changeset
        apply_nvue_changeset(changeset)
    
        # Check if the changeset was applied
        is_config_applied(changeset)
    
    def nvue_get(path):
        r = requests.get(url=nvue_end_point + path,
                         auth=auth,
                         verify=False)
        print_request(r.request)
        print_response(r)
    
    if __name__ == "__main__":
        payload = {
          "system": {
            "hostname": "switch01"
          },
          "bridge": {
            "domain": {
              "br_default": {
                "type": "vlan-aware",
                "vlan": {
                  "10": {
                    "vni": {
                      "10": {}
                      }
                    },
                  "20": {
                    "vni": {
                      "20": {}
                    }
                  },
                  "30": {
                    "vni": {
                      "30": {}
                    }
                  }
                }
              }
            }
          },
          "interface": {
            "eth0": {
              "ip": {
                "address": {
                  "192.168.200.6/24": {}
                },
                "vrf": "mgmt"
              },
              "type": "eth"
            },
            "lo": {
              "ip": {
                "address": {
                  "10.10.10.1/32": {}
                }
              },
              "type": "loopback"
            },
            "swp51": {
              "link": {
                "state": {
                  "up": {}
                }
              },
              "type": "swp"
            },
            "swp52": {
              "link": {
                "state": {
                  "up": {}
                }
              },
              "type": "swp"
            },
            "swp53": {
              "link": {
                "state": {
                  "up": {}
                }
              },
              "type": "swp"
            },
            "swp54": {
              "link": {
                "state": {
                  "up": {}
                }
              },
              "type": "swp"
            }
          },
          "mlag": {
            "backup": {
              "10.10.10.2": {}
            },
            "enable": "on",
            "init-delay": 10,
            "mac-address": "44:38:39:BE:EF:AA",
            "peer-ip": "linklocal",
            "priority": 1000
          }
          "router": {
            "bgp": {
              "enable": "on"
            },
            "vrr": {
              "enable": "on"
            }
          },
          "service": {},
          "vrf": {
            "mgmt": {
              "router": {
                "static": {
                  "0.0.0.0/0": {
                    "address-family": "ipv4-unicast",
                    "via": {
                      "192.168.200.1": {
                        "type": "ipv4-address"
                      }
                    }
                  }
                }
              }
            }
          }
        }
        apply_new_config("/",payload)
        time.sleep(DUMMY_SLEEP)
        print("=====Verifying some of the configurations=====")
        nvue_get("/system")
        nvue_get("/bridge/domain/br_default/vlan/10")
    
    cumulus@switch:~$ nv show system
                operational          applied
    --------  -------------------  -------
    hostname  switch01             cumulus
    build     Cumulus Linux 5.4.0
    uptime    0:12:59
    timezone  Etc/UTC
    
    cumulus@switch:~$ nv show bridge domain br_default vlan 10
    
                     operational  applied  pending  description
    ---------------  -----------  -------  -------  ------------------------------------------------------
    [vni]            10           10       10       L2 VNI
    multicast
      snooping
        querier
          source-ip  0.0.0.0      0.0.0.0  0.0.0.0  Source IP to use when sending IGMP/MLD queries.
    ptp
      enable         off          off      off      Turn the feature 'on' or 'off'.  The default is 'off'.
    

进行配置更改

要进行配置更改

  1. 使用 POST 创建新的修订版本 ID

    cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure -X POST https://127.0.0.1:8765/nvue_v1/revision
    {
       "2": {
       "state": "pending",
       "transition": {
         "issue": {},
         "progress": ""
       }
     }
    }
    
  2. 记录修订版本 ID。在上面的示例中,修订版本 ID 为 "2"

  3. 使用 PATCH 进行更改,并将其链接到修订版本 ID

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"99.99.99.99/32": {}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/interface/lo/ip/address?rev=2
    {
      "99.99.99.99/32": {}
    }
    
    cumulus@switch:~$ nv set interface lo ip address 99.99.99.99/32
    
  4. 使用 PATCH 将更改应用于修订版本变更集

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -H 'Content-Type:application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/revision/2
    {
      "state": "apply",
      "transition": {
        "issue": {},
        "progress": ""
      }
    }
    
    cumulus@switch:~$ nv config apply
    
  5. 查看应用状态和配置

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -X GET https://127.0.0.1:8765/nvue_v1/revision/2
    {
      "state": "applied",
      "transition": {
        "issue": {},
        "progress": ""
      }
    }
    
    cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure https://127.0.0.1:8765/nvue_v1/interface/lo/ip/address
    {
      "127.0.0.1/8": {},
      "99.99.99.99/32": {},
      "::1/128": {}
    }
    
    #!/usr/bin/env python3
    
    import requests
    from requests.auth import HTTPBasicAuth
    import json
    import time
    
    auth = HTTPBasicAuth(username="cumulus", password="password")
    nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
    mime_header = {"Content-Type": "application/json"}
    
    DUMMY_SLEEP = 5  # In seconds
    POLL_APPLIED = 1  # in seconds
    RETRIES = 10
    
    def print_request(r: requests.Request):
        print("=======Request=======")
        print("URL:", r.url)
        print("Headers:", r.headers)
        print("Body:", r.body)
    
    def print_response(r: requests.Response):
        print("=======Response=======")
        print("Headers:", r.headers)
        print("Body:", json.dumps(r.json(), indent=2))
    
    def create_nvue_changest():
        r = requests.post(url=nvue_end_point + "/revision",
                          auth=auth,
                          verify=False)
        print_request(r.request)
        print_response(r)
        response = r.json()
        changeset = response.popitem()[0]
        return changeset
    
    def apply_nvue_changeset(changeset):
        apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
        url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                                   safe="")
        r = requests.patch(url=url,
                           auth=auth,
                           verify=False,
                           data=json.dumps(apply_payload),
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
    def is_config_applied(changeset) -> bool:
        # Check if the configuration was indeed applied
        global RETRIES
        global POLL_APPLIED
        retries = RETRIES
        while retries > 0:
            r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                             auth=auth,
                             verify=False)
            response = r.json()
            print(response)
            if response["state"] == "applied":
                return True
            retries -= 1
            time.sleep(POLL_APPLIED)
    
        return False
    
    def apply_new_config(path,payload):
        # Create a new revision ID
        changeset = create_nvue_changest()
        print("Using NVUE Changeset: '{}'".format(changeset))
    
        # Delete existing configuration
        query_string = {"rev": changeset}
        r = requests.delete(url=nvue_end_point + path,
                           auth=auth,
                           verify=False,
                           params=query_string,
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
        # Patch the new configuration
        
        query_string = {"rev": changeset}
        r = requests.patch(url=nvue_end_point + path,
                           auth=auth,
                           verify=False,
                           data=json.dumps(payload),
                           params=query_string,
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
        # Apply the changes to the new revision changeset
        apply_nvue_changeset(changeset)
    
        # Check if the changeset was applied
        is_config_applied(changeset)
    
    def nvue_get(path):
        r = requests.get(url=nvue_end_point + path,
                         auth=auth,
                         verify=False)
        print_request(r.request)
        print_response(r)
    
    if __name__ == "__main__":
        payload = {
            "99.99.99.99/32": {}
        }
        apply_new_config("/interface/lo/ip/address",payload)
        time.sleep(DUMMY_SLEEP)
        nvue_get("/interface/lo/ip/address")
    
    cumulus@switch:~$ nv show interface lo ip address
       
    -------------
    99.99.99.99/32
    127.0.0.1/8
    ::1/128
    

查看配置之间的差异

要查看配置之间的差异,请使用 API GET /nvue_v1/<resource>?rev=<rev-A>&diff=<rev-B> 方法以及您要 diff 的配置。此方法等效于 NVUE nv config diff <rev-A> <rev-B> 命令。

要查看启动修订版本和应用修订版本之间的差异

cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure -X GET /nvue_v1/interface?rev=startup&diff=applied

要查看修订版本 1 和修订版本 2 之间的差异

cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure -X GET /nvue_v1/<resource>?rev=1&diff=2

您可以更改修订版本的顺序;例如,GET /nvue_v1/<resource>?rev=2&diff=1

配置更改故障排除

当配置更改失败时,您会在更改请求中看到错误。

由于依赖关系而导致配置失败

如果您暂存配置,但由于依赖关系而失败,则失败会显示原因。在以下示例中,更改失败,因为未设置 BGP 路由器 ID。

cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure https://127.0.0.1:8765/nvue_v1/revision/6
{
  "state": "invalid",
  "transition": {
    "issue": {
      "0": {
        "code": "config_invalid",
        "data": {
          "location": "router.bgp.enable",
          "reason": "BGP requires router-id to be set globally or in the VRF.\n"
        },
        "message": "Config invalid at router.bgp.enable: BGP requires router-id to be set globally or in the VRF.\n",
        "severity": "error"
      }
    },
    "progress": "Invalid config"
  }
}

暂存的配置缺少 router-id

cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure https://127.0.0.1:8765/nvue_v1/vrf/default/router/bgp?rev=6
{
  "autonomous-system": 65999,
  "enable": "on"
}

配置应用失败并显示警告

在某些情况下,例如首次使用 NVUE 推送或手动更改文件而不是使用 NVUE 时,您会看到警告提示,并且应用失败。

cumulus@switch:~$ curl -u 'cumulus:cumulus' --insecure -X GET https://127.0.0.1:8765/nvue_v1/revision/6
{
  "6": {
    "state": "ays_fail",
    "transition": {
      "issue": {
        "0": {
          "code": "client_timeout",
          "data": {},
          "message": "Timeout while waiting for client response",
          "severity": "error"
        }
      },
      "progress": "Aborted apply after warnings"
    }
  }

要解决此问题,请观察失败或错误,然后检查您尝试应用的配置。解决错误后,重试 API。如果您希望忽略错误并强制应用,请将 "auto-prompt":{"ays": "ays_yes"} 添加到配置应用。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"state":"apply","auto-prompt":{"ays": "ays_yes"}}' -H 'Content-Type:application/json' --insecure -X PATCH https://127.0.0.1:8765/nvue_v1/revision/6

保存配置

要将应用的配置更改保存到启动配置文件 (/etc/nvue.d/startup.yaml),以便更改在重新启动后仍然存在,请对应用的修订版本使用 PATCH 并使用 save 状态。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -X PATCH -d '{"state": "save", "auto-prompt": {"ays": "ays_yes"}}' -H 'Content-Type: application/json'  https://127.0.0.1:8765/nvue_v1/revision/applied 
{ 
  "state": "save",
  "transition": {
    "issue": {},
    "progress": ""
  }
}
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def save_nvue_changeset():
    apply_payload = {"state": "save", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/applied"
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    save_nvue_changeset()
cumulus@switch:~$ nv config save
saved

取消设置配置更改

要取消设置配置更改,请对键使用 null 值。例如,要从交换机中删除 vlan100,请使用以下语法

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"vlan100":null}' -H 'Content-Type: application/json' --insecure -X PATCH https://127.0.0.1:8765/nvue_v1/interface/rev=4

当您取消设置更改时,您仍然必须使用 PATCH 操作。该值指示删除条目。数据为 {"vlan100":null} 和 PATCH 操作。

使用 API 进行主动监控

以下示例获取 interface swp1 的计数器。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -X GET https://127.0.0.1:8765/nvue_v1/interface/swp1/link/stats
{
  "carrier-transitions": 6,
  "in-bytes": 293771538,
  "in-drops": 0,
  "in-errors": 0,
  "in-pkts": 2321737,
  "out-bytes": 366068936,
  "out-drops": 0,
  "out-errors": 0,
  "out-pkts": 3536629
}
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

if __name__ == "__main__":
    r = requests.get(url=nvue_end_point + "/interface/swp1/link/stats",
                     auth=auth,
                     verify=False)
    print("=======Interface swp1 Statistics=======")
    print(json.dumps(r.json(), indent=2))
cumulus@switch:~$ nv show interface swp1 link stats
                     operational  applied  pending  description
-------------------  -----------  -------  -------  ----------------------------------------------------------------------
carrier-transitions  6                              Number of times the interface state has transitioned between up and...
in-bytes             280.15 MB                      total number of bytes received on the interface
in-drops             0                              number of received packets dropped
in-errors            0                              number of received packets with errors
in-pkts              2321659                        total number of packets received on the interface
out-bytes            349.10 MB                      total number of bytes transmitted out of the interface
out-drops            0                              The number of outbound packets that were chosen to be discarded eve...
out-errors           0                              The number of outbound packets that could not be transmitted becaus...
out-pkts             3536508                        total number of packets transmitted out of the interface

检索视图类型

NVUE 为某些 show 命令提供视图。视图是信息的子集。

要查看 show 命令可用的视图,请使用 --view 运行命令并按 TAB 键

cumulus@switch:~$ nv show interface --view <<TAB>>
acl-statistics  description     lldp            physical        status          
bond-members    detail          lldp-detail     pluggables      svi             
bonds           dot1x-counters  mac             port-security   synce-counters  
brief           dot1x-summary   mlag-cc         qos-profile     up              
counters        down            neighbor        small           vrf
cumulus@switch:~$ nv show vrf default router rib ipv4 route --view <<TAB>>
brief   detail

要通过 REST API 检索视图类型,您可以使用 curl -u 'cumulus:CumulusLinux!' -k -X GET http://path?view=<brief> 语法。例如,NVUE nv show vrf <vrf-id> router rib ipv4 route --view=brief 命令的等效 REST API 方法是

cumulus@switch:~$ curl -u 'cumulus:CumulusLinux!' -k -X GET https://127.0.0.1:8765/nvue_v1/vrf/BLUE/router/rib/ipv4/route?view=brief

NVUE nv show interface --view=acl-statistics 命令的等效 REST API 方法是

cumulus@switch:~$ curl -u 'cumulus:CumulusLinux!' -k -X GET https://127.0.0.1:8765/nvue_v1/interface?view=acl-statistics

对于查询不存在的视图。API 返回 400 Bad Request 错误,并显示该端点的所有已定义视图。

您还可以在查询中提供 includeomit 参数,以在响应中包含或省略某些属性。

您不能在同一 API 方法中同时使用 view=includeomit 参数。例如,以下 REST API 方法返回错误

cumulus@switch:~$ curl -u 'cumulus:CumulusLinux!' --insecure -X GET "https://127.0.0.1:8765/nvue_v1/vrf/default/router/rib/ipv4/route?include=/*/route-entry/*/protocol,/*/route-entry/*/nexthop-group-id,/*/route-entry/*/uptime&view=brief"

以下示例返回路由表中的所有路由,并包含所有属性(API 请求不包含 include 或 omit 选项)

cumulus@switch:~$ curl -u 'cumulus:CumulusLinux!' --insecure -X GET https://127.0.0.1:8765/nvue_v1/vrf/default/router/rib/ipv4/route
{
  "10.0.1.12/32": {
    "route-entry": {
      "1": {
        "distance": 0,
        "flags": {
          "fib-selected": {},
          "installed": {},
          "offloaded": {},
          "selected": {}
        },
        "flags-string": "*Sio",
        "metric": 0,
        "nexthop-group-id": 12,
        "protocol": "connected",
        "uptime": "2024-10-18T22:27:35Z"
      }
    }
  },
  "10.0.1.34/32": {
    "route-entry": {
      "1": {
        "distance": 20,
        "flags": {
          "fib-selected": {},
          "installed": {},
          "selected": {}
        },
        "flags-string": "*Si",
        "metric": 0,
        "nexthop-group-id": 45,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.0.1.255/32": {
    "route-entry": {
      "1": {
        "distance": 20,
        "flags": {
          "fib-selected": {},
          "installed": {},
          "selected": {}
        },
        "flags-string": "*Si",
        "metric": 0,
        "nexthop-group-id": 38,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.10.10.1/32": {
    "route-entry": {
      "1": {
        "distance": 20,
        "flags": {
          "fib-selected": {},
          "installed": {},
          "selected": {}
        },
        "flags-string": "*Si",
        "metric": 0,
        "nexthop-group-id": 45,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.10.10.2/32": {
    "route-entry": {
      "1": {
        "distance": 20,
        "flags": {
          "fib-selected": {},
          "installed": {},
          "selected": {}
        },
        "flags-string": "*Si",
        "metric": 0,
        "nexthop-group-id": 38,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.10.10.3/32": {
    "route-entry": {
      "1": {
        "distance": 20,
        "flags": {
          "fib-selected": {},
          "installed": {},
          "selected": {}
        },
        "flags-string": "*Si",
        "metric": 0,
        "nexthop-group-id": 34,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:38Z"
    }
  }
}

以下示例返回路由表中的所有路由,但仅包含协议、正常运行时间和 nexthop-group-id 属性

cumulus@switch:~$ curl -u 'cumulus:CumulusLinux!' --insecure -X GET "https://127.0.0.1:8765/nvue_v1/vrf/default/router/rib/ipv4/route?include=/*/route-entry/*/protocol,/*/route-entry/*/nexthop-group-id,/*/route-entry/*/uptime"
{
  "10.0.1.12/32": {
    "route-entry": {
      "1": {
        "nexthop-group-id": 12,
        "protocol": "connected",
        "uptime": "2024-10-18T22:27:35Z"
      }
    }
  },
  "10.0.1.34/32": {
    "route-entry": {
      "1": {
        "nexthop-group-id": 45,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.0.1.255/32": {
    "route-entry": {
      "1": {
        "nexthop-group-id": 38,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.10.10.1/32": {
    "route-entry": {
      "1": {
        "nexthop-group-id": 45,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.10.10.2/32": {
    "route-entry": {
      "1": {
        "nexthop-group-id": 38,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:39Z"
      }
    }
  },
  "10.10.10.3/32": {
    "route-entry": {
      "1": {
        "nexthop-group-id": 34,
        "protocol": "bgp",
        "uptime": "2024-10-18T22:27:38Z"
      }
    }
  }
}

以下示例返回路由表中的所有路由,但省略所有其他属性(nexthop-group-id、协议、正常运行时间、距离、指标和标志)

cumulus@switch:~$ curl -u 'cumulus:CumulusLinux!' --insecure -X GET https://127.0.0.1:8765/nvue_v1/vrf/default/router/rib/ipv4/route?omit=/*/*
{
  "10.0.1.12/32": {},
  "10.0.1.34/32": {},
  "10.0.1.255/32": {},
  "10.10.10.1/32": {},
  "10.10.10.2/32": {},
  "10.10.10.3/32": {}
}

转换 CLI 更改以使用 API

您可以从 CLI 获取配置更改,并使用 API 配置同一组更改。

  1. 使用 NVUE CLI 在系统上进行配置更改。

    cumulus@switch:~$ nv set system hostname switch01
    cumulus@switch:~$ nv set interface lo ip address 99.99.99.99/32
    cumulus@switch:~$ nv set interface eth0 ip address 192.168.200.6/24
    cumulus@switch:~$ nv set interface bond0 bond member swp1-4
    
  2. 将更改视为 JSON blob。

    cumulus@switch:~$ nv config diff -o json
    [
      {
        "set": {
          "interface": {
            "bond0": {
              "bond": {
                "member": {
                  "swp1": {},
                  "swp2": {},
                  "swp3": {},
                  "swp4": {}
                }
              },
              "type": "bond"
            },
            "lo": {
              "ip": {
                "address": {
                  "99.99.99.99/32": {}
                }
              }
            }
          },
          "system": {
            "hostname": "switch01"
          }
        }
      }
    ]
    
  3. 将 JSON blob 钉在根补丁请求上作为有效负载。

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{
          "interface": {
            "bond0": {
              "bond": {
                "member": {
                  "swp1": {},
                  "swp2": {},
                  "swp3": {},
                  "swp4": {}
                }
              },
              "type": "bond"
            },
            "lo": {
              "ip": {
                "address": {
                  "99.99.99.99/32": {}
                }
              }
            }
          },
          "system": {
            "hostname": "switch01"
          }
        }' -k -X PATCH https://127.0.0.1:8765/nvue_v1/?rev=3
    
    {
      "bridge": {
        "domain": {
          "br_default": {
            "type": "vlan-aware",
            "vlan": {
              "10": {
                "vni": {
                  "10": {}
                }
              },
              "20": {
                "vni": {
                  "20": {}
                }
              },
              "30": {
                "vni": {
                  "30": {}
                }
              }
            }
          }
        }
      },
      "evpn": {
        "enable": "on"
      },
      "interface": {
        "bond1": {
          "bond": {
            "lacp-bypass": "on",
            "member": {
              "swp1": {}
            },
    ...
    
  4. 使用 PATCH 将更改应用于修订版本变更集。

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -H 'Content-Type:application/json' -k -d '{"state": "apply", "auto-prompt": {"ays": "ays_yes"}}' -X PATCH https://127.0.0.1:8765/nvue_v1/revision/3
    {
      "state": "apply",
      "transition": {
        "issue": {},
        "progress": ""
      }
    }
    
  5. 查看应用状态和配置

    cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -X GET https://127.0.0.1:8765/nvue_v1/revision/3
    {
      "state": "applied",
      "transition": {
        "issue": {},
        "progress": ""
      }
    }
    
    #!/usr/bin/env python3
    
    import requests
    from requests.auth import HTTPBasicAuth
    import json
    import time
    
    auth = HTTPBasicAuth(username="cumulus", password="password")
    nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
    mime_header = {"Content-Type": "application/json"}
    
    DUMMY_SLEEP = 5  # In seconds
    POLL_APPLIED = 1  # in seconds
    RETRIES = 10
    
    def print_request(r: requests.Request):
        print("=======Request=======")
        print("URL:", r.url)
        print("Headers:", r.headers)
        print("Body:", r.body)
    
    def print_response(r: requests.Response):
        print("=======Response=======")
        print("Headers:", r.headers)
        print("Body:", json.dumps(r.json(), indent=2))
    
    def create_nvue_changest():
        r = requests.post(url=nvue_end_point + "/revision",
                          auth=auth,
                          verify=False)
        print_request(r.request)
        print_response(r)
        response = r.json()
        changeset = response.popitem()[0]
        return changeset
    
    def apply_nvue_changeset(changeset):
        # apply_payload = {"state": "apply"}
        apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
        url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                                   safe="")
        r = requests.patch(url=url,
                           auth=auth,
                           verify=False,
                           data=json.dumps(apply_payload),
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
    def is_config_applied(changeset) -> bool:
        # Check if the configuration was indeed applied
        global RETRIES
        global POLL_APPLIED
        retries = RETRIES
        while retries > 0:
            r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                             auth=auth,
                             verify=False)
            response = r.json()
            print(response)
    
            if response["state"] == "applied":
                return True
            retries -= 1
            time.sleep(POLL_APPLIED)
    
        return False
    
    def apply_new_config(path,payload):
        # Create a new revision ID
        changeset = create_nvue_changest()
        print("Using NVUE Changeset: '{}'".format(changeset))
    
        # Delete existing configuration
        query_string = {"rev": changeset}
        r = requests.delete(url=nvue_end_point + path,
                           auth=auth,
                           verify=False,
                           params=query_string,
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
        # Patch the new configuration
        
        query_string = {"rev": changeset}
        r = requests.patch(url=nvue_end_point + path,
                           auth=auth,
                           verify=False,
                           data=json.dumps(payload),
                           params=query_string,
                           headers=mime_header)
        print_request(r.request)
        print_response(r)
    
        # Apply the changes to the new revision changeset
        apply_nvue_changeset(changeset)
    
        # Check if the changeset was applied
        is_config_applied(changeset)
    
    def nvue_get(path):
        r = requests.get(url=nvue_end_point + path,
                         auth=auth,
                         verify=False)
        print_request(r.request)
        print_response(r)
    
    if __name__ == "__main__":
        payload = {
          "interface": {
            "bond0": {
              "bond": {
                "member": {
                  "swp1": {},
                  "swp2": {},
                  "swp3": {},
                  "swp4": {}
                }
              },
              "type": "bond"
            },
            "lo": {
              "ip": {
                "address": {
                  "99.99.99.99/32": {}
                }
              }
            }
          },
          "system": {
            "hostname": "switch01"
          }
        }
        apply_new_config("/",payload)
        time.sleep(DUMMY_SLEEP)
        nvue_get("/interface/bond0")
        nvue_get("/interface/lo")
        nvue_get("/system")
    
    

API 示例

以下部分提供实用的 API 示例。

配置系统

要在交换机上设置系统主机名、登录前或登录后消息以及时区,请将目标 API 请求发送到 /nvue_v1/system

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"system": {"hostname":"switch01","timezone":"America/Los_Angeles","message":{"pre-login":"Welcome to NVIDIA Cumulus Linux","post-login":"You have successfully logged in to switch01"}}}' -k -X PATCH https://127.0.0.1:8765/nvue_v1/?rev=4
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    payload = {
      "system": 
      {
        "hostname":"switch01",
        "timezone":"America/Los_Angeles",
        "message":
        {
          "pre-login":"Welcome to NVIDIA Cumulus Linux",
          "post-login:"You have successfully logged in to switch01"
        }
      }
    }
    apply_new_config("/",payload) # Root patch
    time.sleep(DUMMY_SLEEP)
    nvue_get("/system")
cumulus@switch:~$ nv set system hostname switch01
cumulus@switch:~$ nv set system timezone America/Los_Angeles
cumulus@switch:~$ nv set system message pre-login "Welcome to NVIDIA Cumulus Linux"
cumulus@switch:~$ nv set system message post-login "You have successfully logged into switch01"

配置服务

要在交换机上设置 NTP、DNS 和 SNMP,请将目标 API 请求发送到 /nvue_v1/service

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"service": { "ntp": {"default":{"server":{"4.cumulusnetworks.pool.ntp.org":{"iburst":"on"}}}}, "dns": {"mgmt":{"server":{"192.168.1.100":{}}}}, "syslog": {"mgmt":{"server":{"192.168.1.120":{"port":8000}}}}}}' -k -X PATCH https://127.0.0.1:8765/nvue_v1/?rev=5
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    payload = {
      "service":
      {
        "ntp":
        {
          "default":
          {
            "server:
            {
              "4.cumulusnetworks.pool.ntp.org":
              {
                "iburst":"on"
              }
            }
          }
        },
        "dns":
        {
          "mgmt":
          {
            "server:
            {
              "192.168.1.100":{}
            }
          }
        },
        "syslog":
        {
          "mgmt":
          {
            "server:
            {
              "192.168.1.120":
              {
                "port":8000
              }
            }
          }
        }
      }
    }
    apply_new_config("/",payload) # Root patch
    time.sleep(DUMMY_SLEEP)
    nvue_get("/service/ntp")
    nvue_get("/service/dns")
    nvue_get("/service/syslog")
cumulus@switch:~$ nv set service ntp default server 4.cumulusnetworks.pool.ntp.org iburst on
cumulus@switch:~$ nv set service dns mgmt server 192.168.1.100 
cumulus@switch:~$ nv set service syslog mgmt server 192.168.1.120 port 8000

配置用户

以下示例创建一个新用户,然后删除该用户。

此示例创建一个名为 test1 的新用户。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"system": {"aaa": {"user": {"test1": {"hashed-password":"72b28582708d749c6c82f3b3f226041f1bd37090281641eaeba8d44bd915d0042d609a92759d9f6fb96475cb0601cf428cd22613df8a53a09461e0b426cf0a35","role": "nvue-monitor","enable": "on","full-name": "Test User"}}}}}' -k -X PATCH https://127.0.0.1:8765/nvue_v1/?rev=5

此示例删除 test1 用户。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -X DELETE https://127.0.0.1:8765/nvue_v1/system/aaa/user/test1?rev=6
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def delete_config(path):
    # Create an NVUE changeset
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Equivalent to JSON `null`
    payload = None

    # Stage the change
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                        auth=auth,
                        verify=False,
                        data=json.dumps(payload),
                        params=query_string,
                        headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the staged changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":

    # Need to create a hashed password - The supported password
    # hashes are documented here:
    # https://docs.nvda.net.cn/networking-ethernet-software/cumulus-linux-55/System-Configuration/Authentication-Authorization-and-Accounting/User-Accounts/#hashed-passwords  # noqa
    # Here in this example, we use SHA-512
    import crypt
    hashed_password = crypt.crypt("hello$world#2023", salt=crypt.METHOD_SHA512)
    payload = {
        "system": {
            "aaa": {
                "user": {
                    "test1": {
                        "hashed-password": hashed_password,
                        "role": "nvue-monitor",
                        "enable": "on",
                        "full-name": "Test User",
                    }
                }
            }
        }
    }
    apply_new_config("/",payload) # Root patch
    time.sleep(DUMMY_SLEEP)
    nvue_get("/system/user/aaa")

    """Delete an existing user account using the AAA API."""
    delete_config("/system/aaa/user/test1")
    time.sleep(DUMMY_SLEEP)
    nvue_get("/system/user/aaa")

此示例创建一个新用户 test1

cumulus@switch:~$ nv set system aaa user test1
cumulus@switch:~$ nv set system aaa user test1 full-name "Test User" 
cumulus@switch:~$ nv set system aaa user test1 password "abcd@test"
cumulus@switch:~$ nv set system aaa user test1 role nvue-monitor
cumulus@switch:~$ nv set system aaa user test1 enable on

此示例删除用户 test1

cumulus@switch:~$ nv unset system aaa user test1

配置接口

以下示例配置一个接口。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -k -d '{"swp1": {"link":{"state":{"up": {}}}}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/interface?rev=21
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    payload = {
      "swp1":
      {
        "type":"swp",
        "link":
        {
          "state":"up"
          }
        }
      }
    apply_new_config("/interface",payload)
    time.sleep(DUMMY_SLEEP)
    nvue_get("/interface/swp1")
cumulus@switch:~$ nv set interface swp1

配置 Bond

以下示例配置一个 bond。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"bond0": {"type":"bond","bond":{"member":{"swp1":{},"swp2":{},"swp3":{},"swp4":{}}}}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/interface?rev=7
{
  "bond0": {
    "bond": {
      "member": {
        "swp1": {},
        "swp2": {},
        "swp3": {},
        "swp4": {}
      }
    },
    "type": "bond"
  }
}
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    payload = {
      "bond0":
      {
        "type":"bond",
        "bond":
        {
          "member":
          {
            "swp1":{},
            "swp2":{},
            "swp3":{},
            "swp4":{}
          }
        }
      }
    }
    apply_new_config("/interface",payload)
    time.sleep(DUMMY_SLEEP)
    nvue_get("/interface/bond0")
cumulus@switch:~$ nv set interface bond0 bond member swp1-4

配置 Bridge

以下示例配置一个 bridge。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"swp1": {"bridge":{"domain":{"br_default":{}}}},"swp2": {"bridge":{"domain":{"br_default":{}}}}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/interface?rev=21
{
  "swp1": {
    "bridge": {
      "domain": {
        "br_default": {}
      }
    },
    "type": "swp"
  },
  "swp2": {
    "bridge": {
      "domain": {
        "br_default": {}
      }
    },
    "type": "swp"
  }
}

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"untagged":1,"vlan":{"10":{},"20":{}}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/bridge/domain/br_default?rev=8

{ “untagged”: 1, “vlan”: { “10”: {}, “20”: {} } }

#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    int_payload = {
      "swp1":
      {
        "bridge":
        {
          "domain":
          {
            "br_default":{}
          }
        },
        "swp2": 
        {
          "bridge":
          {
            "domain":
            {
              "br_default":{}
            }
          }
        }
      }
    }
    apply_new_config("/interface",int_payload)
    br_payload = {
      "untagged":1,
      "vlan":
      {
        "10":{},
        "20":{}
      }
    }
    apply_new_config("/bridge/domain/br_default",br_payload)
    time.sleep(DUMMY_SLEEP)
    nvue_get("/interface/swp1")
    nvue_get("/bridge/domain/br_default")
cumulus@switch:~$ nv set interface swp1-2 bridge domain br_default
cumulus@switch:~$ nv set bridge domain br_default vlan 10,20
cumulus@switch:~$ nv set bridge domain br_default untagged 1

配置 BGP

以下示例配置 BGP。

cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"bgp": {"autonomous-system": 65101,"router-id":"10.10.10.1"}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/router?rev=9
cumulus@switch:~$ curl -u 'cumulus:cumulus' -d '{"bgp":{"neighbor":{"swp51":{"remote-as":"external"}},"address-family":{"ipv4-unicast":{"network":{"10.10.10.1/32":{}}}}}}' -H 'Content-Type: application/json' -k -X PATCH https://127.0.0.1:8765/nvue_v1/vrf/default/router?rev=9
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def create_nvue_changest():
    r = requests.post(url=nvue_end_point + "/revision",
                      auth=auth,
                      verify=False)
    print_request(r.request)
    print_response(r)
    response = r.json()
    changeset = response.popitem()[0]
    return changeset

def apply_nvue_changeset(changeset):
    # apply_payload = {"state": "apply"}
    apply_payload = {"state": "apply", "auto-prompt": {"ays": "ays_yes"}}
    url = nvue_end_point + "/revision/" + requests.utils.quote(changeset,
                                                               safe="")
    r = requests.patch(url=url,
                       auth=auth,
                       verify=False,
                       data=json.dumps(apply_payload),
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

def is_config_applied(changeset) -> bool:
    # Check if the configuration was indeed applied
    global RETRIES
    global POLL_APPLIED
    retries = RETRIES
    while retries > 0:
        r = requests.get(url=nvue_end_point + "/revision/" + requests.utils.quote(changeset, safe=""),
                         auth=auth,
                         verify=False)
        response = r.json()
        print(response)

        if response["state"] == "applied":
            return True
        retries -= 1
        time.sleep(POLL_APPLIED)

    return False

def apply_new_config(path,payload):
    # Create a new revision ID
    changeset = create_nvue_changest()
    print("Using NVUE Changeset: '{}'".format(changeset))

    # Delete existing configuration
    query_string = {"rev": changeset}
    r = requests.delete(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Patch the new configuration
    
    query_string = {"rev": changeset}
    r = requests.patch(url=nvue_end_point + path,
                       auth=auth,
                       verify=False,
                       data=json.dumps(payload),
                       params=query_string,
                       headers=mime_header)
    print_request(r.request)
    print_response(r)

    # Apply the changes to the new revision changeset
    apply_nvue_changeset(changeset)

    # Check if the changeset was applied
    is_config_applied(changeset)

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    rt_payload = {
      "bgp":
      {
        "autonomous-system": 65101,
        "router-id":"10.10.10.1"
      }
    }
    apply_new_config("/router",rt_payload)
    vrf_payload = {
      "bgp":
      {
        "neighbor":
        {
          "swp51":
          {
            "remote-as":"external"
          }
        },
        "address-family":
        {
          "ipv4-unicast":
          {
            "network":
            {
              "10.10.10.1/32":{}
            }
          }
        }
      }
    }
    apply_new_config("/vrf/default/router",vrf_payload)
    time.sleep(DUMMY_SLEEP)
    nvue_get("/router")
    nvue_get("/vrf/default/router")
cumulus@switch:~$ nv set router bgp autonomous-system 65101
cumulus@switch:~$ nv set router bgp router-id 10.10.10.1
cumulus@switch:~$ nv set vrf default router bgp neighbor swp51 remote-as external
cumulus@switch:~$ nv set vrf default router bgp address-family ipv4-unicast network 10.10.10.1/32

操作操作

NVUE 操作操作是临时操作,不会修改配置的状态;它们重置接口、BGP、QoS 缓冲区和池的计数器,并从 protodown MLAG bond 中删除冲突。

要清除 swp1 上的计数器

cumulus@switch:~$ curl -u 'cumulus:cumulus' -H 'Content-Type:application/json' -d '{"@clear": {"state": "start", "parameters": {}}}' -k -X POST https://127.0.0.1:8765/nvue_v1/interface/swp1/counters
1
cumulus@switch:~$ curl -u 'cumulus:cumulus' -X GET https://127.0.0.1:8765/nvue_v1/action/1 -k
{"detail":"swp1 counters cleared.","http_status":200,"issue":[],"state":"action_success","status":"swp1 counters cleared.","timeout":60,"type":""}

要清除 swp1 上的 QoS 缓冲区

cumulus@switch:~$ curl -u 'cumulus:cumulus' -H 'Content-Type:application/json' -d '{"@clear": {"state": "start", "parameters": {}}}' -k -X POST https://127.0.0.1:8765/nvue_v1/interface/swp1/qos/buffer
2
cumulus@switch:~$ curl -u 'cumulus:cumulus'  -X GET https://127.0.0.1:8765/nvue_v1/action/2 -k
{"detail":"QoS buffers cleared on swp1.","http_status":200,"issue":[],"state":"action_success","status":"QoS buffers cleared on swp1.","timeout":60,"type":""}
#!/usr/bin/env python3

import requests
from requests.auth import HTTPBasicAuth
import json
import time

auth = HTTPBasicAuth(username="cumulus", password="password")
nvue_end_point = "https://127.0.0.1:8765/nvue_v1"
mime_header = {"Content-Type": "application/json"}

DUMMY_SLEEP = 5  # In seconds
POLL_APPLIED = 1  # in seconds
RETRIES = 10

def print_request(r: requests.Request):
    print("=======Request=======")
    print("URL:", r.url)
    print("Headers:", r.headers)
    print("Body:", r.body)

def print_response(r: requests.Response):
    print("=======Response=======")
    print("Headers:", r.headers)
    print("Body:", json.dumps(r.json(), indent=2))

def nvue_action():
    r = requests.post(url=nvue_end_point + path,
                      auth=auth,
                      verify=False,
                      data=json.dumps(apply_payload),
                      headers=mime_header)
    print_request(r.request)
    print_response(r)
    return response

def nvue_get(path):
    r = requests.get(url=nvue_end_point + path,
                     auth=auth,
                     verify=False)
    print_request(r.request)
    print_response(r)

if __name__ == "__main__":
    payload = {
      "@clear": 
      {
        "state": "start", 
        "parameters": {}
      }
    }
    action_id=nvue_action("/interface/swp1/qos/counter",payload)
    time.sleep(DUMMY_SLEEP)
    nvue_get(f"/action/{action_id}")
   
cumulus@switch:~$ nv action clear interface swp1 qos counter

Python 脚本示例

配置示例

在以下 python 示例中,full_config_example() 方法设置系统登录前消息,全局启用 BGP,并在单个批量操作中更改一些其他配置设置。API 端点转到根节点 /nvue_v1bridge_config_example() 方法执行针对 /nvue_v1/bridge/domain/<domain-id> 的目标 API 请求,以设置 vlan-vni-offset 属性。

示例配置脚本

在以下示例中,get_link_status() 获取作为参数传递的交换机的当前运行状态。link_status_down() 使作为参数传递的 leafsspines 之间的 totalLinks 链路断开。它使用 LLDP 发现邻居交换机,并筛选出非 400G 或 swp 的接口。link_status_up() 将先前断开的 downLinks 链路恢复。

链路状态操作脚本

重启示例

在以下示例中,switch_reboot() 重新启动作为参数传递的交换机。 issu_reboot() 在作为参数传递的交换机上触发 ISSU(系统内服务升级),并以定义的 reboot_mode 重新启动交换机。

ISSU 和交换机重启脚本

尝试 API

要试用 NVUE REST API,请使用 NVIDIA Air 上提供的 NVUE API 实验室。 该实验室提供了一个基本示例,帮助您入门。 您也可以尝试本文档中的其他示例。

资源

有关使用 NVUE REST API 的信息,请参阅 NVUE API Swagger 文档。 完整的对象模型下载可在此处获取:此处。

注意事项

  • 与 NVUE CLI 不同,NVUE API 不支持为用户帐户配置纯文本密码; 您必须使用 NVUE API 为用户帐户配置哈希密码。
  • 如果您需要在交换机上进行多次更新,NVIDIA 建议您使用根补丁,这样可以用更少的往返次数对交换机进行配置更改。 运行许多特定的 NVUE PATCH API 来设置或取消设置对象需要多次往返交换机,以建立 HTTP 连接、传输有效负载和响应、管理网络利用率等等。