VPP-Agent对接UPF CLI的原理
本文档描述 VPP-Agent 对接 UPF CLI 的开发流程及相关基础知识。
VPP Agent 编译部署
1、参考:https://docs.ligato.io/en/latest/developer-guide/build-vpp-agent-no-image/
解决方法:不要用 5GS 库里的(目前可能有问题),从 git 上下载。
下载连接: https://github.com/ligato/vpp-agent
版本:release/3.2.x
2、 编译安装 VPP ,由于我的机器(172.18.22.211)上已经有 VPP 了,故不再重新编译。
3、编译 VPP agent ,在我的 vpp agent 目录下( /opt/vpp-agent-release-3.2.x )下执行 make build
4、编译好 agent 后,在 cmd/vpp-agent 目录下会生成可执行文件 vpp-agent 。
5、配置文件路径(此处使用 /opt/vpp-agent/dev )存放配置文件 etcd.conf 、 grpc.conf 、 supervisor.conf ,三个文件。
6、执行可执行文件 vpp-agent 。 ./vpp-agent -config-dir=/opt/vpp-agent/dev
即可运行。
UPF 与 VPP-Agent 对接
1、对接架构:
注:重点关注图中红框的三部分—— VPP 生成 json 文件, Binary API (govpp) 使用 VPP 生成的 json 文件生成对应的 go 接口, VPP Agent 调用 go 接口来进行对 VPP 的配置。
https://fd.io/docs/vpp/master/gettingstarted/developers/add_plugin_goapi.html
2、 VPP Agent 调用流程(以 interface 为例,使用 ETCD 方式添加)
将 vpp-agent 的 config-dir 目录下的 log.conf 修改日志级别为 debug。
# file : ${config-dir}/log.conf default-level: debug
使用 vppctl 查看 interface :
使用 etcdctl 请求 vpp agent ,命令如下:
docker exec etcd etcdctl put /vnf-agent/vpp1/config/vpp/v2/interfaces/loop1 \ '{"name":"loop1","type":"SOFTWARE_LOOPBACK","enabled":true,"ip_addresses":["192.168.1.1/24"]}'
注: 1)etcd 是 KV 数据库,存放 VPP 配置信息。 etcdctl 是 etcd 客户端的命令行工具,可以用来操作 etcd 数据存储。
2)
/vnf-agent/vpp1/config/vpp/v2/interfaces/loop1
是请求的 key prefix 。该 key prefix 的组成是/vnf-agent/<microservice label>/config/<module>/<version>/<type>/<identifier>
,故/vnf-agent/vpp1/config
表示使用的配置,vpp
是 key 所在的模块,v2
是模块版本信息,interface
是 key 的类型, loop1 是 key 的标识。查看 vpp agent 日志,看到北向接口已经收到数据:
代码调用逻辑:
######################################## 初始化 watcher # file : plugins/orchestrator/watcher/aggregator.go Watch # 初始化观察者 |_ make(chan datasync.ChangeEvent) |_ make(chan datasync.ResyncEvent) |_ go func(i int, chanChange chan datasync.ChangeEvent, chanResync chan datasync.ResyncEvent) ######################################## vppagent 观察到数据库变化后的处理逻辑 # file : plugins/kvscheduler/txn_process.go postProcessTransaction |_ defer trace.StartRegion(txn.ctx, "postProcessTransaction").End() |_ defer trackTransactionMethod("postProcessTransaction")() |_ s.scheduleRetries(txn, graphW, toRetry) |_ for _, key := range s.updatedStates.Iterate() # 收集更新状态信息 |_ s.updatedStates = utils.NewSliceBasedKeySet() # 清除更新状态的集合 |_ if txn.txnType == kvs.NBTransaction && txn.nb.isBlocking |_ case txn.nb.resultChan <- txnResult{txnSeqNum: txn.seqNum, err: txnErr}: # 成功发送到 caller |_ else # 非北向接口,除打日志外无其他操作。 |_ for _, watcher := range s.valStateWatchers # 发送更新值给 watchers |_ for _, stateUpdate := range stateUpdates # # file : plugins/orchestrator/orchestrator.go func (p *Plugin) watchEvents() # |_ case e := <-p.changeChan: # 处理数据变化事件 |_ ctx := e.GetContext() |_ _, withDataSrc := DataSrcFromContext(ctx) |_ ctx = kvs.WithRetryDefault(ctx) |_ _, err = p.PushData(ctx, kvPairs) # 发送数据。详见步骤4。 |_ e.Done(err) |_ case e := <-p.resyncChan: # 处理同步事件 |_ case <-p.quit: # 退出
vpp agent 将请求转发到 plugins/orchestrator/dispatcher.go :
代码调用逻辑:
# file : plugins/orchestrator/dispatcher.go
PushData(ctx, kvPairs)
|_ txn := p.kvs.StartNBTransaction() # 获取北向接口的内容
|_ txn.Commit(ctx)
|_ p.kvs.TransactionBarrier() # 确认通知接收
vpp agent 通过南向接口请求 vpp :
代码调用逻辑:
# file : plugins/kvscheduler/txn_process.go
postProcessTransaction
|_ defer trace.StartRegion(txn.ctx, "postProcessTransaction").End()
|_ defer trackTransactionMethod("postProcessTransaction")()
|_ s.scheduleRetries(txn, graphW, toRetry)
|_ for _, key := range s.updatedStates.Iterate() # 收集更新状态信息
|_ s.updatedStates = utils.NewSliceBasedKeySet() # 清除更新状态的集合
|_ if txn.txnType == kvs.NBTransaction && txn.nb.isBlocking
|_ case txn.nb.resultChan <- txnResult{txnSeqNum: txn.seqNum, err: txnErr}: # 成功发送到 caller
|_ else # 非北向接口,除打日志外无其他操作。
|_ for _, watcher := range s.valStateWatchers # 发送更新值给 watchers
|_ for _, stateUpdate := range stateUpdates
|_ if watcher.selector == nil || watcher.selector(stateUpdate.Value.Key) # 发送给 watcher
|_ case watcher.channel <- stateUpdate:
# file : plugins/vpp/ifplugin/interface_state.go
func (c *InterfaceStateUpdater) doUpdatesIfStateDetails()
|_ ifaces, err := c.ifHandler.DumpInterfaceStates(ifIdxs...)
|_ c.access.Lock()
|_ for _, ifaceDetails := range ifaces
|_ c.updateIfStateDetails(ifaceDetails)
|_ c.access.Unlock()
# file : cn-infra\health\statuscheck\plugin_impl_statuscheck.go
# 注:该文件在 cn-infra 中
func (p *Plugin) reportStateChange(pluginName infra.PluginName, state PluginState, lastError error)
|_ stat.State = stateToProto(state) # update plugins state
|_ p.publishPluginData(pluginName, stat)
|_ p.agentStat.State = stateToProto(state) # update global state
|_ p.publishAgentData()
使用 vppctl 查看 interface 已经添加:
vpp agent 将该条记录存储到 kvscheduler 中:
3、开发流程:
[VPP-UPF] 在 upf.api 文件中添加数据模型;
[VPP-UPF] 在 upf_api.c 文件中添加 API 处理;
[VPP-UPF] 编译安装 upf ,在 /usr/share/vpp/api/plugins 目录中可以看到1,2步生成的 upf.api.json 文件;
[GoVpp] genreate-binapi 将 upf.api. json 文件生成 upf.ba.go 文件。
[VPPAgent] 在 vpp plugin 中导入 GoVppMux plugin 并准备好 VPP channel (初始化申请新 Channel ,结束关闭 channel )。
导入 GoVppMux plugin:
准备 VPP channel:
【 vpp-agent.git\plugins\vpp\upfplugin\upfplugin.go +172】
[VPPAgent] 在 restapi plugin 中导入 vpp plugin 中各接口的 vppcalls 。
【 vpp-agent.git\plugins\restapi\plugin_restapi.go 】
[VPPAgent] 在 restapi plugin 中定义方法处理请求信息。
VPP Agent 相关知识
注:
1、 vpp Agent Northbond : vpp 北向接口。连接外部客户端,你可以使用北向接口管理设计 VPP agent 配置。
2、 vpp Agent Southbound : vpp 南向接口。连接 vpp agent 和 vpp 数据面。 vpp 数据面事件、通知、运行 vpp 配置的转存都在南向接口。
TroubleShooting
1、编译 5GS 库的 vpp agent 有问题,报错如下:
根据报错定位 Makefile line 287 :
发现时执行 gofmt.sh 时出错,经分析,该脚本主要做格式化操作,在有的机器上(172.18.22.213)可以,有的机器上(172.18.22.211)会报错,暂时将格式化功能去掉编译不影响使用,故修改 Makefile 文件(将102行内容修改为103行内容):
修改后重新执行 make build
可以编译成功。
2、用源码编译安装 go 时,在 $GOROOT/src 目录下执行 ./all.bash
报错:
# runtime/cgo
/usr/bin/ld: -r and -pie may not be used together
collect2: error: ld returned 1 exit status
解决办法:
https://golang.org/doc/install/source
参考 “(Optional) Install a C compiler” 一节,设置环境变量 CGO_ENABLED=0
即可。
未解决的问题
重启同步问题(按顺序来、没有错误数据时可以,原因不清)
reference
1、 Add a plugin’s GO API : https://fd.io/docs/vpp/master/gettingstarted/developers/add_plugin_goapi.html
2、 Managing the VPP Agent (by etcdctl, rest api, vpp cli, agentctl ) : https://docs.ligato.io/en/latest/user-guide/quickstart/#environment-and-steps
3、 Key-Value Data Store : https://docs.ligato.io/en/latest/user-guide/concepts/#keys-and-microservice-label