使用ygot工具处理yang模型

806 阅读4分钟

0 介绍

为了方便openconfig yang模型的处理和使用,openconfig提供了ygot(Yang GO Tools)工具,他可以:

  1. 编译yang模型定义文件,生成go数据结构和相应的处理函数
  2. 模型实例数据的合法性检查,即检查数据是否符合yang语法(yang schema)定义,例如,检查数据的取值是否符合yang模型定义的数据有效范围或约束表达式。
  3. 提供数据转换功能。比如,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_fakerootfakeroot_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)