VPP/How to Create a VPP binary control-plane API

From fd.io
< VPP
Revision as of 14:23, 12 January 2019 by Pvinci (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Intro

We need to modify four primary files to add a new binary control-plane API.

  1. vpp/api/vpe.api
  2. vpp/api/api.c
  3. vpp/api/custom_dump.c
  4. vpp/vat/api_format.c

I'll describe each one using the set interface l2 bridge command as an example.

While learning about this specific CLI command, I used cscope to search for the command, using "Find this text string" set to set interface l2 bridge which led me to the file open-repo/vnet/vnet/l2/l2_input.c where I traced its operation.


Note: In some version, the above files will be available in ~/vpp/src/vpp/api/

Ex:

ubuntu@ip-172-31-78-63:~/vpp/src/vpp/api$ ls vpe.api api.c custom_dump.c api_format.c

api.c api_format.c custom_dump.c vpe.api

ubuntu@ip-172-31-78-63:~/vpp/src/vpp/api$

Additions to vpp/api/vpe.api

The first step: determine the parameters the API will need passed to it.

Based on the CLI command "set interface l2 bridge <interface> <bridge-domain> [<split-horizon-group>] [bvi], there are two required parameters and two optional parameters.

  • <interface>: In VPE, an ASCII interface name is converted to unsigned 32-bit interface index number and from inspecting existing code, we'll name the field rx_sw_if_index
  • <bridge-domain>: this is a numeric and convention calls this field bd_id which is 32 bits
  • <split-horizon-group>: this is an optional numeric value internally specified as a 32-bit value which defaults to 0
  • bvi: is a flag to indicate this is a "Bridged Virtual Interface" which although is a simple boolean, the underlying function calls treat as an unsigned 32-bit value

In addition to API-related specifics, VPE API requests always include the fields (client_index and context) and a reply structure (the base API name with _reply appended), yielding the following text to add into vpe.api:

define sw_interface_set_l2_bridge {
   u32 client_index;
   u32 context;
   u32 rx_sw_if_index;
   u32 bd_id;
   u32 bvi;
   u32 shg;
   u8 enable;
};

define sw_interface_set_l2_bridge_reply {
   u32 context;
   i32 retval;
};

Changes to vpp/vpp/api.c

Once the communicating data structure is defined in vpe.api, two items for the API function must be added to api.c:

Within the #define foreach_vpe_api_msg portion of the file, add the new entry for the API function, in this case:

_(SW_INTERFACE_SET_L2_BRIDGE, sw_interface_set_l2_bridge) \

(making sure to properly terminate the line ending depending on if it's in the middle of the #define or at the end).

Then add the actual handler function:

static void 
vl_api_sw_interface_set_l2_bridge_t_handler (
    vl_api_sw_interface_set_l2_bridge_t *mp)
{
    /* stuff required for each API function */
    vl_api_sw_interface_set_l2_bridge_reply_t * rmp;
    int rv = 0;

    /* stuff specific to the sw_interface_set_l2_bridge API function */
    u32 rx_sw_if_index = ntohl(mp->rx_sw_if_index);
    u32 bd_id = ntohl(mp->bd_id);
    u32 bvi = ntohl(mp->bvi);
    u32 shg = ntohl(mp->shg);

    /* stuff specific to this function */

    /* stuff required for each API function */
    REPLY_MACRO(VL_API_SW_INTERFACE_SET_L2_BRIDGE_REPLY);
}   

It's probably obvious, but in the prior examples (and the ones that follow), the text SW_INTERFACE_SET_L2_BRIDGE and sw_interface_set_l2_bridge should be changed to the your specific new API function names.

Please remember to convert message structure members from network byte order to local variables in host byte order, as shown above.

This specific example uses REPLY_MACRO to return whatever is placed in the rv variable. Within this macro, (among other things) rv is carefully stuffed into rmp->.

If you need to return additional information, use the REPLY_MACRO2 macro. An example from vl_api_l2_flags:

REPLY_MACRO2(VL_API_L2_FLAGS_REPLY, rmp->resulting_feature_bitmap = ntohl(rbi));

The second argument to REPLY_MACRO2 can contain multiple statements.

Changes to vpp/api/custom_dump.c

Each API entry needs a corresponding *_print function to support debugging VPE coding and configuration issues. In short, this function prints the configuration of each API interaction via the control plane. The output format should show all the information needed to recreate what the control plane without the actual control plane setup.

This function is complemented by an "custom dump parser" (AKA api_format; described in the next section), so it's important that these items are symmetrical in terms of what custom_dump prints, api_format should be able to parse and act upon.

 static void *vl_api_sw_interface_set_l2_bridge_t_print
 (vl_api_sw_interface_set_l2_bridge_t * mp, void *handle)
 {
     /* stuff required for all API functions */
     u8 * s;
     s = format (0, "SCRIPT: sw_interface_set_l2_bridge ");
 
    /* stuff specific to new API function */
   
     /* stuff required for all API functions */
     s [vec_len(s)-1] = 0;
     vl_print (handle, (char *)s);
     vec_free(s);
     return handle;
 }

You also need to make a definition for your print handler above:

#define foreach_custom_print_function                                   \
_(SW_INTERFACE_SET_L2_BRIDGE, sw_interface_set_l2_bridge)               \

Changes to vppt/vat/api_format.c

As hinted in the custom_dump.c section, vat via the file api_format.c is complementary program to consume output dumps in order to "play back" configurations and setup established via the control plane.

To accomplish this, each API function needs to:

  1. provide a reply function
  2. parse dump text and invoke corresponding internal functions
  3. provide a help string to guide visual inspection of dump files and manual testing via vat tool.

Reply Function

The reply function is specified in two places:

#define foreach_standard_reply_retval_handler \

as

_(sw_interface_set_l2_bridge_reply) \

and in:

#define foreach_vpe_api_reply_msg \

as

_(SW_INTERFACE_SET_L2_BRIDGE_REPLY, sw_interface_set_l2_bridge_reply) \

Standard warnings are given about properly terminating #define statements.

Parsing and function invocation

There are many examples of parsing dump strings in api_format.c but most follow the pattern shown in the example function below.

Here are some things to note:

  1. the string which invokes the function is based on the original structure name listed in vpe.api so sw_interface_set_l2_bridge in the dump file trigger the function named api_sw_interface_set_l2_bridge .
  2. for each parameter it's preferred that the function to be tagged such that their order isn't fixed (this is reflected in the parsing function below using a while loop matching possible tags, then possible parameters to those tags.
static int api_sw_interface_set_l2_bridge (vat_main_t * vam)
{
    unformat_input_t * i = vam->input;
    vl_api_sw_interface_set_l2_bridge_t *mp;
    f64 timeout;
    u32 rx_sw_if_index;
    u8 rx_sw_if_index_set = 0;
    u32 bd_id;
    u8 bd_id_set = 0;
    u32 bvi = 0;
    u32 shg = 0;
    u8 enable = 1;
    
    /* Parse args required to build the message */
    while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) {
	if (unformat (i, "rx_sw_if_index %d", &rx_sw_if_index))
            rx_sw_if_index_set = 1;	
	else if (unformat (i, "bd_id %d", &bd_id))
            bd_id_set = 1;
	else if (unformat (i, "rx")) {
	    if (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) {
		if (unformat (i, "%U", unformat_sw_if_index, vam,
			      &rx_sw_if_index))
		    rx_sw_if_index_set = 1;
	    } else
		break;
	} else if (unformat (i, "shg %d", &shg)) {
		/* no specific action beyond unformat needed(?) */
	} else if (unformat (i, "bvi")) {
		bvi = 1;
	} else if (unformat (i, "enable"))
	    enable = 1;
	else if (unformat (i, "disable")) 
	    enable = 0;
	else
            break;
    }

    if (rx_sw_if_index_set == 0) {
        errmsg ("missing rx interface name or rx_sw_if_index\n");
        return -99;
    }

    if (enable && (bd_id_set == 0)) {
        errmsg ("missing bridge domain\n");
        return -99;
    }
    
    M(SW_INTERFACE_SET_L2_BRIDGE, sw_interface_set_l2_bridge);

    mp->rx_sw_if_index = ntohl(rx_sw_if_index);
    mp->bd_id = ntohl(bd_id);
    mp->bvi = ntohl(bvi);
    mp->shg = ntohl(shg);
    mp->enable = enable;

    S; W;
    /* NOTREACHED */
    return 0;
}

Help strings

Help strings are provided through the after the function specification section of api_format.c. This string shows:

  • the name of the string
  • parameter names inside angle brackets
  • optional parameters inside square brackets

As with other #define specified structures inside VPE, take care that the line endings are properly terminated.

_(sw_interface_set_l2_bridge,                                           \
  "rx <intfc> | rx_sw_if_index <id> bd_id <bridge-domain-id>\n"         \
  "[shg <split-horizon-group>] [bvi]\n"                                 \
  "enable | disable")                                                   \

Building

From clone directory:

 make bootstrap
 make build-vat

From .../build-root:

 make is_build_tool=yes vppapigen-install