VPP API機制分析(上)

VPP除了使用命令行進行配置外,還可使用API進行配置。VPP不只支持c語言的API,還支持python,java,lua等高級語言的API,很是適合自動化部署。java

API簡單使用案例

VPP提供了一個VAT客戶端程序,用於進行簡單的API測試。可執行文件位於:node

  • debug版本:vpp源碼路徑/build-root/build-vpp_debug-native/vpp/bin/vpp_api_test
  • release版本:vpp源碼路徑/build-root/build-vpp-native/vpp/bin/vpp_api_test

啓動vpp與vat程序

sudo systemctl start vpp
cd vpp源碼路徑/build-root/build-vpp-native/vpp/bin/
sudo vpp_api_test
load_one_plugin:68: Loaded plugin: /usr/lib/x86_64-linux-gnu/vpp_api_test_plugins/memif_test_plugin.so
......
load_one_plugin:68: Loaded plugin: /usr/lib/x86_64-linux-gnu/vpp_api_test_plugins/mactime_test_plugin.so
vat# 
vat#

簡單使用

  • [ ] help命令列出全部的命令
vat# 
vat# help
Help is available for the following:
acl_add_replace
......
  • [ ] help + 具體命令 列出指定命令的幫助信息
vat# help acl_del
usage: acl_del <acl-idx>
vat#
  • [ ] quit 或者 q 命令退出vat
vat# quit
admin@ubuntu:~/vpp/build-root/build-vpp-native/vpp/bin$
  • [ ] show_version 顯示VPP版本
vat# show_version
        program: vpe
        version: 19.08-rc0~65-g3b62e29c3
     build date: Sat Apr 20 13:38:27 CST 2019
build directory: /home/admin/vpp
vat#

VPP API交互過程

vpp的api是CS模型。VPP是服務器端,監聽客戶端鏈接請求,處理客戶端發送的api請求,返回應答。以下圖所示:python

clipboard.png

如上圖所示,客戶端VAT向VPP請求鏈接,VPP返回新鏈接。鏈接創建以後,VAT發送API請求,VPP處理請求返回應答。VPP支持unix套接字或者inet套接字。VPP支持多個客戶端同時進行請求。VPP支持使用共享內存進行數據交換,當客戶端和服務器端都在同一個節點上的時候,能夠選擇使用共享內存進行message交換(3,和4交互過程能夠選擇共享內存交換也能夠選擇套接字交換),提升通訊速率。linux

VPP API編寫說明

VPP API命名規則

vpp一共支持三種類型的API:shell

  • [ ] request/reply

    這種類型的規則,客戶端發送一個請求,VPP發送一個應答。應答消息命名爲:method_name + _reply。ubuntu

  • [ ] Dump/Detail

    客戶端發送一個bulk請求,VPP發送多個details消息。一個dump/detail請求最終會以一個"control ping block "結束。請求方法命名規則爲: method + _dump。 應答方法命名規則爲method + _details。該類型消息主要用來請求一個表消息,好比FIB表包含多個表項,可使用一個該消息獲取全部的FIB表項信息。api

  • [ ] Events

    客戶端能夠經過該類型API向服務器註冊一些獲取異步消息通知。好比獲取interface的狀態變化消息,週期性統計消息等。這類API一般以"want_"字段做爲前綴,好比:"want_interface_events"。服務器

客戶端發送的消息會包含一個'client_index'字段,該字段對服務器端是透明的,至關於一個‘cookie’,用於區分不一樣的客戶端。cookie

  • [ ] 命名網絡

    • Reply/Request 方法. 請求: 名字 應答: 名字+_reply
    • Dump/Detail 方法. 請求: 名字+_dump Reply: 名字+_details
    • Event 註冊方法: 請求: want_+名字 Reply: want_+名字+_reply

VPP API編寫步驟(這部份內容來自於網絡)

