专注NETCONF/YANG技术科普,接受任何问题,包括我答不上来的问题。欢迎光临!欢迎提问:链接
按计划这篇是写when和must,但这篇要不要写,写到什么程度我心里一直没底。碰巧又是外出刚回来,头脑思路还不够清晰。这篇计划边写边看,后面慢慢丰富。
7月10日:我又外出又回来了。sorry。
一、when和must的含义
when
when是说其数据节点是否存在需要满足一定的条件。满足when条件(when表达式值为true)时,此数据定义节点存在;不满足when条件时,此数据定义节点不存在。
因此,when条件一般要优先于其他属性要求。
比如如下一段YANG模型:leaf vlan-end先检查when条件是否成立;如果vlan-begin存在,则when条件成立,vlan-end节点存在;再检查vlan-end字段是否有值,如果vlan-end有值,则满足mandatory属性;leaf value节点也类似,如果vlan-begin存在,则leaf value节点的when条件也成立,value节点存在,如果未设置取值,按默认值=7处理。
container dscp {
leaf vlan-begin {
type uint16;
}
leaf vlan-end {
when "../vlan-begin";
type uint16;
mandatory true; // when条件成立时,vlan-end节点才存在,最后检查是否mandatory属性
}
leaf value {
when "../vlan-begin";
type uint8;
default 7; // when条件成立时,value节点才存在,如果没有设置value的值,按默认值=7处理
}
}
}
must
must是在节点已经存在的前提下对有效数据的一种强制约束。must后面跟着XPATH表达式,里面可以包含XPATH1.0的函数。在commit阶段会进行数据的validation,此时会验证must条件的表达式值必须为true。
也就是说,先有节点存在,后进行数据validation验证强制约束必须满足。比如下面这段,如果vlan-begin没有值,则vlan-end上的must条件不用管它;如果vlan-begin有值,则vlan-end必须存在,且必须满足vlan-begin < vlan-end。
container dscp {
leaf vlan-begin {
type uint16;
}
leaf vlan-end {
when "../vlan-begin";
must "../vlan-begin < ../vlan-end"; // when条件成立时,检查此must条件必须成立,即:必须vlan-begin < vlan-end
type uint16;
mandatory true;
}
}
}
扩展:NP-container和must
结合之前说过的NP-container认为永远存在,那么NP-container上的must条件就要永远为true,否则会报错。
下面给出一个错误模型的例子:如果dscp/vlan-begin和dscp/vlan-end都没有值,但是container dscp认为还是存在,那么container dscp上的must条件还是要求成立。这就跟“dscp/vlan-begin和dscp/vlan-end都没有值”是矛盾的。所以说这个例子是错误的。
// 这是个错误的例子
container dscp {
must "vlan-begin < vlan-end"; // NP-container认为永远存在,检查此must条件必须成立,即:必须vlan-begin < vlan-end
leaf vlan-begin {
type uint16;
}
leaf vlan-end {
when "../vlan-begin";
type uint16;
mandatory true;
}
}
}
总结一下,when是对节点是否存在的一个前提条件;而must是在节点存在的前提下对数据validation的一个约束校验。由此可见,两者的含义完全不同,网管实现的阶段也完全不同。
二、XPATH1.0函数
when/must都是使用的XPATH1.0的语法和函数。相关语法和函数建议查看链接。
XPATH实际支持的运算符和函数不少。实际YANG文件中XPATH函数用的并不多,常见的有运算符(见下图)、获取实例(current)、数值计算函数/字符串函数/合计函数等。
YANG的XPATH函数写太复杂了也未必就好。YANG毕竟是机机语言,现在又有哪些网管能完整解析XPATH1.0的函数呢?XPATH的解析栈深度到达一定字节数后就溢出了,XPATH写复杂后网管可能就无法解析了。——这块并不想展开多说。
XPATH按“绝对路径”和“相对路径”,有两类写法。
绝对路径是用"/"开头,只能表达路径位置,对应这个路径位置的所有管理对象模型。写法如:/interface/enabled 是绝对路径的XPATH。
相对路径是从当前路径开始找起,向上或者向下找路径位置,可以表达是相同的实例对象。写法如:…/enabled 是相对路径的XPATH。
下面用一个例子来描述两者区别:
当接口使能的情况下,才能设置mirror-enable=true。因此设置mirror-enable=true时要求这个接口必须enable。这里要找同一个interface实例对象,因此使用的是相对路径…/enabled = true。
如果改成绝对路径“/interface/enabled = true”,含义就变成,任意/interface实例只要有一个enabled=true,其他接口就可以设置mirror-enable=true了。这样显然含义就变了。
list interface {
key "name";
...
leaf enabled {
type boolean;
}
leaf mirror-enable {
type boolean;
must '../enabled = "true"'; // 同一个接口必须enabled=true,才能设置mirror-enable
// must '/interface/enabled = "true"'; // 所有接口中只要有一个接口enabled=true,才能设置mirror-enable。 <-- 这样写是错误的。
}
...
}
current()函数
如果两个不同的YANG实例模型中用到了相同的实例对象,并且相同的实例对象中两个模型中的一些属性要形成一定的业务约束。怎么办呢?
下面只说一种YANG1.1中推荐的当前实例表达的XPATH函数current()。current()函数没有入参,返回的是当前实例对象。
比如:
//模型一:接口创建时生成/interface对象。
list interface {
key "name";
...
leaf enabled {
type boolean;
}
...
}
//模型二:路由策略模型,要求出接口的是能状态必须是true(使能)。其中的路由出接口引用了/interface/name,要求必须和/interface对象名称保持一致。
leaf outgoing-interface {
type leafref {
path "/interface/name";
}
must '/interface[name=current()]/enabled = "true"'; // outgoing-interface引用(leafref)的接口对象,必须在/interface实例下,且enabled=true。
}
网管数据库对when/must的实现
刚才的current函数已经展示了其XPATH的复杂性。这就给网管无论是做when节点的存在性判断,还是must的数据强制约束,都带来了内存的消耗以及时间开销。
更重要的是:有些数据约束must能表达,有些业务上的数据约束,当前的XPATH函数甚至是写不出来的(如:IPV4组播地址仅支持某个小网段)。所以,当前网管当前对when/must的数据校验并不那么热衷于其完备性。
另一方面,when/must并不影响数据库建表。因此网管对于when/must的态度是:交给设备侧自己做校验。网管dryrun一般是尽力而为,甚至不做校验。
使用when/must的必要性
when/must只是模型约束的一种表达。模型约束是业务约束的体现。因此,如果业务约束客观存在无法消除的话,模型约束也是客观存在的。
在数据通信行业中,技术迭代更新慢、可靠性要求高、运营商网络、网管的维护成本也高。因此对兼容性要求比较高。也就是说,在不改变配置模型的情况下,也无约束又客观存在,很大程度上的模型约束是无法消除的(除非有约束但是在YANG模型中不表达,思科的有些产品就是这么做的)。
目前的确有设备供应厂商在对业务特性的配置界面进行重构。传闻:NOKIA在2022年(?)完成重构、思科在近期的CISCO LIVE上也开始宣传他们重构后的YANG模型。
但还有厂商对此持怀疑态度。毕竟,为了一个大家都没有看到商业盈利价值的更高阶的网管配置、管理功能,就让设备厂商进行业务重构,这个代价是巨大的。我们目前看到NOKIA和CISCO的重构也仅仅是针对部分产品进行的。不是所有产品都铺开搞。
设备厂商的when/must使用现状
juniper
juniper在2023年发布的“23.1R1.8\junos-qfx”YANG模型中,
- 没有使用when。
- must采用了juniper自己的私有语法“junos:must”,并对此增加了相关说明“junos:must-message”,两者配对使用。
- 总结:可以看出juniper一贯的XML风格配置能极好地适应机机接口,配置联动(when)很少,业务约束存在,但是juniper只用了私有语法,看来也不指望通用网管能做啥。
NOKIA
诺基亚2022年在欧洲就号称已经完成YANG机机风格的配置模型重构。现拿2023年7月发布的Nokia7x50_YangModels\latest_sros_23.7YANG模型分析:
- when使用了1671次。
- must在私有YANG中没有。但是模型description中有很多约束类的文字描述(must单词搜到1214次):可见诺基亚还是有很多操作限制的,应该是处于一个转型的过程中。
- 另外,openconfig的deviation YANG文件中有deviate add must:可见openconfig对设备厂商的排斥,诺基亚也遇到了类似的问题。
CISCO
CISCO在2023年发布的“xr\791”YANG模型中,除去公有YANG,
- when使用了1710次。
- must使用了5260次。
- 总结:配置联动和业务约束不少,显然没有拜托人机界面防呆的历史特点。但是XPATH并不复杂,when/must比较容易看懂。XPATH相对路径也比较浅,说明相同业务模型聚合的比较好。另外思科喜欢用"…/interface-name[starts-with(text(),‘Loopback’)]"表达接口类型,是这家公司的配置特点。
问题来了:您所在产品的业务特性中有很多依赖互斥的约束吗?
如何优化when/must逻辑
具体问题具体对待。我在github上找了一些模型,给出一些我认为的更合理的优化方案。因为涉及一些厂商比较尴尬,这里略去厂商名称。
案例一:当xxx=true时yyy必须存在——改presence container
container bit-error-detection {
leaf enable {
type boolean;
must "(../enable='false') or (../enable='true' and (../med or ../local-preference))"; // <-- (2)
default "false";
}
leaf med {
when "../enable='true'"; // <-- (1)
type uint32;
}
leaf local-preference {
when "../enable='true'"; // <-- (1)
type uint32;
}
leaf route-policy {
when "../enable='true'"; // <-- (1)
type leafref {
path "/rtp:routing-policy/rtp:policy-definitions/rtp:policy-definition/rtp:name";
}
}
}
}
优化点:
(1) container中的其他leaf都是when “…/enable=‘true’”,显然leaf enable可以改presence container,从而省略下面的when条件。
(2) bit-error-detection使能时,med 和 local-preference两个节点必须二选一。这个must条件提到container上。并做优化。
优化后的模型如下:
container bit-error-detection {
presence "Enable bit error detection";
must "med or local-preference";
leaf med {
type uint32;
}
leaf local-preference {
type uint32;
}
leaf route-policy {
type leafref {
path "/rtp:routing-policy/rtp:policy-definitions/rtp:policy-definition/rtp:name";
}
}
}
}
案例二:当xxx=true时yyy必须存在——改when - mandatory
typedef route-limit-type {
type enumeration {
enum "noparameter" { value 0; }
enum "alert-only" { value 1; }
enum "idle-forever" { value 2; }
enum "idle-timeout" { value 3; }
}
}
...
augment "/ni:network-instance/ni:instances/ni:instance/bgp:bgp/bgp:base-process/bgp:peers/bgp:peer/bgp:afs/bgp:af" {
description "Augment for an L2VPN instance and EVPN association.";
...
container l2vpn-evpn {
leaf route-limit-type {
when "../route-limit";
type route-limit-type;
must "(../route-limit-type = 'idle-timeout' and ../route-limit-idle-timeout) or (../route-limit-type = 'alert-only') or (../route-limit-type = 'idle-forever') or (../route-limit-type = 'noparameter')";; // <-- (1)
}
leaf route-limit-idle-timeout {
when "../route-limit-type = 'idle-timeout'";
type uint16;
units "min";
}
}
}
优化点:
(1) leaf route-limit-type的must条件中,对所有的route-limit-type枚举做了列举。纯属多余,且后续typedef扩充时这里容易修改遗漏。这里是为了表达leaf route-limit-type = 'idle-timeout’时,下面的leaf route-limit-idle-timeout必须有值。这里改成when-mandatory即可。
优化后的模型如下:
typedef route-limit-type {
type enumeration {
enum "noparameter" { value 0; }
enum "alert-only" { value 1; }
enum "idle-forever" { value 2; }
enum "idle-timeout" { value 3; }
}
}
...
augment "/ni:network-instance/ni:instances/ni:instance/bgp:bgp/bgp:base-process/bgp:peers/bgp:peer/bgp:afs/bgp:af" {
description "Augment for an L2VPN instance and EVPN association.";
...
container l2vpn-evpn {
leaf route-limit-type {
when "../route-limit";
type route-limit-type;
}
leaf route-limit-idle-timeout {
when "../route-limit-type = 'idle-timeout'";
mandatory true;
type uint16;
units "min";
}
}
}
案例三:must逻辑冗余——简化must逻辑(when类似)
container color-aware {
leaf cir-value {
type uint32;
must "(../cir-units and ../set-dei) or (../cir-units and ../eir-cos) or (../cir-units and ../set-dei and ../eir-cos)"; // <-- (1)
}
leaf cir-units {
type dt1:Sat-inf-rate-units;
must "(../cir-value and ../set-dei) or (../cir-value and ../eir-cos) or (../cir-value and ../set-dei and ../eir-cos)"; // <-- (1)
}
leaf set-dei {
type boolean;
must "../cir-value and ../cir-units"; // <-- (2)
}
leaf eir-cos {
type uint32 { range "0..7"; }
must "../cir-value and ../cir-units"; // <-- (2)
}
}
优化点:
(1) 单独阅读leaf cir-value 和cir-units的must条件,呈现A or B or (A and B)的格式,因此第三个“或”逻辑语句冗余,可以删除。
优化后的模型如下:
container color-aware {
leaf cir-value {
type uint32;
must "../cir-units and (../set-dei or ../eir-cos)"; // <-- (1)
}
leaf cir-units {
type dt1:Sat-inf-rate-units;
must "../cir-value and (../set-dei or ../eir-cos)"; // <-- (1)
}
leaf set-dei {
type boolean;
must "../cir-value and ../cir-units"; // <-- (2)
}
leaf eir-cos {
type uint32 { range "0..7"; }
must "../cir-value and ../cir-units"; // <-- (2)
}
}