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

解决方法:不要用 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

注:重点关注图中红框的三部分—— 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。

    # file : ${config-dir}/log.conf
    default-level: debug
    

    使用 vppctl 查看 interface :

    VppAgentAndUpfCli-2-2

  2. 使用 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 的标识。

  3. 查看 vpp agent 日志,看到北向接口已经收到数据:

    VppAgentAndUpfCli-2-3

    代码调用逻辑:

    ######################################## 初始化 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

代码调用逻辑:

# file : plugins/orchestrator/dispatcher.go
PushData(ctx, kvPairs)
 |_ txn := p.kvs.StartNBTransaction() # 获取北向接口的内容
 |_ txn.Commit(ctx)
 |_ p.kvs.TransactionBarrier() # 确认通知接收
  1. vpp agent 通过南向接口请求 vpp :

VppAgentAndUpfCli-2-5

VppAgentAndUpfCli-2-6

代码调用逻辑:

# 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()
  1. 使用 vppctl 查看 interface 已经添加:

VppAgentAndUpfCli-2-7

  1. vpp agent 将该条记录存储到 kvscheduler 中:

    VppAgentAndUpfCli-2-8

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

    准备 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

注:

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

VppAgentAndUpfCli-4-2

根据报错定位 Makefile line 287 :

VppAgentAndUpfCli-4-3

发现时执行 gofmt.sh 时出错,经分析,该脚本主要做格式化操作,在有的机器上(172.18.22.213)可以,有的机器上(172.18.22.211)会报错,暂时将格式化功能去掉编译不影响使用,故修改 Makefile 文件(将102行内容修改为103行内容):

VppAgentAndUpfCli-4-4

修改后重新执行 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 即可。

未解决的问题

  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