注:這部份內容來自於網絡 [](https://blog.51cto.com/zhangc...

添加一個新的二進制控制層的API,涉及兩部份內容,一部分是客戶端,另外一個就是服務器端。咱們以acl插件的api爲例進行說明。

添加一個api須要修改三個文件。代碼路徑是vpp/src/plugins/acl下
acl.api    --  vat 與vpp 通訊的結構體定義
acl_test.c  --  vat使用(客戶端)
acl.c    --  vpp使用(服務器端)

1.acl.api

acl.api中定義vat與vpp通訊的結構體,而後由vppapigen文件處理,最終生成acl.api.h的頭文件。兩邊都包含這個頭文件,這樣vat與vpp就使用了相同的結構體通訊了。 咱們看一下acl.api中的定義:

/** \brief Delete an ACL
    @param client_index - opaque cookie to identify the sender
    @param context - sender context, to match reply w/ request
    @param acl_index - ACL index to delete
*/

autoreply manual_print define acl_del
{
  u32 client_index;  //系統使用
  u32 context;       //系統使用
  u32 acl_index;     //經過命令acl_del <acl-idx>輸入的acl-idx
};

這個結構體的定義由3個關鍵字(autoreply 、manual_print、define )加上名稱再加成員構成,最終會被轉化爲:

typedef VL_API_PACKED(struct _vl_api_acl_del {
    u16 _vl_msg_id;      
    u32 client_index;
    u32 context;
    u32 acl_index;
}) vl_api_acl_del_t;

typedef VL_API_PACKED(struct _vl_api_acl_del_reply {
    u16 _vl_msg_id;
    u32 context;
    i32 retval;
}) vl_api_acl_del_reply_t;

這樣就能夠用使用vl_api_acl_del_t與vl_api_acl_del_reply這個結構體通訊了。
具體說一下每一個部分:

關鍵字分析

  • 關鍵字 autoreply
    在這裏須要先提一下reply
    正常狀況下,vat發送一個請求消息,而後等待一個reply消息。因此xxx_reply_t結構是不可少的,能夠本身寫,也可自動生成。
    而這個關鍵字表示了自動生成xxx_reply_t結構體,可是自動生成的結構體只有默認的參數_vl_msg_id,context,retval。如上所示vl_api_acl_del_reply_t。
    這個轉換的函數實現以下。
void autoreply (void *np_arg)
{
static u8 *s;
node_t *np = (node_t *)np_arg;
int i;

vec_reset_length (s);

s = format (0, " define %s_reply\n", (char *)(np->data[0]));
s = format (s, "{\n");
s = format (s, "    u32 context;\n");
s = format (s, "    i32 retval;\n");
s = format (s, "};\n");

for (i = 0; i < vec_len (s); i++)
    clib_fifo_add1 (push_input_fifo, s[i]);
}
  • 關鍵字manual_print
    xxx_print函數是用來打印消息結構體內容的。默認狀況下會自動生成。若是你想本身來實現,就須要加入這個關鍵字,而後在模塊路徑下的manual_fns.h中實現。
static inline void *
vl_api_acl_del_t_print (vl_api_macip_acl_del_t * a, void *handle)
{
  u8 *s;

  s = format (0, "SCRIPT: acl_del %d ",
              clib_host_to_net_u32 (a->acl_index));

  PRINT_S;
  return handle;
}
  • 關鍵字define
    define 關鍵字是轉化的關鍵。每一個定義都要加上。
  • 其餘關鍵字
    還有一些其餘的關鍵字,你們對比一下定義與生成的結果也基本都能看出來,在這裏就不贅述了。

2.acl_test.c

這個文件是vat使用。
有三件事要作,1.寫cli的help 2.寫函數 3.函數加載

2.1寫cli的help

#define foreach_vpe_api_msg \
_(acl_del, "<acl-idx>")

2.2寫函數

咱們須要寫兩個函數
api_acl_del與vl_api_acl_del_reply_t_handler
這兩個函數是配合使用的,來一個一個看

  • api_acl_del
    這個函數是與cli直接關聯,命令輸入後就調用的就是這個函數.
static int api_acl_del (vat_main_t * vam)
{
    unformat_input_t * i = vam->input;
    //這個結構體就是在acl.api中定義的消息傳遞結構體
    vl_api_acl_del_t * mp; 
    u32 acl_index = ~0;
    int ret;
    
    //解析字符串,跟vpp的命令行解析同樣
    if (!unformat (i, "%d", &acl_index)) {
      errmsg ("missing acl index\n");
      return -99;
    }

    //給mp分配內存,而後填寫要傳遞的值
    /* Construct the API message */
    M(ACL_DEL, mp);
    mp->acl_index = ntohl(acl_index);

    /* send it... */
    S(mp);

    /* Wait for a reply... */
    W (ret);
    return ret;
}

在這裏把這幾個宏的實現也貼一下。對應一下,就能看明白了。

/* M: construct, but don't yet send a message */
#define M(T, mp)                                                \
do {                                                            \
    vam->result_ready = 0;                                      \
    mp = vl_msg_api_alloc_as_if_client(sizeof(*mp));            \
    memset (mp, 0, sizeof (*mp));                               \
    mp->_vl_msg_id = ntohs (VL_API_##T+__plugin_msg_base);      \
    mp->client_index = vam->my_client_index;                    \
} while(0);

/* S: send a message */
#define S(mp) (vl_msg_api_send_shmem (vam->vl_input_queue, (u8 *)&mp))

/* W: wait for results, with timeout */
#define W(ret)                  \
do {                                            \
    f64 timeout = vat_time_now (vam) + 1.0;     \
    ret = -99;                                  \
                                                \
    while (vat_time_now (vam) < timeout) {      \
        if (vam->result_ready == 1) {           \
            ret = vam->retval;                  \
            break;                              \
        }                                       \
        vat_suspend (vam->vlib_main, 1e-5);     \
    }                                           \
} while(0);
  • vl_api_acl_del_reply_t_handler
    這個函數是在vpp回覆消息後,clinet接收回應的函數。
    因爲大多數都同樣,就直接用宏來實現了。
#define foreach_standard_reply_retval_handler   \
_(acl_del_reply) 

#define _(n)                                            \
    static void vl_api_##n##_t_handler                  \
    (vl_api_##n##_t * mp)                               \
    {                                                   \
        vat_main_t * vam = acl_test_main.vat_main;   \
        i32 retval = ntohl(mp->retval);                 \
        if (vam->async_mode) {                          \
            vam->async_errors += (retval < 0);          \
        } else {                                        \
            vam->retval = retval;                       \
            vam->result_ready = 1;                      \
        }                                               \
    }
foreach_standard_reply_retval_handler;
#undef _

註上面這個宏只是定義多個xxx_reply_retval_handler函數

  • api_acl_del和acl_del_reply函數的關係
    這兩個函數是在不一樣的線程,在宏W中,是在等待result_ready被置位;而result_ready 就是在acl_del_reply中被置位的。

    從下面信息能夠看出VAT有兩個線程:

admin@ubuntu:~/vpp$ pstree -p `pidof vpp_api_test`
vpp_api_test(25810)───{vpp_api_test}(25811)
admin@ubuntu:~/vpp$

也能夠從以下的gdb信息能夠看出vat有一個線程進行應答消息的處理。

(gdb) info thread
  Id   Target Id         Frame 
  1    Thread 0x7f3bb05d2b80 (LWP 25810) "vpp_api_test" vat_time_now (
    vam=vam@entry=0x5575ebcdc800 <vat_main>)
    at /home/jd/vpp/src/vat/api_format.c:141
* 2    Thread 0x7f3ba6d09700 (LWP 25811) "vpp_api_test" 0x00007f3ba5cec470 in vl_api_acl_del_reply_t_handler (mp=0x13004dee8)
    at /home/jd/vpp/src/plugins/acl/acl_test.c:91
(gdb) bt
#0  0x00007f3ba5cec470 in vl_api_acl_del_reply_t_handler (mp=0x13004dee8)
    at /home/jd/vpp/src/plugins/acl/acl_test.c:91
#1  0x00007f3bb01c2251 in msg_handler_internal (free_it=1, do_it=1, 
    trace_it=<optimized out>, the_msg=0x13004dee8, 
    am=0x7f3bb03ca420 <api_main>)
    at /home/jd/vpp/src/vlibapi/api_shared.c:425
#2  vl_msg_api_handler (the_msg=0x13004dee8)
    at /home/jd/vpp/src/vlibapi/api_shared.c:559
#3  0x00007f3bb01c321a in vl_msg_api_queue_handler (
    q=q@entry=0x1301c69c0) at /home/jd/vpp/src/vlibapi/api_shared.c:770
#4  0x00007f3bb01bc07e in rx_thread_fn (arg=<optimized out>)
    at /home/jd/vpp/src/vlibmemory/memory_client.c:94
#5  0x00007f3baf6b86db in start_thread (arg=0x7f3ba6d09700)
    at pthread_create.c:463
#6  0x00007f3baf3e188f in clone ()
    at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95
(gdb)

2.3加載函數

須要把寫的兩個函數掛載上。只須要在對應的宏下按格式寫就行了。

2.3.1 在宏中添加定義

  • api_acl_del

    在foreach_vpe_api_msg宏下定義
    其實這個在「2.1寫cli的help」中已經寫過,就不用再寫了

/*
 * List of messages that the api test plugin sends,
 * and that the data plane plugin processes
 */
#define foreach_vpe_api_msg 
_(acl_del, "<acl-idx>") \
  • vl_api_acl_del_reply_t_handler

    在foreach_vpe_api_reply_msg宏下定義

/*
  * Table of message reply handlers, must include boilerplate handlers
  * we just generated
  */
  #define foreach_vpe_api_reply_msg                                       \
  _(ACL_DEL_REPLY, acl_del_reply)

2.3.2 函數掛載

上一節提到的宏,都是在acl_vat_api_hookup這個函數中使用的,咱們不須要作任何修改。

static
void acl_vat_api_hookup (vat_main_t *vam)
{
    acl_test_main_t * sm = &acl_test_main;
    /* Hook up handlers for replies from the data plane plug-in */
#define _(N,n)                                                  \
    vl_msg_api_set_handlers((VL_API_##N + sm->msg_id_base),     \
                           #n,                                  \
                           vl_api_##n##_t_handler,              \
                           vl_noop_handler,                     \
                           vl_api_##n##_t_endian,               \
                           vl_api_##n##_t_print,                \
                           sizeof(vl_api_##n##_t), 1);
    foreach_vpe_api_reply_msg;
#undef _

    /* API messages we can send */
#define _(n,h) hash_set_mem (vam->function_by_name, #n, api_##n);
    foreach_vpe_api_msg;
#undef _

    /* Help strings */
#define _(n,h) hash_set_mem (vam->help_by_name, #n, h);
    foreach_vpe_api_msg;
#undef _
}

3.acl.c

這個文件是vpp使用,它用來接收vat(vpp-api-test)發送的消息,而後處理,最後迴應給vat。
咱們須要寫對應的函數,而後掛上就能夠了

  • 寫宏
    只須要在這個宏裏,把函數添加進去便可
/* List of message types that this plugin understands */

#define foreach_acl_plugin_api_msg      \
_(ACL_DEL, acl_del)
  • 寫函數
    這裏的函數也得遵循格式,vl_api_xxx_t_handler,函數以下所示
static void
vl_api_acl_del_t_handler (vl_api_acl_del_t * mp)
{
  acl_main_t *am = &acl_main;
  //這個結構體就是在acl.api中定義的消息應答傳遞結構體,用於給VAT發送應答消息
  vl_api_acl_del_reply_t *rmp;
  int rv;

  //mp中就是VAT發送來的結構體,咱們能夠從中取得配置的acl_index使用。而後調用相應的處理函數。
  rv = acl_del_list (ntohl (mp->acl_index));

  //這裏是消息處理完畢後的應答消息,VAT會在那裏等待迴應。也是經過共享內存的方式來通訊。
  //若是須要在迴應消息裏傳遞參數,可使用另外一個宏 ---  REPLY_MACRO2
  REPLY_MACRO (VL_API_ACL_DEL_REPLY);
}
  • 掛鉤子
    使用定義的宏來掛載函數。這裏也不用作任何的改變。
/* Set up the API message handling tables */
static clib_error_t *
acl_plugin_api_hookup (vlib_main_t * vm)
{
acl_main_t *am = &acl_main;
#define _(N,n)                                                  \
vl_msg_api_set_handlers((VL_API_##N + am->msg_id_base),     \
                       #n,                  \
                       vl_api_##n##_t_handler,              \
                       vl_noop_handler,                     \
                       vl_api_##n##_t_endian,               \
                       vl_api_##n##_t_print,                \
                       sizeof(vl_api_##n##_t), 1);
foreach_acl_plugin_api_msg;
#undef _

return 0;
}

參考連接

[](https://wiki.fd.io/view/VPP/A...

[](https://wiki.fd.io/view/VPP/H...

相關文章
相關標籤/搜索