题图由 Helena Sushitskaya 在 Pixabay 上发布
---------------------
很久之前我就发现 Cisco SDN 产品的网络拓扑非常漂亮,而且性能极佳,尤其是 Cisco Crosswork Optimization Engine,但不知道是怎么做出来的。最近在 Cisco DevNet 网站上发现一个网络拓扑可视化工具的教程,工具的名字叫做 NeXt UI,非常酷,原来那些拓扑就是用它来实现的。
链接在:
https://developer.cisco.com/site/neXt/
里面有 2 个视频,如果你不能访问油管,我把它们搬过来了。第 1 个是宣传片,很明显这是五年前 OpenDayLight 正如日中天的时候发布的,当时开发的目的是作为 ODL 的一个 App。虽然现在 ODL 日渐式微,但 NeXt UI 在 C 记的 SDN 家族中焕发了新生。
第 2 个是 Demo 视频:
只要去 Cisco 官网注册一个 CCO 账号就可以访问 DevNet 网站。如果不想注册,用 Github 账号也可以。但我个人感觉 Github 上面的 NeXt 教程读起来更舒服一些。网址:
https://github.com/NeXt-UI/next-tutorials
另外,有位俄罗斯的客户写了一个 Blog 介绍 Nornir + NeXt,很详细,推荐各位阅读:
https://habr.com/en/post/534716/
下面是我的笔记,如果您更喜欢读中文,可以用来做参考。但是建议先了解怎样使用 Nornir:
-----------------------------------
1. 安装 NeXt
NeXt 是基于 JavaScript 框架开发的,如果要做成 App 对外提供服务,需要完整安装 NeXt 和 JavaScript 环境。macOS 需要使用 bower 或者 npm 来安装 NeXt。
brew updatebrew install nodenpm install -g bowerbower install NeXt
也可以直接使用 npm 安装 NeXt:
brew updatebrew install nodenpm install next-ui
如果你已经有 JavaScript 环境,也可以直接从 GitHub 克隆源码,将 CSS/JS/Fonts 等文件夹复制到工作目录。
git clone https://github.com/NeXt-UI/next-bower.git
然后新建一个 HTML 文件,写一个 Hello world。注意要根据你的文件存储位置来修改 index.html 里面的相对路径。
<!DOCTYPE html><html><head><link rel="stylesheet" href="next-bower/css/next.css"><script type="text/javascript" src="next-bower/js/next.js"></script></head><body>Hello NeXt</body></html>
然后浏览器打开 index.html 验证一下。

