# VPP-Agent对接UPF CLI的原理 本文档描述 VPP-Agent 对接 UPF CLI 的开发流程及相关基础知识。 ## VPP Agent 编译部署 1、参考:https://docs.ligato.io/en/latest/developer-guide/build-vpp-agent-no-image/ ![VppAgentAndUpfCli](../../../../_static/VppAgentAndUpfCli-1-1.png) 解决方法:不要用 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、对接架构: ![VppAgentAndUpfCli-2-1](../../../../_static/VppAgentAndUpfCli-2-1.png) 注:重点关注图中红框的三部分—— 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 方式添加) 1. 将 vpp-agent 的 config-dir 目录下的 log.conf 修改日志级别为 debug。 ```bash # file : ${config-dir}/log.conf default-level: debug ``` 使用 vppctl 查看 interface : ![VppAgentAndUpfCli-2-2](../../../../_static/VppAgentAndUpfCli-2-2.png) 2. 使用 etcdctl 请求 vpp agent ,命令如下: ```bash 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//config////` ,故 `/vnf-agent/vpp1/config` 表示使用的配置, `vpp` 是 key 所在的模块, `v2` 是模块版本信息, `interface` 是 key 的类型, loop1 是 key 的标识。 3. 查看 vpp agent 日志,看到北向接口已经收到数据: ![VppAgentAndUpfCli-2-3](../../../../_static/VppAgentAndUpfCli-2-3.png) 代码调用逻辑: ```bash ######################################## 初始化 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: # 退出 ``` 4. vpp agent 将请求转发到 plugins/orchestrator/dispatcher.go : ![VppAgentAndUpfCli-2-4](../../../../_static/VppAgentAndUpfCli-2-4.png) 代码调用逻辑: ```bash # file : plugins/orchestrator/dispatcher.go PushData(ctx, kvPairs) |_ txn := p.kvs.StartNBTransaction() # 获取北向接口的内容 |_ txn.Commit(ctx) |_ p.kvs.TransactionBarrier() # 确认通知接收 ``` 5. vpp agent 通过南向接口请求 vpp : ![VppAgentAndUpfCli-2-5](../../../../_static/VppAgentAndUpfCli-2-5.png) ![VppAgentAndUpfCli-2-6](../../../../_static/VppAgentAndUpfCli-2-6.png) 代码调用逻辑: ```bash # 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() ``` 6. 使用 vppctl 查看 interface 已经添加: ![VppAgentAndUpfCli-2-7](../../../../_static/VppAgentAndUpfCli-2-7.png) 7. vpp agent 将该条记录存储到 kvscheduler 中: ![VppAgentAndUpfCli-2-8](../../../../_static/VppAgentAndUpfCli-2-8.png) ### 3、开发流程: 1. [VPP-UPF] 在 upf.api 文件中添加数据模型; 2. [VPP-UPF] 在 upf_api.c 文件中添加 API 处理; 3. [VPP-UPF] 编译安装 upf ,在 /usr/share/vpp/api/plugins 目录中可以看到1,2步生成的 upf.api.json 文件; 4. [GoVpp] genreate-binapi 将 upf.api. json 文件生成 upf.ba.go 文件。 5. [VPPAgent] 在 vpp plugin 中导入 GoVppMux plugin 并准备好 VPP channel (初始化申请新 Channel ,结束关闭 channel )。 导入 GoVppMux plugin: ![VppAgentAndUpfCli-2-9](../../../../_static/VppAgentAndUpfCli-2-9.png) 准备 VPP channel: 【 vpp-agent.git\plugins\vpp\upfplugin\upfplugin.go +172】 6. [VPPAgent] 在 restapi plugin 中导入 vpp plugin 中各接口的 vppcalls 。 【 vpp-agent.git\plugins\restapi\plugin_restapi.go 】 7. [VPPAgent] 在 restapi plugin 中定义方法处理请求信息。 ## VPP Agent 相关知识 ![VppAgentAndUpfCli-3-1](../../../../_static/VppAgentAndUpfCli-3-1.png) 注: 1、 vpp Agent Northbond : vpp 北向接口。连接外部客户端,你可以使用北向接口管理设计 VPP agent 配置。 2、 vpp Agent Southbound : vpp 南向接口。连接 vpp agent 和 vpp 数据面。 vpp 数据面事件、通知、运行 vpp 配置的转存都在南向接口。 ## TroubleShooting 1、编译 5GS 库的 vpp agent 有问题,报错如下: ![VppAgentAndUpfCli-4-1](../../../../_static/VppAgentAndUpfCli-4-1.png) ![VppAgentAndUpfCli-4-2](../../../../_static/VppAgentAndUpfCli-4-2.png) 根据报错定位 Makefile line 287 : ![VppAgentAndUpfCli-4-3](../../../../_static/VppAgentAndUpfCli-4-3.png) 发现时执行 gofmt.sh 时出错,经分析,该脚本主要做格式化操作,在有的机器上(172.18.22.213)可以,有的机器上(172.18.22.211)会报错,暂时将格式化功能去掉编译不影响使用,故修改 Makefile 文件(将102行内容修改为103行内容): ![VppAgentAndUpfCli-4-4](../../../../_static/VppAgentAndUpfCli-4-4.png) 修改后重新执行 `make build` 可以编译成功。 2、用源码编译安装 go 时,在 $GOROOT/src 目录下执行 `./all.bash` 报错: ```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` 即可。 ## 未解决的问题 1. 重启同步问题(按顺序来、没有错误数据时可以,原因不清) ## 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