REST API 启动程序:vpp-agent/cmd/vpp-agent/main.go
API 文档:https://docs.ligato.io/en/latest/api/api-vpp-agent/
启动端口为9191,暂未找到修改点
监听的socket文件为/run/vpp/api.sock,暂未找到修改点
测试vpp配置文件如下:
unix {
cli-listen /run/vpp/cli.sock
cli-no-pager
full-coredump
log /home/data/vpp/vpp.log
runtime-dir /home/data/vpp
pidfile /home/data/vpp/agentvpp1.pid
}
plugins {
plugin dpdk_plugin.so {
disable
}
}
socksvr {
socket-name /run/vpp/api.sock
}
statseg {
socket-name /run/vpp/stats.sock
per-node-counters on
}
VPP Agent 实现了两个 API:
NB API:外部客户端与 VPP Agent 进行交互的接口,客户端可以完成 VPP configuration management。
SB API:VPP Agent 与 VPP Data Plane 进行交互的接口。完成 VPP Data Plane 的 Events、Notifications 和 Configuration Dumps 操作。
通过 VPP Agent 提供的 REST plugin 我们可以完成以下两类操作:
NB configuration management
SB VPP configuration dumps
NB Configuration 用于完成对 VPP Data Plane 的查询、配置、验证等操作。
PUT /configuration?replace
GET /configuration
GET /info/configuration/jsonschema
POST /configuration/validate
PUT /configuration
例如:
cat loop-bd.yaml
vppConfig:
interfaces:
- name: loop1
type: SOFTWARE_LOOPBACK
enabled: true
ipAddresses:
- 192.168.1.1/24
bridgeDomains:
- name: bd1
forward: true
learn: true
interfaces:
- name: loop1
curl -X PUT -H "Content-Type: application/yaml" --data-binary @loop-bd.yaml http://localhost:9191/configuration
SB Dumps 仅用于获取 VPP Data Plane 的运行时配置信息。详细见 API 文档。
VPP Agent REST Plugin 还支持通过 REST API 来执行 VPP CLI 指令,例如:
curl -X POST 'http://localhost:9191/vpp/command?Content-Type=application/json' \
-H 'Content-Type: application/json' \
-d '{"vppclicommand":"show version"}'
"vpp v21.10.1-release built by root on 4f6ead0c141f at 2021-11-17T14:25:30\n"
vpp-agent/proto/ligato
Vpp插件相关功能的proto文件存放在vpp-agent/proto/ligato/vpp
Linux插件相关功能的proto文件存放在vpp-agent/proto/ligato/linux
url文件:vpp-agent/plugins/restapi/resturl/urls.go
处理url的代码:vpp-agent/plugins/restapi/resturl/handlers.go
vpp-agent/plugins/vpp/binapi/
使用2110版本的vpp调用的接口是2106的接口
定义url:vpp-agent/plugins/restapi/resturl/urls.go
const (
...
// zhkj开发
ZHKJ_CreateLoopback = "/zhkj/vpp/v2/interafces/loopback"
)
注册url:vpp-agent/plugins/restapi/resturl/plugin_restapi.go
// Fill index item lists
func getIndexPageItems() map[string][]indexItem {
idxMap := map[string][]indexItem{
...
"Interface plugin": {
...
// zhkj开发
{Name: "zhkj create loopback interface", Path: resturl.ZHKJ_CreateLoopback},
},
...
}
return idxMap
}
// Create permission groups (tracer, telemetry, dump - optionally add more in the future). Used only if
// REST security is enabled in plugin
func getPermissionsGroups() []*access.PermissionGroup {
...
dumpPg := &access.PermissionGroup{
Name: "dump",
Permissions: []*access.PermissionGroup_Permissions{
...
// zhkj 开发
newPermission(resturl.ZHKJ_CreateLoopback, POST),
},
}
return []*access.PermissionGroup{infoPg, tracerPg, telemetryPg, dumpPg,
nbConfigValidationPg, nbConfigReadPg, nbConfigWritePg}
}
url调用处理(测试):vpp-agent/plugins/restapi/resturl/handlers.go
// Registers interface REST handlers
func (p *Plugin) registerInterfaceHandlers() {
...
// zhkj 开发 POST create loopback interface
p.registerHTTPHandler(resturl.ZHKJ_CreateLoopback, POST, func() (interface{}, error) {
return "zhkj gogogogo", nil
})
}
调用测试一:
lxj@lxj:~/vpp$ curl -X POST http://localhost:9191/zhkj/vpp/v2/interafces/loopback
"zhkj gogogogo"
url调用处理:vpp-agent/plugins/restapi/resturl/handlers.go
// Registers interface REST handlers
func (p *Plugin) registerInterfaceHandlers() {
...
// zhkj 开发 POST create loopback interface
p.registerHTTPHandler(resturl.ZHKJ_CreateLoopback, POST, func() (interface{}, error) {
return "success", p.ifHandler.CreateLoopbackByZhkj(context.TODO())
})
}
在接口中声明方法:vpp-agent/plugins/vpp/ifplugin/vppcalls/interface_hander_api.go
type InterfaceVppRead interface {
...
// zhkj 开发 创建loopback
CreateLoopbackByZhkj(ctx context.Context) error
}
在所有版本下实现方法:vpp-agent/plugins/vpp/ifplugin/vppcalls/vpp2106/dump_interface_vppcalls.go
// zhkj 开发
func (h *InterfaceVppHandler) CreateLoopbackByZhkj(ctx context.Context) error {
// Dump all
fmt.Println("2106 CreateLoopback By Zhkj")
// 一条消息的
reqCtx := h.callsChannel.SendRequest(&vpp_ifs.CreateLoopback{})
reply := &vpp_ifs.CreateLoopbackReply{}
fmt.Println(reply, reply.Retval, reply.SwIfIndex)
err := reqCtx.ReceiveReply(reply)
if err != nil {
return err
}
// 多条消息的
// reqCtx := h.callsChannel.SendMultiRequest(&vpp_ifs.CreateLoopback{})
// for {
// reply := &vpp_ifs.CreateLoopbackReply{}
// over, err := reqCtx.ReceiveReply(reply)
// fmt.Println(reply, reply.Retval, reply.SwIfIndex)
// if over {
// break
// }
// if err != nil {
// return err
// }
// }
return nil
}
如果之后确定是哪个版本,可以将其他版本删了
vpp-agent/plugins/kvscheduler/*.go
txn_process的processTransaction
方法
processTransaction 分 6 步处理事务:
1. 预处理:事务参数被初始化,重试操作从过时的操作中过滤出来,并且为了重新同步,图形被刷新
2. 排序:使用启发式的预排序操作以获得最短的平均图游走
3. 模拟:模拟事务而不实际执行任何创建/删除/更新操作以获得“执行计划”
4. 预记录:在执行前记录事务参数+计划,以保存一些信息,以防执行过程中发生崩溃
5. 执行:执行事务,收集错误
6. Recording:记录最终确定的事务(log + in-memory)
7. 后处理:为失败的操作安排重试,将值状态更新传播给订阅者,并将error/nil返回给阻塞提交的调用者
8. 交易统计更新
执行过程,以以下yaml为例
vppConfig:
interfaces:
- name: "loop1"
type: SOFTWARE_LOOPBACK
enabled: true
ipAddresses:
- 192.168.1.1/24
bridgeDomains:
- name: bd1
forward: true
learn: true
interfaces:
- name: loop1
以上yaml数据将会先转为json再转为kv结构
- key: config/vpp/l2/v2/bridge-domain/bd1
val: { name:"bd1" forward:true learn:true interfaces:{name:"loop1"} }
- key: config/vpp/v2/interfaces/loop1
val: { name:"loop1" type:SOFTWARE_LOOPBACK enabled:true ip_addresses:"192.168.1.1/24" }
其具体执行过程为:
[BEGIN] execute transaction (seqNum=1)
[BEGIN] applyValue (key = config/vpp/l2/v2/bridge-domain/bd1)
[BEGIN] applyCreate (key = config/vpp/l2/v2/bridge-domain/bd1)
-> change value state from NONEXISTENT to CONFIGURED
[BEGIN] runDepUpdates (key = config/vpp/l2/v2/bridge-domain/bd1)
[END] runDepUpdates (key = config/vpp/l2/v2/bridge-domain/bd1)
[BEGIN] applyDerived (key = config/vpp/l2/v2/bridge-domain/bd1)
[BEGIN] applyValue (key = vpp/bd/bd1/interface/loop1)
[BEGIN] applyCreate (key = vpp/bd/bd1/interface/loop1)
-> change value state from NONEXISTENT to PENDING
[END] applyCreate (key = vpp/bd/bd1/interface/loop1)
[END] applyValue (key = vpp/bd/bd1/interface/loop1)
[END] applyDerived (key = config/vpp/l2/v2/bridge-domain/bd1)
[END] applyCreate (key = config/vpp/l2/v2/bridge-domain/bd1)
[END] applyValue (key = config/vpp/l2/v2/bridge-domain/bd1)
[BEGIN] applyValue (key = config/vpp/v2/interfaces/loop1)
[BEGIN] applyCreate (key = config/vpp/v2/interfaces/loop1)
-> change value state from NONEXISTENT to CONFIGURED
[BEGIN] runDepUpdates (key = config/vpp/v2/interfaces/loop1)
[BEGIN] applyValue (key = vpp/bd/bd1/interface/loop1)
[BEGIN] applyCreate (key = vpp/bd/bd1/interface/loop1)
-> change value state from PENDING to CONFIGURED
[BEGIN] runDepUpdates (key = vpp/bd/bd1/interface/loop1)
[END] runDepUpdates (key = vpp/bd/bd1/interface/loop1)
[END] applyCreate (key = vpp/bd/bd1/interface/loop1)
[END] applyValue (key = vpp/bd/bd1/interface/loop1)
[END] runDepUpdates (key = config/vpp/v2/interfaces/loop1)
[BEGIN] applyDerived (key = config/vpp/v2/interfaces/loop1)
[BEGIN] applyValue (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[BEGIN] applyCreate (key = vpp/interface/loop1/address/static/192.168.1.1/24)
-> change value state from NONEXISTENT to PENDING
[END] applyCreate (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[END] applyValue (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[BEGIN] applyValue (key = vpp/interface/loop1/has-IP-address)
[BEGIN] applyCreate (key = vpp/interface/loop1/has-IP-address)
-> change value state from NONEXISTENT to PENDING
[END] applyCreate (key = vpp/interface/loop1/has-IP-address)
[END] applyValue (key = vpp/interface/loop1/has-IP-address)
[BEGIN] applyValue (key = vpp/interface/loop1/vrf/0/ip-version/v4)
[BEGIN] applyCreate (key = vpp/interface/loop1/vrf/0/ip-version/v4)
-> change value state from NONEXISTENT to CONFIGURED
[BEGIN] runDepUpdates (key = vpp/interface/loop1/vrf/0/ip-version/v4)
[BEGIN] applyValue (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[BEGIN] applyCreate (key = vpp/interface/loop1/address/static/192.168.1.1/24)
-> change value state from PENDING to CONFIGURED
[BEGIN] runDepUpdates (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[BEGIN] applyValue (key = vpp/interface/loop1/has-IP-address)
[BEGIN] applyCreate (key = vpp/interface/loop1/has-IP-address)
-> change value state from PENDING to CONFIGURED
[BEGIN] runDepUpdates (key = vpp/interface/loop1/has-IP-address)
[END] runDepUpdates (key = vpp/interface/loop1/has-IP-address)
[END] applyCreate (key = vpp/interface/loop1/has-IP-address)
[END] applyValue (key = vpp/interface/loop1/has-IP-address)
[END] runDepUpdates (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[END] applyCreate (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[END] applyValue (key = vpp/interface/loop1/address/static/192.168.1.1/24)
[END] runDepUpdates (key = vpp/interface/loop1/vrf/0/ip-version/v4)
[END] applyCreate (key = vpp/interface/loop1/vrf/0/ip-version/v4)
[END] applyValue (key = vpp/interface/loop1/vrf/0/ip-version/v4)
[END] applyDerived (key = config/vpp/v2/interfaces/loop1)
[END] applyCreate (key = config/vpp/v2/interfaces/loop1)
[END] applyValue (key = config/vpp/v2/interfaces/loop1)
[END] execute transaction (seqNum=1)
具体步骤为:
1. CREATE:
- key: config/vpp/l2/v2/bridge-domain/bd1
- value: { name:"bd1" forward:true learn:true interfaces:{name:"loop1"} }
2. CREATE:
- key: config/vpp/v2/interfaces/loop1
- value: { name:"loop1" type:SOFTWARE_LOOPBACK enabled:true ip_addresses:"192.168.1.1/24" }
3. CREATE [DERIVED]:
- key: vpp/bd/bd1/interface/loop1
- value: { name:"loop1" }
4. CREATE [DERIVED]:
- key: vpp/interface/loop1/vrf/0/ip-version/v4
- value:
5. CREATE [DERIVED]:
- key: vpp/interface/loop1/address/static/192.168.1.1/24
- value:
6. CREATE [DERIVED]:
- key: vpp/interface/loop1/has-IP-address
- value:
以第一个创建config/vpp/l2/v2/bridge-domain/bd1
为例,执行调用函数过程为:
PUT /configuration
Plugin.configurationUpdateHandler (将yaml数据转成json之后,根据json中字段转成k-v结构)
Plugin.dispatcher.PushData
txn.Commit (将配置提交到事务队列中)
preRecordTransaction (从事务队列中拿出执行)
Scheduler.executeTransaction
Scheduler.applyValue
Scheduler.applyCreate
Scheduler.registry.GetDescriptorForKey (key是config/vpp/l2/v2/bridge-domain/bd1,获取到的desriptor名字是 vpp-bridge-domain,本方法在plugins/kvsheduler/internal/registry/registry_impl.go中)
descriptorHandler.create
BridgeDomainDescriptor.Create
BridgeDomainVppHandler.AddBridgeDomain
BridgeDomainVppHandler.callsChannel.SendRequest(req).ReceiveReply(reply) (实际也是调用GoVpp)