2. 制作一个拓扑
2.1. NeXt 的文件结构
我们刚才在工作目录安装 NeXt 生成了 next-bower/ 文件夹,又在工作目录(也是 Web-service 的根目录)创建了 index.html 文件。接下来还需要创建 2 个重要的 JavaScript 文件 app.js 和 data.js。我把它们放在 app/ 文件夹里面,当然也可以放到其他目录。
app/app.jsdata.jsnext-bower/css/fonts/js/index.html
其中 data.js 存储拓扑的节点信息和链路信息;app.js 利用 data.js 的数据生成拓扑实例。
2.2. data.js
制作拓扑需要我们了解一些 JavaScript 变量和函数的写法,这对于我这种没有编程功底的菜鸟来说蛮挑战的。推荐菜鸟教程,关于 JS 函数的部分。
https://www.runoob.com/js/js-function-definition.html
data.js 很简单,就是一个变量。这里我们将这个变量命名为 topologyData,它的值是一个 json 格式的数列。它包含 3 个主 key: nodes,links,和一个可选的 nodeSet。注意结尾要有一个分号。
var topologyData = {"nodes": [{"id": 0,"name": "Beijing"},{"id": 1,"name": "Shanghai"},{"id": 2,"name": "Guangzhou"}],"links": [{"source": 0,"target": 1},{"source": 0,"target": 2}],"nodeSet": [{"id": 3, // 不能和 node ID 重复"nodes": [1, 2]}]};
nodes 包含 6 个属性,其中:
id 和 name 用于标识节点,建议自定义,但需要保证唯一性
x 和 y 是坐标,可以不提供,因为 NeXt 支持自动 layout
type 和 color 用于自定义节点的图标和颜色,可以采用默认的设置

links 包含 3 个属性,其中:
id 用于标识链路,如果自定义,需要保证唯一性,但可以和 node id 重复
source 和 target 用于指定 link 的起始节点。这也是为什么建议自定义 node id 的原因,否则很难对 link 进行交互式的操作

nodeSet 的作用是把拓扑上的几个节点折叠成一个图标,并且提供展开的按钮,还支持多层折叠。如果看过上面的视频,就会知道我在说什么了。因为折叠不是必须的,所以 nodeSet 也不是必须的。但要注意 nodeSet id 不能和 node id 重复。

2.3. JavaScript 函数的语法
JavaScript 支持 2 种函数的定义方式。一种是声明式的函数:
// JavaScriptfunction myFunction(a, b) {return a * b;}
上面这段代码等同于:
# Pythondef myFunction(a, b):return a * b
另外一种函数定义方式是函数表达式,将函数赋值给一个变量:
// JavaScriptvar x = function (a, b) {return a * b};
上面的代码等同于:
# Pythonx = lambda a, b: a * b
JavaScript 表达式函数支持自调用,这一点和 Python 很不一样。下面 2 段代码会返回相同的结果(注意第一段代码以分号结尾):
// JavaScript(function(a, b){return a * b})(2, 3);
# Pythonx = lambda a, b: a * bx(2, 3)
此外,如果在 Python 中,要为一个对象创建一个实例,直接赋值变量即可。但是在 JavaScript 中,必须用 new 关键字来声明这是一个基于 Object 创建的 instance。
2.4. app.js
app.js 稍微复杂一点,并且需要调用若干 NeXt 的 API。API 参考手册在本地
./bower_components/next-bower/doc/index.html,直接用浏览器打开即可。

撰写一个基本的 app.js 分为以下几个步骤:
定义一个自调用的表达式函数,参数为 nx
实例化一个 nx.ui.Application() 类,并赋值给一个变量 A
定义一个拓扑配置文件并赋值给一个变量 B
利用第 3 步的变量 B,实例化一个 nx.graphic.Topology() 类,并赋值给一个变量 C
变量 C 从 data.js 中加载拓扑数据(C 本身是一个 instance)
将 NeXt 应用实例(变量 A)绑定到拓扑实例(变量 C)
指定 NeXt 应用实例(变量 A)运行在一个 DOM (Document Object Model) container
下面是一个 app.js 的例子:
// 1. 定义一个自调用的表达式函数,参数为 nx(function(nx){// 2. 实例化一个 nx.ui.Application() 类,并赋值给一个变量 appvar app = new nx.ui.Application();// 3. 定义一个拓扑配置文件并赋值给一个变量 topologyConfigvar topologyConfig = {// 指定 nodes 的配置,以 name 作为标签显示在拓扑上,图标是 “router”"nodeConfig": {"label": "model.name","iconType": "router"},// 指定 links 的配置"linkConfig": {"linkType": "curve"},// true 则显示图标,false 则显示一个点。注意 t 和 f 是小写的,与 Python 不同"showIcon": true,// 自动计算节点的位置,不需要指定 x 和 y 坐标"dataProcessor": "force"};// 4. 利用第 3 步的变量 topologyConfig,实例化一个 nx.graphic.Topology() 类,并赋值给变量 topologyvar topology = new nx.graphic.Topology(topologyConfig);// 5. 变量 topology 从 data.js 中加载拓扑数据(topology 本身是一个 instance)topology.data(topologyData);// 6. 将 NeXt 应用实例(变量 app)绑定到拓扑实例(变量 topology)topology.attach(app);// 7. 指定 NeXt 应用实例(变量 app)运行在一个 BOM container。ID 是可以自定义的app.container(document.getElementById("topology-container"));})(nx);
2.5. 补充 index.html 的 body
在 index.html 的 <body></body> 里面要指定 data.js 和 app.js 的路径,还有 DOM container 的 ID。注意这个 ID 必须和 app.js 里面定义的相同。
<!DOCTYPE html><html><head><title>My first NeXt topology!</title><link rel="stylesheet" href="next-bower/css/next.css"><script type="text/javascript" src="next-bower/js/next.js"></script></head><body><div id="topology-container"></div><script type="text/javascript" src="app/data.js"></script><script type="text/javascript" src="app/app.js"></script></body></html>
2.6. Done
拓扑搞定,打开本地的 index.html 验证:

3. 交互式拓扑的思路
现在咱们来梳理以下拓扑应用的工作流。在了解 NeXt 的使用方法之后,我们可以发现应用的关键是自动化修改 data.js;而 data.js 的数据来源可以从 LLDP/BGP-LS/PCEP 等协议获取;而 LLDP/BGP-LS/PCEP 的数据则可以利用 Nornir/Ansible 自动化获取。
这就是我们下一篇要研究的内容。




