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,避免版本冲突。我曾经因为没指定版本,导致网管加载了旧模块,解析失败——嗯,这坑我踩过。
我的习惯:每个模块开头都写descriptionrevision。别偷懒,半年后你自己都记不清这个模块是干嘛的。

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,维护起来多痛苦。定义一次,到处引用,这才是工程化的思路。

注意:YANG的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; } }
        }
    }
}

这种嵌套结构,说白了就是设备物理拓扑的映射。机框→槽位→端口,一层层下来,清晰得很。

避坑指南:列表的key不要用字符串类型,除非你能保证它永远不变。我建议用数字ID,性能更好,也避免大小写问题。

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参数,限制每秒上报数量——嗯,这是血的教训。

我的建议:RPC的输入参数尽量用mandatory true标记必填项,避免调用方漏传。通知里一定要带时间戳,否则网管侧无法排序和去重。

3.5 知识体系总览

说了这么多,咱们用一张图把YANG的核心逻辑串起来。你看着这张图,就能明白模块、数据类型、容器列表、RPC通知之间的关系。

YANG数据建模语言核心结构 YANG Module 数据类型定义 内置类型 派生类型 (typedef) leafref引用 容器与列表 container (单实例) list (多实例, key) 嵌套结构 RPC操作 input (输入参数) output (返回结果) mandatory约束 通知定义 事件上报 (notification) 图:YANG模块的四大核心组成部分及其关系

从这张图能看出来,YANG模块是根,下面分出四个分支:数据类型定义是基础,容器和列表是数据组织方式,RPC是主动操作,通知是被动上报。它们共同构成了一个完整的设备接口描述体系。

好了,这一章的内容就到这儿。YANG这东西,光看文档是学不会的。我建议你找个开源项目(比如OpenConfig的模型),打开一个YANG文件,对照着今天讲的结构,一行一行读下去。读懂了,你就能自己写模型了。

专注资料整理