0 介绍
为了方便openconfig yang模型的处理和使用,openconfig提供了ygot(Yang GO Tools)工具,他可以:
- 编译yang模型定义文件,生成go数据结构和相应的处理函数
- 模型实例数据的合法性检查,即检查数据是否符合yang语法(yang schema)定义,例如,检查数据的取值是否符合yang模型定义的数据有效范围或约束表达式。
- 提供数据转换功能。比如,yang模型实例数据与json、gnmi notification数据格式之间的转换
1 基本使用举例
1.1 编译ygot
ygot并没有直接提供二进制版本文件,我们可以从github上git pull代码后自行编译。
下载ygot最新版本代码(v0.28.3),然后进入generator目录,执行go build命令,生成二进制文件generator
% cd ygot-0.28.3/generator
% ls
generator.go generator_test.go
% go build
% ls
generator generator.go generator_test.go
1.2 准备yang模型文件
为了方便介绍ygot generator的使用,我们准备一个基本的yang module文件person.yang,这个文件只包含了list、container、leaf、leaf-list、type等几个最基本的最常用的语法元素,内容如下:
module person {
prefix "p";
namespace "urn:person";
leaf name { type string; }
list friends {
key "name";
leaf name { type string; }
leaf country { type string; }
}
container info {
leaf age { type uint32 { range "1..200"; } }
leaf-list hobbies { type string; }
}
}
1.2 编译yang模型文件
将上面生成的generator文件copy到bin目录下、person.yang文件copy到base/yang目录下,同时创建一个base/oc目录(oc为openconfig的简写),用来存放yang文件编译后生成的go文件。执行下面的命令:
% ./bin/generator -path=base/yang -output_file=base/oc/oc.go -package_name=oc base/yang/person.yang
参数说明:
-path=base/yang:表示yang文件存放的路径
-output_file=base/oc/oc.go:生成的go文件的文件路径名
-package_name=oc:生成的go文件的package名字
base/yang/person.yang:被编译的yang模型文件
命令执行完后,会在base/oc目录下新生成一个oc.go文件
1.3 生成的go文件与yang模型文件的对应关系
查看生成的oc.go文件,我们可以发现generator为list friends、container info分别生成了一个结构体,如下所示:
// Person_Friends represents the /person/friends YANG schema element.
type Person_Friends struct {
Country *string `path:"country" module:"person"`
Name *string `path:"name" module:"person"`
}
// Person_Info represents the /person/info YANG schema element.
type Person_Info struct {
Age *uint32 `path:"age" module:"person"`
Hobbies []string `path:"hobbies" module:"person"`
}
从上面两个结构体中,我们可以看出,这两个结构体的名字都是以yang模块名字为前缀的(Person_)。yang模型中的leaf成员对应结构体的指针成员,leaf-list对应结构体的切片,它们在yang模型文件中的路径和module信息在字段的tag中体现。同时,原文件中的leaf name { type string; }数据定义在oc.go文件中并没有相应的数据结构。list friends的key信息也丢失了。
同时,Person_Friends和Person_Info两个结构体均有几个同样的方法:
func (*Person_Friends) IsYANGGoStruct();
func (t *Person_Friends) ΛListKeyMap() (map[string]interface{}, error);
func (t *Person_Friends) ΛValidate(opts ...ygot.ValidationOption) error;
func (t *Person_Friends) Validate(opts ...ygot.ValidationOption) error;
func (t *Person_Friends) ΛEnumTypeMap() map[string][]reflect.Type;
func (*Person_Friends) ΛBelongingModule() string;
测试一下Person_Info的Validate()方法:
age := uint32(201)
personInfo := oc.Person_Info{
Age: &age,
Hobbies: []string{"basketball", "football"},
}
err := personInfo.Validate()
if err == nil { // success
fmt.Println("Valid")
} else {
fmt.Println("Fail")
}
上述代码执行后会输出Fail,与person.yang文件中定义的age变量有效范围是0到200的约束是一致。
我们定义的yang模型文件往往是为某个被管理对象定义的,典型的被管理对象就是一个设备,所以编译时指定一个顶层数据结构来封装yang文件定义的所有数据结构也是合情合理的,编译时可以通过generate_fakeroot和fakeroot_name参数来指定,如下所示:
% ./bin/generator -generate_fakeroot -fakeroot_name=device -compress_paths=true -path=base/yang -output_file=base/oc/oc.go -package_name=oc base/yang/person.yang
参数说明:
-generate_fakeroot:编译时生成root数据结构
-fakeroot_name=device:指定root数据结构的名字
-compress_paths=true:压缩路径名字
执行上面的命令,查看生成的oc.yang文件,可以发现生成了一个叫做Device的根数据结构:
type Device struct {
Friends map[string]*Friends `path:"friends" module:"person"`
Info *Info `path:"info" module:"person"`
Name *string `path:"name" module:"person"`
}
person.yang文件中定义的leaf name这次也出现在了Device结构体中,list friends生成的数据结构这次变成了map[string]*Friends,这个map的key就是person.yang文件中定义的friends的key name。list friends、container info对应的数据结构的名字也没有yang module名前缀了(因为编译时添加了-compress_paths=true选项参数)
生成的oc.yang文件的另一个变化是,Friends结构体有一个相应的NewFriends()函数,用来创建相应的变量。
1.4 json序列化和反序列化
根据yang模型定义文件生成了相应的go数据结构后,我们就可以使用通常的编程技术处理go数据结构了,包括将数据序列化为json字符串或进行json反序列化
示例代码如下所示:
// 构造新的Device
device := &oc.Device{}
device.NewFriends("zhangsan")
age := uint32(30)
device.Info = &oc.Info{
Age: &age,
Hobbies: []string{"basketbal", "football"},
}
// 合法性检查
err := device.Validate()
if err != nil { // success
fmt.Println("invalid")
os.Exit(1)
}
// 序列化为json字符串
jsonStr, _ := ygot.EmitJSON(device, &ygot.EmitJSONConfig{
Format: ygot.RFC7951,
Indent: " ",
RFC7951Config: &ygot.RFC7951JSONConfig{
AppendModuleName: true,
},
})
fmt.Println(jsonStr)
// 反序列化
newDevice := &oc.Device{}
oc.Unmarshal([]byte(jsonStr), newDevice)
// 合法性检查
err = newDevice.Validate()
if err != nil { // success
fmt.Println("invalid")
os.Exit(1)
}
// 再序列化,跟上面序列化后的内容应该一样
newJsonStr, _ := ygot.EmitJSON(device, &ygot.EmitJSONConfig{
Format: ygot.RFC7951,
Indent: " ",
RFC7951Config: &ygot.RFC7951JSONConfig{
AppendModuleName: true,
},
})
fmt.Println(newJsonStr)