Difference between revisions of "VPP/Python API"

From fd.io
< VPP
Jump to: navigation, search
(Step by Step (Ubuntu 14.04))
(Step by Step (Ubuntu 14.04))
Line 66: Line 66:
  
 
   '''export LD_LIBRARY_PATH=/home/ubuntu/vpp/build-root/install-vpp_debug-native/vpp-api/lib64/'''
 
   '''export LD_LIBRARY_PATH=/home/ubuntu/vpp/build-root/install-vpp_debug-native/vpp-api/lib64/'''
 +
 +
Or, in a more automatic fashion:
 +
 +
  '''export LD_LIBRARY_PATH=`find . -name 'libpneum.so' -exec dirname {} \; | grep lib64 | head -n 1`'''
 +
  
  

Revision as of 20:39, 12 October 2016

Python binding for the VPP API

The vpp-papi module in vpp-api/python/ provides a Python binding to the VPP API. The Python bindings to the API is auto-generated. The main API definition is in vpp/vpp-api/vpe.api. Plugins and other components may provide their own API definitions, and those are distributed as separate Python packages.

Currently there are three classes of VPP API methods:

  1. Simple request / reply. For example the show_version() call the SHOW_VERSION message is the request and the SHOW_VERSION_REPLY is the answer back. By convention replies are named ending with _REPLY.
  2. Dump functions. For example sw_interface_dump() send the SW_INTERFACE_DUMP message and receive a set of messages back. In this example SW_INTERFACE_DETAILS and SW_INTERFACE_SET_FLAGS are (typically) received. The CONTROL_PING/CONTROL_PING_REPLY is used as a method to signal to the client that the last message has been received. By convention the request message have names ending with _DUMP and the replies have names ending in _DETAILS.
  3. Register for events. For example want_stats() sends a WANT_STATS message, get a WANT_STATS_REPLY message back, and the client will then asynchronously receive VNET_INTERFACE_COUNTERS messages.

The API is by default blocking although there is possible to get asynchronous behaviour by setting the function argument async=True.

Each call uses the arguments as specified in the API definitions file (e.g. vpe.api). The "client_index" and "context" fields are handled by the module itself. A call returns a named tuple or a list of named tuples.

Installation

The main VPP build will build the C library (libpneum.so). If VPP is installed via the Linux package system that library will be available on the system. To run within a build directory, set LD_LIBRARY_PATH to point to the location of libpneum.so.

To build the VPP_PAPI Python package (and shared library):

make build-vpp-api
 

The Python package can either be installed in the Python system directory or in a Virtualenv environment. To install the Python component:

cd vpp-api/python
python setup.py install
 

If you run from the development directory in a virtualenv environment, you might have to set LD_LIBRARY_PATH to e.g. build-root/install-vpp_debug-native/vpp-api/lib64/

The Python package can also be installed from the vpp-python-api RPM. It is then installed in the default Python library directory.

Step by Step (Ubuntu 14.04)

These steps assume you have a checked out VPP tree at ~/vpp and that you have successfully got to the stage of "make build" succeeding

They also assume you are logged in as a user "ubuntu" with the home directory "/home/ubuntu".

First install the virtualenv package:

 sudo apt-get install python-virtualenv

Now change to the VPP directory and create the virtualenv there:

 cd ~/vpp
 virtualenv virtualenv

Now make the API:

 make build-vpp-api

Install the ipaddress package (used by some of the scripts)

 virtualenv/bin/pip install ipaddress

Now install the python API into the virtualenv

 pushd vpp-api/python/
 ~/vpp/virtualenv/bin/python setup.py install
 popd

Now set the LD_LIBRARY_PATH such that it points to the directory containing libpneum.so:

 export LD_LIBRARY_PATH=/home/ubuntu/vpp/build-root/install-vpp_debug-native/vpp-api/lib64/

Or, in a more automatic fashion:

 export LD_LIBRARY_PATH=`find . -name 'libpneum.so' -exec dirname {} \; | grep lib64 | head -n 1`


In another window, edit a file ~/vpp/startup.conf such that it looks like follows:

 unix { interactive exec /home/ubuntu/vpp/vpp.cmd cli-listen 127.0.0.1:5002 }  api-segment { uid ubuntu gid ubuntu }
 dpdk {
   no-pci
   vdev eth_af_packet0,iface=eth0
 }

