3. YANG数据建模语言:YANG模块结构、数据类型定义、容器与列表、RPC与通知定义
好,咱们今天聊聊YANG。说实话,我刚接触YANG那会儿,觉得它就是一堆嵌套的括号和关键字,看着头大。但干久了你会发现,YANG就是网管和设备之间的“普通话”。没有它,每个厂家的接口都是方言,集成起来能把你逼疯。
YANG是RFC 6020定义的,后来升级到RFC 7950(YANG 1.1)。它用来描述网络设备的数据模型,说白了就是告诉网管:设备上有哪些配置项、能查哪些状态、支持什么操作。咱们今天把这几个核心概念掰开揉碎讲清楚。
3.1 YANG模块结构
一个YANG模块,就像一份设备接口的“说明书”。我习惯把它拆成三部分:头部声明、导入依赖、正文定义。
先看个最简单的模块骨架:
module example-interface {
yang-version 1.1;
namespace "http://example.com/ns/interface";
prefix if;
import ietf-yang-types {
prefix yang;
}
description "这是一个接口配置的示例模块";
revision 2024-01-15 {
description "初始版本";
}
// 正文内容:容器、列表、叶子等
}
这里有几个关键点:
- module:模块名,全局唯一。我一般用“公司名-设备类型-功能”的命名方式,比如
huawei-otn-line。 - namespace:XML命名空间,必须是一个URI。这玩意儿不能重复,我见过有人随便写个URL,结果跟别人冲突了,调试时一脸懵。
- prefix:前缀,相当于别名。引用其他模块时用这个前缀,比如
yang:date-and-time。 - import:导入其他模块。注意,YANG 1.1支持
import时指定revision-date,避免版本冲突。我曾经因为没指定版本,导致网管加载了旧模块,解析失败——嗯,这坑我踩过。
description和revision。别偷懒,半年后你自己都记不清这个模块是干嘛的。
3.2 数据类型定义
YANG的数据类型,分为内置类型和派生类型。内置类型就像乐高的基础积木,派生类型就是你用这些积木搭出来的特殊形状。
内置类型常用的有:
| 类型 | 说明 | 示例 |
|---|---|---|
| string | 字符串 | "GigabitEthernet0/1" |
| uint32 | 32位无符号整数 | 1000 |
| boolean | 布尔值 | true / false |
| enumeration | 枚举 | up / down / testing |
| leafref | 引用另一个叶子节点的值 | 引用接口名 |
派生类型用typedef定义。举个例子:
typedef port-speed {
type enumeration {
enum 10M;
enum 100M;
enum 1G;
enum 10G;
enum 100G;
}
description "端口速率,单位bps";
}
leaf speed {
type port-speed;
default 1G;
}
为什么用派生类型?你想想看,如果每个端口速率的地方都写一遍enumeration,维护起来多痛苦。定义一次,到处引用,这才是工程化的思路。
leafref类型很强大,但容易造成循环引用。我曾经设计一个模型,A引用了B,B又引用了A,结果验证工具直接报栈溢出。所以,引用关系一定要画清楚。
3.3 容器与列表
容器(container)和列表(list)是YANG里最常用的结构。容器就像文件夹,列表就像表格。
容器:用来组织相关的叶子节点。它本身没有值,就是个“收纳盒”。
container interface-config {
leaf name {
type string;
}
leaf mtu {
type uint16;
default 1500;
}
leaf admin-status {
type enumeration {
enum up;
enum down;
}
default down;
}
}
列表:可以包含多个实例,每个实例由key唯一标识。这在实际设备中太常见了——比如端口列表、光模块列表、交叉连接列表。
list optical-port {
key "port-id";
leaf port-id {
type uint32;
description "端口编号";
}
leaf port-type {
type enumeration {
enum "10GE";
enum "100GE";
enum "OTU4";
}
}
leaf tx-power {
type decimal64 {
fraction-digits 2;
}
units "dBm";
}
leaf rx-power {
type decimal64 {
fraction-digits 2;
}
units "dBm";
}
}
这里有个细节:key必须唯一。我在项目中遇到过,有人把端口名和端口号都设为key,结果同一个端口改了名字后,网管里出现了两条记录——数据全乱了。所以,key要选真正不变的属性,比如端口ID或槽位号。
容器和列表还能嵌套。比如:
container device {
list slot {
key "slot-id";
leaf slot-id { type uint8; }
list port {
key "port-id";
leaf port-id { type uint8; }
leaf status { type enumeration { enum up; enum down; } }
}
}
}
这种嵌套结构,说白了就是设备物理拓扑的映射。机框→槽位→端口,一层层下来,清晰得很。
3.4 RPC与通知定义
YANG不光能描述配置和状态,还能定义操作和事件。这就是RPC和通知。
RPC:远程过程调用,相当于设备对外提供的“服务接口”。比如重启端口、切换保护、执行诊断。
rpc reset-port {
description "复位指定端口";
input {
leaf port-name {
type string;
mandatory true;
description "要复位的端口名称";
}
leaf reset-type {
type enumeration {
enum "soft";
enum "hard";
}
default "soft";
}
}
output {
leaf result {
type enumeration {
enum success;
enum failure;
}
}
leaf message {
type string;
description "操作结果描述";
}
}
}
你看,RPC有输入和输出,就像函数调用。我习惯把输出里加一个message字段,这样失败时能返回具体原因,方便排查。
通知:设备主动上报的事件。比如端口down了、光功率越限、设备温度过高。
notification port-alarm {
description "端口告警通知";
leaf port-name {
type string;
}
leaf alarm-type {
type enumeration {
enum "link-down";
enum "signal-degrade";
enum "los";
}
}
leaf timestamp {
type yang:date-and-time;
}
}
通知的定义很简单,但实际部署时要注意频率控制。我曾经遇到一个设备,每秒钟上报几百条告警,网管直接被冲垮了。后来我们在模型里加了throttle参数,限制每秒上报数量——嗯,这是血的教训。
mandatory true标记必填项,避免调用方漏传。通知里一定要带时间戳,否则网管侧无法排序和去重。
3.5 知识体系总览
说了这么多,咱们用一张图把YANG的核心逻辑串起来。你看着这张图,就能明白模块、数据类型、容器列表、RPC通知之间的关系。
从这张图能看出来,YANG模块是根,下面分出四个分支:数据类型定义是基础,容器和列表是数据组织方式,RPC是主动操作,通知是被动上报。它们共同构成了一个完整的设备接口描述体系。
好了,这一章的内容就到这儿。YANG这东西,光看文档是学不会的。我建议你找个开源项目(比如OpenConfig的模型),打开一个YANG文件,对照着今天讲的结构,一行一行读下去。读懂了,你就能自己写模型了。