The critical part above is "api-segment" part which configures the uid/gid so that your API client did not need the sudo privileges.

Make sure to export the environment variable with the path:

 export STARTUP_CONF=startup.conf

Now touch the ~/vpp/vpp.cmd, later on you can put some VPP startup commands there

 touch /home/ubuntu/vpp/vpp.cmd

now in this window you should be able to issue "make run" and get to a running instance of VPP.

 make run


Go back the "python window"

Install scapy

 virtualenv/bin/pip install scapy

Launch scapy to get the tab completion and other CLI niceties:

 virtualenv/bin/scapy

The next actions are inside the scapy prompt:

 >>> import vpp_papi

Connect to VPP

 >>> vpp_papi.connect("foo")
 0L
 >>>

Issue a "show version" request

 >>> vpp_papi.show_version()
 show_version_reply(vl_msg_id=164, context=1, retval=0, program='vpe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', version='16.12-rc0~208-g5899fde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', build_date='Wed Oct 12 19:49:12 UTC 2016\x00\x00\x00\x00', build_directory='/home/ubuntu/vpp\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
  >>>


Disconnect from VPP

 >>> vpp_papi.disconnect()
 0L
 >>>

NB: currently, if you forget to connect() first, the python prompt will segfault. So, don't forget to connect! :)


Now you can run a script using Python API:

  ubuntu@vpp-tdd:~/vpp$ virtualenv/bin/python vpp-api/python/tests/test_papi.py
  13
  VPP BIN: /home/ubuntu/vpp/vpp-api/python/tests/../../../build-root/install-vpp_debug-native/vpp/bin/vpp
  Started VPP
  Connecting API
  Summary stats vnet_summary_stats_reply(vl_msg_id=55, context=1, retval=0, total_pkts=(0, 0), total_bytes=(0, 0), vector_rate=0.0)
  Packets: 0
  Packets: 0
  .Connecting API
  Dump/details T [sw_interface_details(vl_msg_id=12, context=2, sw_if_index=0, sup_sw_if_index=0, l2_address_length=0, l2_address='\x00\x00\x00\x00\x00\x00\x00\x00',    interface_name='local0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', admin_up_down=0, link_up_down=0, link_duplex=0, link_speed=0, link_mtu=0, sub_id=0, sub_dot1ad=0, sub_number_of_tags=0, sub_outer_vlan_id=0, sub_inner_vlan_id=0, sub_exact_match=0, sub_default=0, sub_outer_vlan_id_any=0, sub_inner_vlan_id_any=0, vtr_op=0, vtr_push_dot1q=0, vtr_tag1=0, vtr_tag2=0), sw_interface_details(vl_msg_id=12, context=2, sw_if_index=1, sup_sw_if_index=1, l2_address_length=6, l2_address='\x02\xfe\x03\xc99d\x00\x00', interface_name='af_packet0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', admin_up_down=0, link_up_down=0, link_duplex=2, link_speed=8, link_mtu=9216, sub_id=0, sub_dot1ad=0, sub_number_of_tags=0, sub_outer_vlan_id=0, sub_inner_vlan_id=0, sub_exact_match=0, sub_default=0, sub_outer_vlan_id_any=0, sub_inner_vlan_id_any=0, vtr_op=0, vtr_push_dot1q=0, vtr_tag1=0, vtr_tag2=0)]
  .Connecting API
  T show_version_reply(vl_msg_id=164, context=3, retval=0, program='vpe\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', version='16.12-rc0~208-g5899fde\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', build_date='Wed Oct 12 19:49:12 UTC 2016\x00\x00\x00\x00', build_directory='/home/ubuntu/vpp\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
  .Connecting API
  create_loopback_reply(vl_msg_id=80, context=4, retval=0, sw_if_index=2)
  sw_interface_add_del_address_reply(vl_msg_id=17, context=5, retval=0)
  want_stats_reply(vl_msg_id=50, context=7, retval=0)
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=0, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=1, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=2, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=3, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=4, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=5, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=6, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=7, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=8, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=0, is_combined=1, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=1, is_combined=1, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=0, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=1, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=2, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=3, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=4, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=5, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=6, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=7, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=8, is_combined=0, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=0, is_combined=1, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  Interface counters vnet_interface_counters(vl_msg_id=51, vnet_counter_type=1, is_combined=1, first_sw_if_index=0, count=3, data=('\x00\x00\x00',))
  IPv6 FIB counters vnet_ip6_fib_counters(vl_msg_id=53, vrf_id=0, count=2, c=[(18375249429625044992L, 1, 128, 1, 72), (18375249429625044992L, 22, 128, 1, 156)])
  want_stats_reply(vl_msg_id=50, context=8, retval=0)
  .
  ----------------------------------------------------------------------
  Ran 4 tests in 11.067s
  OK
  ubuntu@vpp-tdd:~/vpp$

Implementation

The vpp/api/vpe.api file specifies a set of messages that can be exchanged between VPP and the API client. The semantics of those messages are somewhat up to interpretation and convention.

The language binding is implemented simply by exposing four C calls to Python. Those are:

 int pneum_connect(char *name);
 int pneum_disconnect(void);
 int pneum_read(char **data, int *l);
 int pneum_write(char *data, int len);

In addition there is a Python message handler callback called by the C RX pthread. All message handling and parsing is done in Python.

Architecture

Architecture.png

Packaging:

VPP PAPI Packaging

Example: Dumping interface table

The example is in "vpp-api-client/python/examply.py" and there are unit tests in "vpp-api-client/python/test_papi.py".

#!/usr/bin/env python

import vpp_papi

r = vpp_papi.connect("test_papi")

t = vpp_papi.show_version()
print('VPP version:', t.version.decode())

t = vpp_papi.sw_interface_dump(0, b'ignored')

if t:
    print('List of interfaces')
    for interface in t:
        if interface.vl_msg_id == vpp_papi.vpe.VL_API_SW_INTERFACE_DETAILS:
            print(interface.interface_name.decode())
r = vpp_papi.disconnect()
 

Examples

Example: Receive statistics


#!/usr/bin/env python

import struct
import time

import vpp_papi

def papi_event_handler(result):
    if result.vl_msg_id == vpp_papi.VL_API_VNET_INTERFACE_COUNTERS:
        format = '>' + str(int(len(result.data) / 8)) + 'Q'
        counters = struct.unpack(format, result.data)
        print('Counters:', counters)
        return

    print('Unknown message id:', result.vl_msg_id)

r = vpp_papi.connect("test_papi")
vpp_papi.register_event_callback(papi_event_handler)
r = vpp_papi.want_stats(True, 123)

#
# Wait for some stats
#
time.sleep(60)
r = vpp_papi.want_stats(False, pid)
r = vpp_papi.disconnect()

 

API generation

The Python binding is automatically generated from the API definitions. See figure below.

Assumptions

  • A common context field is used as a transaction id, for the client to be able to match replies with requests. Not all messages have context and the API handles that, as long as the CONTROL_PING is used to embed them.
  • The API generates code that will send a CONTROL_PING for _DUMP/_DETAIL message exchanges. It will not do so for CALL/CALL_REPLY style calls, so it is important that those conventions are followed.
  • Some messages, e.g. VNET_INTERFACE_COUNTERS are variable sized, with an unspecified u8 data[0] field and a something like a u32 count or u32 nitems field telling the message specific handler the size of the message. There is no way to automatically generate code to handle this, so the Python API returns these to the caller as a byte string. These can then be handled by message specific code like:
 if result.vl_msg_id == vpp_papi.VL_API_VNET_INTERFACE_COUNTERS:
        format = '>' + str(int(len(result.data) / 8)) + 'Q'
        counters = struct.unpack(format, result.data)
 

Papi.png

16.09 release package

vpp-python-api-16.09-release.x86_64.rpm

Future Improvements / TODOs

Performance: Python essentially runs single threaded. The RX thread will hold the Global Interpreter Lock during callback. Current performance is about 1500 messages/second. An implementation in C gets about 450000 messages/second in comparison.

API: Use Python Async I/O?

Exception / error handling

Handle messages like GET_NODE_GRAPH where the reply is a reference to shared memory.