diff options
-rw-r--r-- | .clang-format | 38 | ||||
-rwxr-xr-x | build-root/scripts/checkstyle.sh | 62 | ||||
-rw-r--r-- | src/configure.ac | 1 | ||||
-rw-r--r-- | src/vpp-api/vapi/Makefile.am | 27 | ||||
-rw-r--r-- | src/vpp-api/vapi/libvapiclient.map | 3 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.c | 59 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.h | 102 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi.hpp | 905 | ||||
-rwxr-xr-x | src/vpp-api/vapi/vapi_c_gen.py | 188 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_common.h | 61 | ||||
-rwxr-xr-x | src/vpp-api/vapi/vapi_cpp_gen.py | 262 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_dbg.h | 1 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_doc.md | 155 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_internal.h | 22 | ||||
-rw-r--r-- | src/vpp-api/vapi/vapi_json_parser.py | 2 | ||||
-rw-r--r-- | test/ext/Makefile | 26 | ||||
-rw-r--r-- | test/ext/fake.api.json | 35 | ||||
-rw-r--r-- | test/ext/vapi_c_test.c (renamed from test/ext/vapi_test.c) | 30 | ||||
-rw-r--r-- | test/ext/vapi_cpp_test.cpp | 591 | ||||
-rw-r--r-- | test/test_vapi.py | 36 |
20 files changed, 2338 insertions, 268 deletions
diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000000..977ed2dbf00 --- /dev/null +++ b/.clang-format @@ -0,0 +1,38 @@ +--- +AlignEscapedNewlinesLeft: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AllowShortFunctionsOnASingleLine: false +AlwaysBreakBeforeMultilineStrings: false +BreakBeforeBinaryOperators: false +BreakBeforeTernaryOperators: true +BinPackParameters: true +BreakBeforeBraces: GNU +ColumnLimit: 79 +IndentCaseLabels: false +MaxEmptyLinesToKeep: 1 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 60 +PenaltyBreakString: 1000 +PenaltyBreakFirstLessLess: 120 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerBindsToType: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: Always +SpacesBeforeTrailingComments: 1 +SpacesInParentheses: false +SpaceInEmptyParentheses: false +SpacesInCStyleCastParentheses: false +SpaceAfterControlStatementKeyword: true +Cpp11BracedListStyle: true +Standard: Cpp11 +SortIncludes: false +IndentWidth: 2 +TabWidth: 4 +UseTab: Never +IndentFunctionDeclarationAfterType: false +ContinuationIndentWidth: 4 +... diff --git a/build-root/scripts/checkstyle.sh b/build-root/scripts/checkstyle.sh index 55fe4ab53ae..bd2ba81371b 100755 --- a/build-root/scripts/checkstyle.sh +++ b/build-root/scripts/checkstyle.sh @@ -32,37 +32,75 @@ fi # don't *fail*. command -v indent > /dev/null if [ $? != 0 ]; then - echo "Cound not find required commend \"indent\". Checkstyle aborted" + echo "Cound not find required command \"indent\". Checkstyle aborted" exit ${EXIT_CODE} fi indent --version +# Check to make sure we have clang-format. Exit if we don't with an error message, but +# don't *fail*. +command -v clang-format > /dev/null +if [ $? != 0 ]; then + echo "Could not find command \"clang-format\". Checking C++ files will cause abort" + HAVE_CLANG_FORMAT=0 +else + HAVE_CLANG_FORMAT=1 + clang-format --version +fi + cd ${VPP_DIR} git status for i in ${FILELIST}; do if [ -f ${i} ] && [ ${i} != "build-root/scripts/checkstyle.sh" ] && [ ${i} != "extras/emacs/fix-coding-style.el" ]; then grep -q "fd.io coding-style-patch-verification: ON" ${i} if [ $? == 0 ]; then + EXTENSION=`basename ${i} | sed 's/^\w\+.//'` + case ${EXTENSION} in + hpp|cpp|cc|hh) + CMD="clang-format" + if [ ${HAVE_CLANG_FORMAT} == 0 ]; then + echo "C++ file detected. Abort. (missing clang-format)" + exit ${EXIT_CODE} + fi + ;; + *) + CMD="indent" + ;; + esac CHECKSTYLED_FILES="${CHECKSTYLED_FILES} ${i}" if [ ${FIX} == 0 ]; then - indent ${i} -o ${i}.out1 > /dev/null 2>&1 - indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 - # Remove trailing whitespace - sed -i -e 's/[[:space:]]*$//' ${i}.out2 + if [ "${CMD}" == "clang-format" ] + then + clang-format ${i} > ${i}.out2 + else + indent ${i} -o ${i}.out1 > /dev/null 2>&1 + indent ${i}.out1 -o ${i}.out2 > /dev/null 2>&1 + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i}.out2 diff -q ${i} ${i}.out2 else - indent ${i} - indent ${i} - # Remove trailing whitespace - sed -i -e 's/[[:space:]]*$//' ${i} + if [ "${CMD}" == "clang-format" ]; then + clang-format -i ${i} > /dev/null 2>&1 + else + indent ${i} + indent ${i} + fi + # Remove trailing whitespace + sed -i -e 's/[[:space:]]*$//' ${i} fi if [ $? != 0 ]; then EXIT_CODE=1 echo echo "Checkstyle failed for ${i}." - echo "Run indent (twice!) as shown to fix the problem:" - echo "indent ${VPP_DIR}${i}" - echo "indent ${VPP_DIR}${i}" + if [ "${CMD}" == "clang-format" ]; then + echo "Run clang-format as shown to fix the problem:" + echo "clang-format -i ${VPP_DIR}${i}" + else + echo "Run indent (twice!) as shown to fix the problem:" + echo "indent ${VPP_DIR}${i}" + echo "indent ${VPP_DIR}${i}" + fi fi if [ -f ${i}.out1 ]; then rm ${i}.out1 diff --git a/src/configure.ac b/src/configure.ac index 2efb23ad690..f5ce3be250c 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -7,6 +7,7 @@ AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/ AC_CONFIG_MACRO_DIR([m4]) AC_PROG_CC +AC_PROG_CXX AM_PROG_AS AM_PROG_LIBTOOL AC_PROG_YACC diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am index ce681c38459..74b2b47e8e4 100644 --- a/src/vpp-api/vapi/Makefile.am +++ b/src/vpp-api/vapi/Makefile.am @@ -15,7 +15,7 @@ AUTOMAKE_OPTIONS = foreign ACLOCAL_AMFLAGS = -I m4 AM_LIBTOOLFLAGS = --quiet -AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/vapi +AM_CFLAGS = -Wall -I${top_srcdir} -I${top_builddir} -I. -I$(top_srcdir)/vpp-api/ AM_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined @@ -23,26 +23,33 @@ bin_PROGRAMS = noinst_LTLIBRARIES = CLEANDIRS = -%.api.vapi.h: %.api.json vapi_c_gen.py +vapi/%.api.vapi.h: %.api.json vapi_c_gen.py vapi_json_parser.py @echo " VAPI C GEN $< " $@ ; \ mkdir -p `dirname $@` ; \ - $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py $< + $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py --prefix=vapi $< + +vapi/%.api.vapi.hpp: %.api.json vapi_cpp_gen.py vapi_c_gen.py vapi_json_parser.py + @echo " VAPI CPP GEN $< " $@ ; \ + mkdir -p `dirname $@` ; \ + $(top_srcdir)/vpp-api/vapi/vapi_cpp_gen.py --prefix=vapi --gen-h-prefix=vapi $< %.api.json: find $(top_builddir) -name '$@' | xargs ln -s BUILT_SOURCES = $(shell find $(top_builddir) -name '*.api.json' | xargs -n1 basename) \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) vapi.c: $(BUILT_SOURCES) JSON_FILES = $(wildcard *.api.json) - lib_LTLIBRARIES = libvapiclient.la libvapiclient_la_SOURCES = vapi.c +libvapiclient_la_DEPENDENCIES = libvapiclient.map + libvapiclient_la_LIBADD = -lpthread -lm -lrt \ $(top_builddir)/libvppinfra.la \ $(top_builddir)/libvlibmemoryclient.la \ @@ -54,10 +61,14 @@ libvapiclient_la_LDFLAGS = \ libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi -nobase_include_HEADERS = ${top_srcdir}/vpp-api/client/vppapiclient.h \ - vapi.h \ +vapiincludedir = $(includedir)/vapi + +vapiinclude_HEADERS = vapi.h \ + vapi.hpp \ vapi_dbg.h \ + vapi_common.h \ vapi_internal.h \ - $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + $(patsubst %.api.json,vapi/%.api.vapi.h,$(JSON_FILES)) \ + $(patsubst %.api.json,vapi/%.api.vapi.hpp,$(JSON_FILES)) # vi:syntax=automake diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map index 537330026c5..6b58d1e9eb9 100644 --- a/src/vpp-api/vapi/libvapiclient.map +++ b/src/vpp-api/vapi/libvapiclient.map @@ -23,6 +23,7 @@ VAPICLIENT_17.07 { vapi_register_msg; vapi_get_client_index; vapi_is_nonblocking; + vapi_requests_empty; vapi_requests_full; vapi_gen_req_context; vapi_producer_lock; @@ -36,6 +37,8 @@ VAPICLIENT_17.07 { vapi_get_context_offset; vapi_msg_id_control_ping; vapi_msg_id_control_ping_reply; + vapi_get_message_count; + vapi_get_msg_name; local: *; }; diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c index b9c81a1307a..59415e0308f 100644 --- a/src/vpp-api/vapi/vapi.c +++ b/src/vpp-api/vapi/vapi.c @@ -102,7 +102,7 @@ vapi_requests_full (vapi_ctx_t ctx) return (ctx->requests_count == ctx->requests_size); } -static bool +bool vapi_requests_empty (vapi_ctx_t ctx) { return (0 == ctx->requests_count); @@ -229,6 +229,16 @@ vapi_msg_free (vapi_ctx_t ctx, void *msg) vl_msg_api_free (msg); } +vapi_msg_id_t +vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id) +{ + if (vl_msg_id <= ctx->vl_msg_id_max) + { + return ctx->vl_msg_id_to_vapi_msg_t[vl_msg_id]; + } + return ~0; +} + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result) { @@ -420,16 +430,17 @@ vapi_send (vapi_ctx_t ctx, void *msg) vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; if (id < __vapi_metadata.count) { - VAPI_DBG ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name); + VAPI_DBG ("send msg@%p:%u[%s]", msg, msgid, + __vapi_metadata.msgs[id]->name); } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } } else { - VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + VAPI_DBG ("send msg@%p:%u[UNKNOWN]", msg, msgid); } #endif tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, @@ -522,7 +533,26 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) } *msg = (u8 *) data; *msg_size = ntohl (msgbuf->data_len); - VAPI_DBG ("recv msg %p", *msg); +#if VAPI_DEBUG + unsigned msgid = be16toh (*(u16 *) * msg); + if (msgid <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid]; + if (id < __vapi_metadata.count) + { + VAPI_DBG ("recv msg@%p:%u[%s]", *msg, msgid, + __vapi_metadata.msgs[id]->name); + } + else + { + VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); + } + } + else + { + VAPI_DBG ("recv msg@%p:%u[UNKNOWN]", *msg, msgid); + } +#endif } else { @@ -534,7 +564,6 @@ vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) { - /* FIXME */ return VAPI_ENOTSUP; } @@ -657,7 +686,7 @@ vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) return VAPI_OK; } -static bool +bool vapi_msg_is_with_context (vapi_msg_id_t id) { assert (id <= __vapi_metadata.count); @@ -785,10 +814,6 @@ vapi_is_nonblocking (vapi_ctx_t ctx) return (VAPI_MODE_NONBLOCKING == ctx->mode); } -bool vapi_requests_full (vapi_ctx_t ctx); - -size_t vapi_get_request_count (vapi_ctx_t ctx); - size_t vapi_get_max_request_count (vapi_ctx_t ctx) { @@ -886,6 +911,18 @@ vapi_producer_unlock (vapi_ctx_t ctx) return VAPI_OK; } +size_t +vapi_get_message_count () +{ + return __vapi_metadata.count; +} + +const char * +vapi_get_msg_name (vapi_msg_id_t id) +{ + return __vapi_metadata.msgs[id]->name; +} + /* * fd.io coding-style-patch-verification: ON * diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h index 1e1d567a0db..245bf654e8a 100644 --- a/src/vpp-api/vapi/vapi.h +++ b/src/vpp-api/vapi/vapi.h @@ -21,6 +21,12 @@ #include <string.h> #include <stdbool.h> #include <vppinfra/types.h> +#include <vapi/vapi_common.h> + +#ifdef __cplusplus +extern "C" +{ +#endif /** * @file vapi.h @@ -36,39 +42,7 @@ * process). It's not recommended to mix the higher and lower level APIs. Due * to version issues, the higher-level APIs are not part of the shared library. */ - -typedef enum -{ - VAPI_OK = 0, /**< success */ - VAPI_EINVAL, /**< invalid value encountered */ - VAPI_EAGAIN, /**< operation would block */ - VAPI_ENOTSUP, /**< operation not supported */ - VAPI_ENOMEM, /**< out of memory */ - VAPI_ENORESP, /**< no response to request */ - VAPI_EMAP_FAIL, /**< failure while mapping api */ - VAPI_ECON_FAIL, /**< failure while connecting to vpp */ - VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp - (control ping/control ping reply mismatch) */ - VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ - VAPI_EUSER, /**< user error used for breaking dispatch, - never used by VAPI */ -} vapi_error_e; - -typedef enum -{ - VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ - VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ -} vapi_mode_e; - -typedef enum -{ - VAPI_WAIT_FOR_READ, /**< wait until a message can be read */ - VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */ - VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */ -} vapi_wait_mode_e; - -typedef int vapi_msg_id_t; -typedef struct vapi_ctx_s *vapi_ctx_t; + typedef struct vapi_ctx_s *vapi_ctx_t; /** * @brief allocate vapi message of given size @@ -80,7 +54,7 @@ typedef struct vapi_ctx_s *vapi_ctx_t; * * @return pointer to message or NULL if out of memory */ -void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); + void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); /** * @brief free a vapi message @@ -90,7 +64,7 @@ void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); * @param ctx opaque vapi context * @param msg message to be freed */ -void vapi_msg_free (vapi_ctx_t ctx, void *msg); + void vapi_msg_free (vapi_ctx_t ctx, void *msg); /** * @brief allocate vapi context @@ -99,18 +73,18 @@ void vapi_msg_free (vapi_ctx_t ctx, void *msg); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); + vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); /** * @brief free vapi context */ -void vapi_ctx_free (vapi_ctx_t ctx); + void vapi_ctx_free (vapi_ctx_t ctx); /** * @brief check if message identified by it's message id is known by the vpp to * which the connection is open */ -bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); + bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); /** * @brief connect to vpp @@ -124,10 +98,10 @@ bool vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t type); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, - const char *chroot_prefix, - int max_outstanding_requests, - int response_queue_size, vapi_mode_e mode); + vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, + const char *chroot_prefix, + int max_outstanding_requests, + int response_queue_size, vapi_mode_e mode); /** * @brief disconnect from vpp @@ -136,7 +110,7 @@ vapi_error_e vapi_connect (vapi_ctx_t ctx, const char *name, * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_disconnect (vapi_ctx_t ctx); + vapi_error_e vapi_disconnect (vapi_ctx_t ctx); /** * @brief get event file descriptor @@ -149,7 +123,7 @@ vapi_error_e vapi_disconnect (vapi_ctx_t ctx); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); + vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); /** * @brief low-level api for sending messages to vpp @@ -162,7 +136,7 @@ vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); + vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); /** * @brief low-level api for atomically sending two messages to vpp - either @@ -177,7 +151,7 @@ vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); + vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); /** * @brief low-level api for reading messages from vpp @@ -191,7 +165,7 @@ vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); + vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); /** * @brief wait for connection to become readable or writable @@ -201,14 +175,14 @@ vapi_error_e vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); + vapi_error_e vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode); /** * @brief pick next message sent by vpp and call the appropriate callback * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); + vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); /** * @brief loop vapi_dispatch_one until responses to all currently outstanding @@ -224,11 +198,11 @@ vapi_error_e vapi_dispatch_one (vapi_ctx_t ctx); * * @return VAPI_OK on success, other error code on error */ -vapi_error_e vapi_dispatch (vapi_ctx_t ctx); + vapi_error_e vapi_dispatch (vapi_ctx_t ctx); /** generic vapi event callback */ -typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, - void *payload); + typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, + void *payload); /** * @brief set event callback to call when message with given id is dispatched @@ -238,8 +212,8 @@ typedef vapi_error_e (*vapi_event_cb) (vapi_ctx_t ctx, void *callback_ctx, * @param callback callback * @param callback_ctx context pointer stored and passed to callback */ -void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, - vapi_event_cb callback, void *callback_ctx); + void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx); /** * @brief clear event callback for given message id @@ -247,12 +221,12 @@ void vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, * @param ctx opaque vapi context * @param id message id */ -void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); + void vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id); /** generic vapi event callback */ -typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, - void *callback_ctx, - vapi_msg_id_t id, void *msg); + typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, + void *callback_ctx, + vapi_msg_id_t id, void *msg); /** * @brief set generic event callback * @@ -263,16 +237,20 @@ typedef vapi_error_e (*vapi_generic_event_cb) (vapi_ctx_t ctx, * @param callback callback * @param callback_ctx context pointer stored and passed to callback */ -void vapi_set_generic_event_cb (vapi_ctx_t ctx, - vapi_generic_event_cb callback, - void *callback_ctx); + void vapi_set_generic_event_cb (vapi_ctx_t ctx, + vapi_generic_event_cb callback, + void *callback_ctx); /** * @brief clear generic event callback * * @param ctx opaque vapi context */ -void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + +#ifdef __cplusplus +} +#endif #endif diff --git a/src/vpp-api/vapi/vapi.hpp b/src/vpp-api/vapi/vapi.hpp new file mode 100644 index 00000000000..3be78b41b0a --- /dev/null +++ b/src/vpp-api/vapi/vapi.hpp @@ -0,0 +1,905 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_hpp_included +#define vapi_hpp_included + +#include <cstddef> +#include <vector> +#include <mutex> +#include <queue> +#include <cassert> +#include <functional> +#include <algorithm> +#include <atomic> +#include <vppinfra/types.h> +#include <vapi/vapi.h> +#include <vapi/vapi_internal.h> +#include <vapi/vapi_dbg.h> +#include <vapi/vpe.api.vapi.h> + +#if VAPI_CPP_DEBUG_LEAKS +#include <unordered_set> +#endif + +/** + * @file + * @brief C++ VPP API + */ + +namespace vapi +{ + +class Connection; + +template <typename Req, typename Resp, typename... Args> class Request; +template <typename M> class Msg; +template <typename M> void vapi_swap_to_be (M *msg); +template <typename M> void vapi_swap_to_host (M *msg); +template <typename M, typename... Args> +M *vapi_alloc (Connection &con, Args...); +template <typename M> vapi_msg_id_t vapi_get_msg_id_t (); +template <typename M> class Event_registration; + +class Unexpected_msg_id_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "unexpected message id"; + } +}; + +class Msg_not_available_exception : public std::exception +{ +public: + virtual const char *what () const throw () + { + return "message unavailable"; + } +}; + +typedef enum { + /** response not ready yet */ + RESPONSE_NOT_READY, + + /** response to request is ready */ + RESPONSE_READY, + + /** no response to request (will never come) */ + RESPONSE_NO_RESPONSE, +} vapi_response_state_e; + +/** + * Class representing common functionality of a request - response state + * and context + */ +class Common_req +{ +public: + virtual ~Common_req (){}; + + Connection &get_connection () + { + return con; + }; + + vapi_response_state_e get_response_state (void) const + { + return response_state; + } + +private: + Connection &con; + Common_req (Connection &con) : con{con}, response_state{RESPONSE_NOT_READY} + { + } + + void set_response_state (vapi_response_state_e state) + { + response_state = state; + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) = 0; + + void set_context (u32 context) + { + this->context = context; + } + + u32 get_context () + { + return context; + } + + u32 context; + vapi_response_state_e response_state; + + friend class Connection; + + template <typename M> friend class Msg; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename M> friend class Event_registration; +}; + +/** + * Class representing a connection to VPP + * + * After creating a Connection object, call connect() to actually connect + * to VPP. Use is_msg_available to discover whether a specific message is known + * and supported by the VPP connected to. + */ +class Connection +{ +public: + Connection (void) : vapi_ctx{0}, event_count{0} + { + + vapi_error_e rv = VAPI_OK; + if (!vapi_ctx) + { + if (VAPI_OK != (rv = vapi_ctx_alloc (&vapi_ctx))) + { + throw std::bad_alloc (); + } + } + events.reserve (vapi_get_message_count () + 1); + } + + Connection (const Connection &) = delete; + + ~Connection (void) + { + vapi_ctx_free (vapi_ctx); +#if VAPI_CPP_DEBUG_LEAKS + for (auto x : shm_data_set) + { + printf ("Leaked shm_data@%p!\n", x); + } +#endif + } + + /** + * @brief check if message identified by it's message id is known by the + * vpp to which the connection is open + */ + bool is_msg_available (vapi_msg_id_t type) + { + return vapi_is_msg_available (vapi_ctx, type); + } + + /** + * @brief connect to vpp + * + * @param name application name + * @param chroot_prefix shared memory prefix + * @param max_queued_request max number of outstanding requests queued + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e connect (const char *name, const char *chroot_prefix, + int max_outstanding_requests, int response_queue_size) + { + return vapi_connect (vapi_ctx, name, chroot_prefix, + max_outstanding_requests, response_queue_size, + VAPI_MODE_BLOCKING); + } + + /** + * @brief disconnect from vpp + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e disconnect () + { + auto x = requests.size (); + while (x > 0) + { + VAPI_DBG ("popping request @%p", requests.front ()); + requests.pop_front (); + --x; + } + return vapi_disconnect (vapi_ctx); + }; + + /** + * @brief get event file descriptor + * + * @note this file descriptor becomes readable when messages (from vpp) + * are waiting in queue + * + * @param[out] fd pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e get_fd (int *fd) + { + return vapi_get_fd (vapi_ctx, fd); + } + + /** + * @brief wait for responses from vpp and assign them to appropriate objects + * + * @param limit stop dispatch after the limit object received it's response + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e dispatch (const Common_req *limit = nullptr) + { + std::lock_guard<std::mutex> lock (dispatch_mutex); + vapi_error_e rv = VAPI_OK; + bool loop_again = true; + while (loop_again) + { + void *shm_data; + size_t shm_data_size; + rv = vapi_recv (vapi_ctx, &shm_data, &shm_data_size); + if (VAPI_OK != rv) + { + return rv; + } +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_alloc (shm_data); +#endif + std::lock_guard<std::recursive_mutex> requests_lock (requests_mutex); + std::lock_guard<std::recursive_mutex> events_lock (events_mutex); + vapi_msg_id_t id = vapi_lookup_vapi_msg_id_t ( + vapi_ctx, be16toh (*static_cast<u16 *> (shm_data))); + bool has_context = vapi_msg_is_with_context (id); + bool break_dispatch = false; + Common_req *matching_req = nullptr; + if (has_context) + { + u32 context = *reinterpret_cast<u32 *> ( + (static_cast<u8 *> (shm_data) + vapi_get_context_offset (id))); + const auto x = requests.front (); + matching_req = x; + if (context == x->context) + { + std::tie (rv, break_dispatch) = + x->assign_response (id, shm_data); + } + else + { + std::tie (rv, break_dispatch) = + x->assign_response (id, nullptr); + } + if (break_dispatch) + { + requests.pop_front (); + } + } + else + { + if (events[id]) + { + std::tie (rv, break_dispatch) = + events[id]->assign_response (id, shm_data); + matching_req = events[id]; + } + else + { + msg_free (shm_data); + } + } + if ((matching_req && matching_req == limit && break_dispatch) || + VAPI_OK != rv) + { + return rv; + } + loop_again = !requests.empty () || (event_count > 0); + } + return rv; + } + + /** + * @brief convenience wrapper function + */ + vapi_error_e dispatch (const Common_req &limit) + { + return dispatch (&limit); + } + + /** + * @brief wait for response to a specific request + * + * @param req request to wait for response for + * + * @return VAPI_OK on success, other error code on error + */ + vapi_error_e wait_for_response (const Common_req &req) + { + if (RESPONSE_READY == req.get_response_state ()) + { + return VAPI_OK; + } + return dispatch (req); + } + +private: + void msg_free (void *shm_data) + { +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (shm_data); +#endif + vapi_msg_free (vapi_ctx, shm_data); + } + + template <template <typename XReq, typename XResp, typename... XArgs> + class X, + typename Req, typename Resp, typename... Args> + vapi_error_e send (X<Req, Resp, Args...> *req) + { + if (!req) + { + return VAPI_EINVAL; + } + u32 req_context = + req_context_counter.fetch_add (1, std::memory_order_relaxed); + req->request.shm_data->header.context = req_context; + vapi_swap_to_be<Req> (req->request.shm_data); + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + vapi_error_e rv = vapi_send (vapi_ctx, req->request.shm_data); + if (VAPI_OK == rv) + { + VAPI_DBG ("Push %p", req); + requests.emplace_back (req); + req->set_context (req_context); +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (req->request.shm_data); +#endif + req->request.shm_data = nullptr; /* consumed by vapi_send */ + } + else + { + vapi_swap_to_host<Req> (req->request.shm_data); + } + return rv; + } + + template <template <typename XReq, typename XResp, typename... XArgs> + class X, + typename Req, typename Resp, typename... Args> + vapi_error_e send_with_control_ping (X<Req, Resp, Args...> *req) + { + if (!req) + { + return VAPI_EINVAL; + } + u32 req_context = + req_context_counter.fetch_add (1, std::memory_order_relaxed); + req->request.shm_data->header.context = req_context; + vapi_swap_to_be<Req> (req->request.shm_data); + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + vapi_error_e rv = vapi_send_with_control_ping ( + vapi_ctx, req->request.shm_data, req_context); + if (VAPI_OK == rv) + { + VAPI_DBG ("Push %p", req); + requests.emplace_back (req); + req->set_context (req_context); +#if VAPI_CPP_DEBUG_LEAKS + on_shm_data_free (req->request.shm_data); +#endif + req->request.shm_data = nullptr; /* consumed by vapi_send */ + } + else + { + vapi_swap_to_host<Req> (req->request.shm_data); + } + return rv; + } + + void unregister_request (Common_req *request) + { + std::lock_guard<std::recursive_mutex> lock (requests_mutex); + std::remove (requests.begin (), requests.end (), request); + } + + template <typename M> void register_event (Event_registration<M> *event) + { + const vapi_msg_id_t id = M::get_msg_id (); + std::lock_guard<std::recursive_mutex> lock (events_mutex); + events[id] = event; + ++event_count; + } + + template <typename M> void unregister_event (Event_registration<M> *event) + { + const vapi_msg_id_t id = M::get_msg_id (); + std::lock_guard<std::recursive_mutex> lock (events_mutex); + events[id] = nullptr; + --event_count; + } + + vapi_ctx_t vapi_ctx; + std::atomic_ulong req_context_counter; + std::mutex dispatch_mutex; + + std::recursive_mutex requests_mutex; + std::recursive_mutex events_mutex; + std::deque<Common_req *> requests; + std::vector<Common_req *> events; + int event_count; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename M> friend class Result_set; + + template <typename M> friend class Event_registration; + + template <typename M, typename... Args> + friend M *vapi_alloc (Connection &con, Args...); + + template <typename M> friend class Msg; + +#if VAPI_CPP_DEBUG_LEAKS + void on_shm_data_alloc (void *shm_data) + { + if (shm_data) + { + auto pos = shm_data_set.find (shm_data); + if (pos == shm_data_set.end ()) + { + shm_data_set.insert (shm_data); + } + else + { + printf ("Double-add shm_data @%p!\n", shm_data); + } + } + } + + void on_shm_data_free (void *shm_data) + { + auto pos = shm_data_set.find (shm_data); + if (pos == shm_data_set.end ()) + { + printf ("Freeing untracked shm_data @%p!\n", shm_data); + } + else + { + shm_data_set.erase (pos); + } + } + std::unordered_set<void *> shm_data_set; +#endif +}; + +template <typename Req, typename Resp, typename... Args> class Request; + +template <typename Req, typename Resp, typename... Args> class Dump; + +template <class, class = void> struct vapi_has_payload_trait : std::false_type +{ +}; + +template <class... T> using vapi_void_t = void; + +template <class T> +struct vapi_has_payload_trait<T, vapi_void_t<decltype (&T::payload)>> + : std::true_type +{ +}; + +template <typename M> void vapi_msg_set_msg_id (vapi_msg_id_t id) +{ + Msg<M>::set_msg_id (id); +} + +/** + * Class representing a message stored in shared memory + */ +template <typename M> class Msg +{ +public: + Msg (const Msg &) = delete; + + ~Msg () + { + VAPI_DBG ("Destroy Msg<%s>@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), this, shm_data); + if (shm_data) + { + con.get ().msg_free (shm_data); + shm_data = nullptr; + } + } + + static vapi_msg_id_t get_msg_id () + { + return *msg_id_holder (); + } + + template <typename X = M> + typename std::enable_if<vapi_has_payload_trait<X>::value, + decltype (X::payload) &>::type + get_payload () const + { + return shm_data->payload; + } + +private: + Msg (Msg<M> &&msg) : con{msg.con} + { + VAPI_DBG ("Move construct Msg<%s> from msg@%p to msg@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); + shm_data = msg.shm_data; + msg.shm_data = nullptr; + } + + Msg<M> &operator= (Msg<M> &&msg) + { + VAPI_DBG ("Move assign Msg<%s> from msg@%p to msg@%p, shm_data@%p", + vapi_get_msg_name (get_msg_id ()), &msg, this, msg.shm_data); + con.get ().msg_free (shm_data); + con = msg.con; + shm_data = msg.shm_data; + msg.shm_data = nullptr; + return *this; + } + + struct Msg_allocator : std::allocator<Msg<M>> + { + template <class U, class... Args> void construct (U *p, Args &&... args) + { + ::new ((void *)p) U (std::forward<Args> (args)...); + } + + template <class U> struct rebind + { + typedef Msg_allocator other; + }; + }; + + static void set_msg_id (vapi_msg_id_t id) + { + assert ((~0 == *msg_id_holder ()) || (id == *msg_id_holder ())); + *msg_id_holder () = id; + } + + static vapi_msg_id_t *msg_id_holder () + { + static vapi_msg_id_t my_id{~0}; + return &my_id; + } + + Msg (Connection &con, void *shm_data) throw (Msg_not_available_exception) + : con{con} + { + if (!con.is_msg_available (get_msg_id ())) + { + throw Msg_not_available_exception (); + } + this->shm_data = static_cast<shm_data_type *> (shm_data); + VAPI_DBG ("New Msg<%s>@%p shm_data@%p", vapi_get_msg_name (get_msg_id ()), + this, shm_data); + } + + void assign_response (vapi_msg_id_t resp_id, + void *shm_data) throw (Unexpected_msg_id_exception) + { + assert (nullptr == this->shm_data); + if (resp_id != get_msg_id ()) + { + throw Unexpected_msg_id_exception (); + } + this->shm_data = static_cast<M *> (shm_data); + vapi_swap_to_host<M> (this->shm_data); + VAPI_DBG ("Assign response to Msg<%s>@%p shm_data@%p", + vapi_get_msg_name (get_msg_id ()), this, shm_data); + } + + std::reference_wrapper<Connection> con; + using shm_data_type = M; + shm_data_type *shm_data; + + friend class Connection; + + template <typename Req, typename Resp, typename... Args> + friend class Request; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename X> friend class Event_registration; + + template <typename X> friend class Result_set; + + friend struct Msg_allocator; + + template <typename X> friend void vapi_msg_set_msg_id (vapi_msg_id_t id); +}; + +/** + * Class representing a simple request - with a single response message + */ +template <typename Req, typename Resp, typename... Args> +class Request : public Common_req +{ +public: + Request (Connection &con, Args... args, + std::function<vapi_error_e (Request<Req, Resp, Args...> &)> + callback = nullptr) + : Common_req{con}, callback{callback}, + request{con, vapi_alloc<Req> (con, args...)}, response{con, nullptr} + { + } + + Request (const Request &) = delete; + + virtual ~Request () + { + if (RESPONSE_NOT_READY == get_response_state ()) + { + con.unregister_request (this); + } + } + + vapi_error_e execute () + { + return con.send (this); + } + + const Msg<Req> &get_request (void) const + { + return request; + } + + const Msg<Resp> &get_response (void) + { + return response; + } + +private: + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + assert (RESPONSE_NOT_READY == get_response_state ()); + response.assign_response (id, shm_data); + set_response_state (RESPONSE_READY); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + std::function<vapi_error_e (Request<Req, Resp, Args...> &)> callback; + Msg<Req> request; + Msg<Resp> response; + + friend class Connection; +}; + +/** + * Class representing iterable set of responses of the same type + */ +template <typename M> class Result_set +{ +public: + ~Result_set () + { + } + + Result_set (const Result_set &) = delete; + + bool is_complete () const + { + return complete; + } + + size_t size () const + { + return set.size (); + } + + using const_iterator = + typename std::vector<Msg<M>, + typename Msg<M>::Msg_allocator>::const_iterator; + + const_iterator begin () const + { + return set.begin (); + } + + const_iterator end () const + { + return set.end (); + } + + void free_response (const_iterator pos) + { + set.erase (pos); + } + + void free_all_responses () + { + set.clear (); + } + +private: + void mark_complete () + { + complete = true; + } + + void assign_response (vapi_msg_id_t resp_id, + void *shm_data) throw (Unexpected_msg_id_exception) + { + if (resp_id != Msg<M>::get_msg_id ()) + { + { + throw Unexpected_msg_id_exception (); + } + } + else if (shm_data) + { + vapi_swap_to_host<M> (static_cast<M *> (shm_data)); + set.emplace_back (con, shm_data); + VAPI_DBG ("Result_set@%p emplace_back shm_data@%p", this, shm_data); + } + } + + Result_set (Connection &con) : con{con}, complete{false} + { + } + + Connection &con; + bool complete; + std::vector<Msg<M>, typename Msg<M>::Msg_allocator> set; + + template <typename Req, typename Resp, typename... Args> friend class Dump; + + template <typename X> friend class Event_registration; +}; + +/** + * Class representing a dump request - zero or more identical responses to a + * single request message + */ +template <typename Req, typename Resp, typename... Args> +class Dump : public Common_req +{ +public: + Dump (Connection &con, Args... args, + std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback = + nullptr) + : Common_req{con}, request{con, vapi_alloc<Req> (con, args...)}, + result_set{con}, callback{callback} + { + } + + Dump (const Dump &) = delete; + + virtual ~Dump () + { + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + if (id == vapi_msg_id_control_ping_reply) + { + con.msg_free (shm_data); + result_set.mark_complete (); + set_response_state (RESPONSE_READY); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + else + { + result_set.assign_response (id, shm_data); + } + return std::make_pair (VAPI_OK, false); + } + + vapi_error_e execute () + { + return con.send_with_control_ping (this); + } + + Msg<Req> &get_request (void) + { + return request; + } + + using resp_type = typename Msg<Resp>::shm_data_type; + + const Result_set<Resp> &get_result_set (void) const + { + return result_set; + } + +private: + Msg<Req> request; + Result_set<resp_type> result_set; + std::function<vapi_error_e (Dump<Req, Resp, Args...> &)> callback; + + friend class Connection; +}; + +/** + * Class representing event registration - incoming events (messages) from + * vpp as a result of a subscription (typically a want_* simple request) + */ +template <typename M> class Event_registration : public Common_req +{ +public: + Event_registration ( + Connection &con, + std::function<vapi_error_e (Event_registration<M> &)> callback = + nullptr) throw (Msg_not_available_exception) + : Common_req{con}, result_set{con}, callback{callback} + { + if (!con.is_msg_available (M::get_msg_id ())) + { + throw Msg_not_available_exception (); + } + con.register_event (this); + } + + Event_registration (const Event_registration &) = delete; + + virtual ~Event_registration () + { + con.unregister_event (this); + } + + virtual std::tuple<vapi_error_e, bool> assign_response (vapi_msg_id_t id, + void *shm_data) + { + result_set.assign_response (id, shm_data); + if (nullptr != callback) + { + return std::make_pair (callback (*this), true); + } + return std::make_pair (VAPI_OK, true); + } + + using resp_type = typename M::shm_data_type; + + Result_set<resp_type> &get_result_set (void) + { + return result_set; + } + +private: + Result_set<resp_type> result_set; + std::function<vapi_error_e (Event_registration<M> &)> callback; +}; +}; + +#endif + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi_c_gen.py b/src/vpp-api/vapi/vapi_c_gen.py index 2bc1eef87e5..ef6e2663cf4 100755 --- a/src/vpp-api/vapi/vapi_c_gen.py +++ b/src/vpp-api/vapi/vapi_c_gen.py @@ -26,7 +26,7 @@ class CField(Field): def get_swap_to_be_code(self, struct, var): if self.len is not None: if self.len > 0: - return "do { int i; for (i = 0; i < %d; ++i) { %s } }"\ + return "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\ " while(0);" % ( self.len, self.type.get_swap_to_be_code(struct, "%s[i]" % var)) @@ -38,7 +38,7 @@ class CField(Field): else: nelem_field = "%s%s" % (struct, self.nelem_field.name) return ( - "do { int i; for (i = 0; i < %s; ++i) { %s } }" + "do { unsigned i; for (i = 0; i < %s; ++i) { %s } }" " while(0);" % (nelem_field, self.type.get_swap_to_be_code( struct, "%s[i]" % var))) @@ -47,14 +47,14 @@ class CField(Field): def get_swap_to_host_code(self, struct, var): if self.len is not None: if self.len > 0: - return "do { int i; for (i = 0; i < %d; ++i) { %s } }"\ + return "do { unsigned i; for (i = 0; i < %d; ++i) { %s } }"\ " while(0);" % ( self.len, self.type.get_swap_to_host_code(struct, "%s[i]" % var)) else: # nelem_field already swapped to host here... return ( - "do { int i; for (i = 0; i < %s%s; ++i) { %s } }" + "do { unsigned i; for (i = 0; i < %s%s; ++i) { %s } }" " while(0);" % (struct, self.nelem_field.name, self.type.get_swap_to_host_code( @@ -199,14 +199,17 @@ class CMessage (Message): def get_alloc_func_name(self): return "vapi_alloc_%s" % self.name + def get_alloc_vla_param_names(self): + return [self.get_alloc_func_vla_field_length_name(f) + for f in self.fields + if f.nelem_field is not None] + def get_alloc_func_decl(self): return "%s* %s(struct vapi_ctx_s *ctx%s)" % ( self.get_c_name(), self.get_alloc_func_name(), - "".join([", size_t %s" % - self.get_alloc_func_vla_field_length_name(f) - for f in self.fields - if f.nelem_field is not None])) + "".join([", size_t %s" % n for n in + self.get_alloc_vla_param_names()])) def get_alloc_func_def(self): extra = [] @@ -228,7 +231,8 @@ class CMessage (Message): for f in self.fields if f.nelem_field is not None ])), - " msg = vapi_msg_alloc(ctx, size);", + " /* cast here required to play nicely with C++ world ... */", + " msg = (%s*)vapi_msg_alloc(ctx, size);" % self.get_c_name(), " if (!msg) {", " return NULL;", " }", @@ -441,7 +445,7 @@ class CMessage (Message): def get_event_cb_func_decl(self): if not self.is_reply(): raise Exception( - "Cannot register event callback for non-reply function") + "Cannot register event callback for non-reply message") if self.has_payload(): return "\n".join([ "void vapi_set_%s_event_cb (" % @@ -498,7 +502,7 @@ class CMessage (Message): ' offsetof(%s, context),' % self.header.get_c_name() if has_context else ' 0,', (' offsetof(%s, payload),' % self.get_c_name()) - if self.has_payload() else '-1,', + if self.has_payload() else ' ~0,', ' sizeof(%s),' % self.get_c_name(), ' (generic_swap_fn_t)%s,' % self.get_swap_to_be_func_name(), ' (generic_swap_fn_t)%s,' % self.get_swap_to_host_func_name(), @@ -529,8 +533,8 @@ vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context) """ -def gen_json_header(parser, logger, j, io): - logger.info("Generating header `%s'" % io.name) +def gen_json_unified_header(parser, logger, j, io, name): + logger.info("Generating header `%s'" % name) orig_stdout = sys.stdout sys.stdout = io include_guard = "__included_%s" % ( @@ -538,131 +542,22 @@ def gen_json_header(parser, logger, j, io): print("#ifndef %s" % include_guard) print("#define %s" % include_guard) print("") - print("#include <vapi_internal.h>") - print("") - if io.name == "vpe.api.vapi.h": - print("static inline vapi_error_e vapi_send_with_control_ping " - "(vapi_ctx_t ctx, void * msg, u32 context);") - print("") - for m in parser.messages_by_json[j].values(): - print("extern vapi_msg_id_t %s;" % m.get_msg_id_name()) - print("") - for t in parser.types_by_json[j].values(): - try: - print("%s" % t.get_c_def()) - print("") - except: - pass - for t in parser.types_by_json[j].values(): - print("%s;" % t.get_swap_to_be_func_decl()) - print("") - print("%s;" % t.get_swap_to_host_func_decl()) - print("") - for m in parser.messages_by_json[j].values(): - print("%s" % m.get_c_def()) - print("") - for m in parser.messages_by_json[j].values(): - if not m.is_reply(): - print("%s;" % m.get_alloc_func_decl()) - print("") - print("%s;" % m.get_op_func_decl()) - if m.has_payload(): - print("%s;" % m.get_swap_payload_to_be_func_decl()) - print("") - print("%s;" % m.get_swap_payload_to_host_func_decl()) - print("") - print("%s;" % m.get_calc_msg_size_func_decl()) - print("") - print("%s;" % m.get_swap_to_host_func_decl()) - print("") - print("%s;" % m.get_swap_to_be_func_decl()) - print("") - for m in parser.messages_by_json[j].values(): - if not m.is_reply(): - continue - print("%s;" % m.get_event_cb_func_decl()) - print("") - - if io.name == "vpe.api.vapi.h": - print("%s" % vapi_send_with_control_ping) - print("") - - print("#endif") - sys.stdout = orig_stdout - - -def gen_json_code(parser, logger, j, io): - logger.info("Generating code `%s'" % io.name) - orig_stdout = sys.stdout - sys.stdout = io - print("#include <%s>" % json_to_header_name(j)) print("#include <stdlib.h>") print("#include <stddef.h>") print("#include <arpa/inet.h>") - print("#include <vapi_internal.h>") - print("#include <vapi_dbg.h>") - print("") - for t in parser.types_by_json[j].values(): - print("%s" % t.get_swap_to_be_func_def()) - print("") - print("%s" % t.get_swap_to_host_func_def()) - print("") - for m in parser.messages_by_json[j].values(): - if m.has_payload(): - print("%s" % m.get_swap_payload_to_be_func_def()) - print("") - print("%s" % m.get_swap_payload_to_host_func_def()) - print("") - print("%s" % m.get_calc_msg_size_func_def()) - print("") - print("%s" % m.get_swap_to_be_func_def()) - print("") - print("%s" % m.get_swap_to_host_func_def()) - print("") - for m in parser.messages_by_json[j].values(): - if m.is_reply(): - continue - print("%s" % m.get_alloc_func_def()) - print("") - print("%s" % m.get_op_func_def()) - print("") + print("#include <vapi/vapi_internal.h>") + print("#include <vapi/vapi.h>") + print("#include <vapi/vapi_dbg.h>") print("") - for m in parser.messages_by_json[j].values(): - print("%s" % m.get_c_constructor()) - print("") - print("") - for m in parser.messages_by_json[j].values(): - if not m.is_reply(): - continue - print("%s;" % m.get_event_cb_func_def()) - print("") - print("") - for m in parser.messages_by_json[j].values(): - print("vapi_msg_id_t %s;" % m.get_msg_id_name()) - sys.stdout = orig_stdout - - -def gen_json_unified_header(parser, logger, j, io): - logger.info("Generating header `%s'" % io.name) - orig_stdout = sys.stdout - sys.stdout = io - include_guard = "__included_%s" % ( - j.replace(".", "_").replace("/", "_").replace("-", "_")) - print("#ifndef %s" % include_guard) - print("#define %s" % include_guard) - print("") - print("#include <vapi_internal.h>") - print("#include <vapi.h>") - print("#include <stdlib.h>") - print("#include <stddef.h>") - print("#include <arpa/inet.h>") - print("#include <vapi_dbg.h>") - if io.name == "vpe.api.vapi.h": + print("#ifdef __cplusplus") + print("extern \"C\" {") + print("#endif") + if name == "vpe.api.vapi.h": print("") print("static inline vapi_error_e vapi_send_with_control_ping " "(vapi_ctx_t ctx, void * msg, u32 context);") else: - print("#include <vpe.api.vapi.h>") + print("#include <vapi/vpe.api.vapi.h>") print("") for m in parser.messages_by_json[j].values(): print("extern vapi_msg_id_t %s;" % m.get_msg_id_name()) @@ -725,46 +620,33 @@ def gen_json_unified_header(parser, logger, j, io): print("") print("") - if io.name == "vpe.api.vapi.h": + if name == "vpe.api.vapi.h": print("%s" % vapi_send_with_control_ping) print("") + print("#ifdef __cplusplus") + print("}") + print("#endif") + print("") print("#endif") sys.stdout = orig_stdout -def json_to_header_name(json_name): +def json_to_c_header_name(json_name): if json_name.endswith(".json"): return "%s.vapi.h" % os.path.splitext(json_name)[0] raise Exception("Unexpected json name `%s'!" % json_name) -def json_to_code_name(json_name): - if json_name.endswith(".json"): - return "%s.vapi.c" % os.path.splitext(json_name)[0] - raise Exception("Unexpected json name `%s'!" % json_name) - - -def gen_c_headers_and_code(parser, logger, prefix): - if prefix == "" or prefix is None: - prefix = "" - else: - prefix = "%s/" % prefix - for j in parser.json_files: - with open('%s%s' % (prefix, json_to_header_name(j)), "w") as io: - gen_json_header(parser, logger, j, io) - with open('%s%s' % (prefix, json_to_code_name(j)), "w") as io: - gen_json_code(parser, logger, j, io) - - def gen_c_unified_headers(parser, logger, prefix): if prefix == "" or prefix is None: prefix = "" else: prefix = "%s/" % prefix for j in parser.json_files: - with open('%s%s' % (prefix, json_to_header_name(j)), "w") as io: - gen_json_unified_header(parser, logger, j, io) + with open('%s%s' % (prefix, json_to_c_header_name(j)), "w") as io: + gen_json_unified_header( + parser, logger, j, io, json_to_c_header_name(j)) if __name__ == '__main__': @@ -784,7 +666,7 @@ if __name__ == '__main__': logger = logging.getLogger("VAPI C GEN") logger.setLevel(log_level) - argparser = argparse.ArgumentParser(description="VPP JSON API parser") + argparser = argparse.ArgumentParser(description="VPP C API generator") argparser.add_argument('files', metavar='api-file', action='append', type=str, help='json api file' '(may be specified multiple times)') diff --git a/src/vpp-api/vapi/vapi_common.h b/src/vpp-api/vapi/vapi_common.h new file mode 100644 index 00000000000..ce64469d02c --- /dev/null +++ b/src/vpp-api/vapi/vapi_common.h @@ -0,0 +1,61 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#ifndef vapi_common_h_included +#define vapi_common_h_included + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum +{ + VAPI_OK = 0, /**< success */ + VAPI_EINVAL, /**< invalid value encountered */ + VAPI_EAGAIN, /**< operation would block */ + VAPI_ENOTSUP, /**< operation not supported */ + VAPI_ENOMEM, /**< out of memory */ + VAPI_ENORESP, /**< no response to request */ + VAPI_EMAP_FAIL, /**< failure while mapping api */ + VAPI_ECON_FAIL, /**< failure while connecting to vpp */ + VAPI_EINCOMPATIBLE, /**< fundamental incompatibility while connecting to vpp + (control ping/control ping reply mismatch) */ + VAPI_MUTEX_FAILURE, /**< failure manipulating internal mutex(es) */ + VAPI_EUSER, /**< user error used for breaking dispatch, + never used by VAPI */ +} vapi_error_e; + +typedef enum +{ + VAPI_MODE_BLOCKING = 1, /**< operations block until response received */ + VAPI_MODE_NONBLOCKING = 2, /**< operations never block */ +} vapi_mode_e; + +typedef enum +{ + VAPI_WAIT_FOR_READ, /**< wait until some message is readable */ + VAPI_WAIT_FOR_WRITE, /**< wait until a message can be written */ + VAPI_WAIT_FOR_READ_WRITE, /**< wait until a read or write can be done */ +} vapi_wait_mode_e; + +typedef int vapi_msg_id_t; + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/vpp-api/vapi/vapi_cpp_gen.py b/src/vpp-api/vapi/vapi_cpp_gen.py new file mode 100755 index 00000000000..6e9f5d3f46f --- /dev/null +++ b/src/vpp-api/vapi/vapi_cpp_gen.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import logging +from vapi_c_gen import CField, CStruct, CSimpleType, CStructType, CMessage, \ + json_to_c_header_name +from vapi_json_parser import JsonParser + + +class CppField(CField): + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + super().__init__(field_name, field_type, array_len, nelem_field) + + +class CppStruct(CStruct): + def __init__(self, name, fields): + super().__init__(name, fields) + + +class CppSimpleType (CSimpleType): + + def __init__(self, name): + super().__init__(name) + + +class CppStructType (CStructType, CppStruct): + def __init__(self, definition, typedict, field_class): + super().__init__(definition, typedict, field_class) + + +class CppMessage (CMessage): + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + super().__init__(logger, definition, typedict, struct_type_class, + simple_type_class, field_class) + + def get_swap_to_be_template_instantiation(self): + return "\n".join([ + "template <> inline void vapi_swap_to_be<%s>(%s *msg)" % + (self.get_c_name(), self.get_c_name()), + "{", + " %s(msg);" % self.get_swap_to_be_func_name(), + "}", + ]) + + def get_swap_to_host_template_instantiation(self): + return "\n".join([ + "template <> inline void vapi_swap_to_host<%s>(%s *msg)" % + (self.get_c_name(), self.get_c_name()), + "{", + " %s(msg);" % self.get_swap_to_host_func_name(), + "}", + ]) + + def get_alloc_template_instantiation(self): + return "\n".join([ + "template <> inline %s* vapi_alloc<%s%s>" + "(Connection &con%s)" % + (self.get_c_name(), self.get_c_name(), + ", size_t" * len(self.get_alloc_vla_param_names()), + "".join([", size_t %s" % n for n in + self.get_alloc_vla_param_names()]) + ), + "{", + " %s* result = %s(con.vapi_ctx%s);" % + (self.get_c_name(), self.get_alloc_func_name(), + "".join([", %s" % n + for n in self.get_alloc_vla_param_names()])), + "#if VAPI_CPP_DEBUG_LEAKS", + " con.on_shm_data_alloc(result);", + "#endif", + " return result;", + "}", + ]) + + def get_cpp_name(self): + return "%s%s" % (self.name[0].upper(), self.name[1:]) + + def get_req_template_name(self): + if self.is_dump(): + template = "Dump" + else: + template = "Request" + + return "%s<%s, %s%s>" % ( + template, + self.get_c_name(), + self.reply.get_c_name(), + "".join([", size_t"] * len(self.get_alloc_vla_param_names())) + ) + + def get_req_template_instantiation(self): + return "template class %s;" % self.get_req_template_name() + + def get_type_alias(self): + return "using %s = %s;" % ( + self.get_cpp_name(), self.get_req_template_name()) + + def get_reply_template_name(self): + return "Msg<%s>" % (self.get_c_name()) + + def get_reply_type_alias(self): + return "using %s = %s;" % ( + self.get_cpp_name(), self.get_reply_template_name()) + + def get_msg_class_instantiation(self): + return "template class Msg<%s>;" % self.get_c_name() + + def get_get_msg_id_t_instantiation(self): + return "\n".join([ + ("template <> inline vapi_msg_id_t vapi_get_msg_id_t<%s>()" + % self.get_c_name()), + "{", + " return ::%s; " % self.get_msg_id_name(), + "}", + "", + ("template <> inline vapi_msg_id_t " + "vapi_get_msg_id_t<Msg<%s>>()" % self.get_c_name()), + "{", + " return ::%s; " % self.get_msg_id_name(), + "}", + ]) + + def get_cpp_constructor(self): + return '\n'.join([ + ('static void __attribute__((constructor)) ' + '__vapi_cpp_constructor_%s()' + % self.name), + '{', + (' vapi::vapi_msg_set_msg_id<%s>(%s);' % ( + self.get_c_name(), self.get_msg_id_name())), + '}', + ]) + + +def gen_json_header(parser, logger, j, io, gen_h_prefix, add_debug_comments): + logger.info("Generating header `%s'" % io.name) + orig_stdout = sys.stdout + sys.stdout = io + include_guard = "__included_hpp_%s" % ( + j.replace(".", "_").replace("/", "_").replace("-", "_")) + print("#ifndef %s" % include_guard) + print("#define %s" % include_guard) + print("") + print("#include <vapi/vapi.hpp>") + print("#include <%s%s>" % (gen_h_prefix, json_to_c_header_name(j))) + print("") + print("namespace vapi {") + print("") + for m in parser.messages_by_json[j].values(): + # utility functions need to go first, otherwise internal instantiation + # causes headaches ... + if add_debug_comments: + print("/* m.get_swap_to_be_template_instantiation() */") + print("%s" % m.get_swap_to_be_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_swap_to_host_template_instantiation() */") + print("%s" % m.get_swap_to_host_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_get_msg_id_t_instantiation() */") + print("%s" % m.get_get_msg_id_t_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_cpp_constructor() */") + print("%s" % m.get_cpp_constructor()) + print("") + if not m.is_reply(): + if add_debug_comments: + print("/* m.get_alloc_template_instantiation() */") + print("%s" % m.get_alloc_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_msg_class_instantiation() */") + print("%s" % m.get_msg_class_instantiation()) + print("") + if m.is_reply(): + if add_debug_comments: + print("/* m.get_reply_type_alias() */") + print("%s" % m.get_reply_type_alias()) + continue + if add_debug_comments: + print("/* m.get_req_template_instantiation() */") + print("%s" % m.get_req_template_instantiation()) + print("") + if add_debug_comments: + print("/* m.get_type_alias() */") + print("%s" % m.get_type_alias()) + print("") + print("}") # namespace vapi + + print("#endif") + sys.stdout = orig_stdout + + +def json_to_cpp_header_name(json_name): + if json_name.endswith(".json"): + return "%s.vapi.hpp" % os.path.splitext(json_name)[0] + raise Exception("Unexpected json name `%s'!" % json_name) + + +def gen_cpp_headers(parser, logger, prefix, gen_h_prefix, + add_debug_comments=False): + if prefix == "" or prefix is None: + prefix = "" + else: + prefix = "%s/" % prefix + if gen_h_prefix is None: + gen_h_prefix = "" + else: + gen_h_prefix = "%s/" % gen_h_prefix + for j in parser.json_files: + with open('%s%s' % (prefix, json_to_cpp_header_name(j)), "w") as io: + gen_json_header(parser, logger, j, io, + gen_h_prefix, add_debug_comments) + + +if __name__ == '__main__': + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + if verbose >= 2: + log_level = 10 + elif verbose == 1: + log_level = 20 + else: + log_level = 40 + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("VAPI CPP GEN") + logger.setLevel(log_level) + + argparser = argparse.ArgumentParser(description="VPP C++ API generator") + argparser.add_argument('files', metavar='api-file', action='append', + type=str, help='json api file' + '(may be specified multiple times)') + argparser.add_argument('--prefix', action='store', default=None, + help='path prefix') + argparser.add_argument('--gen-h-prefix', action='store', default=None, + help='generated C header prefix') + args = argparser.parse_args() + + jsonparser = JsonParser(logger, args.files, + simple_type_class=CppSimpleType, + struct_type_class=CppStructType, + field_class=CppField, + message_class=CppMessage) + + gen_cpp_headers(jsonparser, logger, args.prefix, args.gen_h_prefix) + + for e in jsonparser.exceptions: + logger.error(e) diff --git a/src/vpp-api/vapi/vapi_dbg.h b/src/vpp-api/vapi/vapi_dbg.h index 95a8008915b..ec3a30065d4 100644 --- a/src/vpp-api/vapi/vapi_dbg.h +++ b/src/vpp-api/vapi/vapi_dbg.h @@ -22,6 +22,7 @@ #define VAPI_DEBUG (0) #define VAPI_DEBUG_CONNECT (0) #define VAPI_DEBUG_ALLOC (0) +#define VAPI_CPP_DEBUG_LEAKS (0) #if VAPI_DEBUG #include <stdio.h> diff --git a/src/vpp-api/vapi/vapi_doc.md b/src/vpp-api/vapi/vapi_doc.md new file mode 100644 index 00000000000..0e7e29dde01 --- /dev/null +++ b/src/vpp-api/vapi/vapi_doc.md @@ -0,0 +1,155 @@ +# VPP API module {#vapi_doc} + +## Overview + +VPP API module allows communicating with VPP over shared memory interface. +The API consists of 3 parts: + +* common code - low-level API +* generated code - high-level API +* code generator - to generate your own high-level API e.g. for custom plugins + +### Common code + +#### C common code + +C common code represents the basic, low-level API, providing functions to +connect/disconnect, perform message discovery and send/receive messages. +The C variant is in vapi.h. + +#### C++ common code + +C++ is provided by vapi.hpp and contains high-level API templates, +which are specialized by generated code. + +### Generated code + +Each API file present in the source tree is automatically translated to JSON +file, which the code generator parses and generates either C (`vapi_c_gen.py`) +or C++ (`vapi_cpp_gen.py`) code. + +This can then be included in the client application and provides convenient way +to interact with VPP. This includes: + +* automatic byte-swapping +* automatic request-response matching based on context +* automatic casts to appropriate types (type-safety) when calling callbacks +* automatic sending of control-pings for dump messages + +The API supports two modes of operation: + +* blocking +* non-blocking + +In blocking mode, whenever an operation is initiated, the code waits until it +can finish. This means that when sending a message, the call blocks until +the message can be written to shared memory. Similarly, receiving a message +blocks until a message becomes available. On higher level, this also means that +when doing a request (e.g. `show_version`), the call blocks until a response +comes back (e.g. `show_version_reply`). + +In non-blocking mode, these are decoupled, the API returns VAPI_EAGAIN whenever +an operation cannot be performed and after sending a request, it's up to +the client to wait for and process a response. + +### Code generator + +Python code generator comes in two flavors - C and C++ and generates high-level +API headers. All the code is stored in the headers. + +## Usage + +### Low-level API + +Refer to inline API documentation in doxygen format in `vapi.h` header +for description of functions. It's recommened to use the safer, high-level +API provided by specialized headers (e.g. `vpe.api.vapi.h` +or `vpe.api.vapi.hpp`). + +#### C high-level API + +##### Callbacks + +The C high-level API is strictly callback-based for maximum efficiency. +Whenever an operation is initiated a callback with a callback context is part +of that operation. The callback is then invoked when the response (or multiple +responses) arrive which are tied to the request. Also, callbacks are invoked +whenever an event arrives, if such callback is registered. All the pointers +to responses/events point to shared memory and are immediately freed after +callback finishes so the client needs to extract/copy any data in which it +is interested in. + +#### Blocking mode + +In simple blocking mode, the whole operation (being a simple request or a dump) +is finished and it's callback is called (potentially multiple times for dumps) +during function call. + +Example pseudo-code for a simple request in this mode: + +` +vapi_show_version(message, callback, callback_context) + +1. generate unique internal context and assign it to message.header.context +2. byteswap the message to network byte order +3. send message to vpp (message is now consumed and vpp will free it) +4. create internal "outstanding request context" which stores the callback, + callback context and the internal context value +5. call dispatch, which in this mode receives and processes responses until + the internal "outstanding requests" queue is empty. In blocking mode, this + queue always contains at most one item. +` + +**Note**: it's possible for different - unrelated callbacks to be called before +the response callbacks is called in cases where e.g. events are stored +in shared memory queue. + +#### Non-blocking mode + +In non-blocking mode, all the requests are only byte-swapped and the context +information along with callbacks is stored locally (so in the above example, +only steps 1-4 are executed and step 5 is skipped). Calling dispatch is up to +the client application. This allows to alternate between sending/receiving +messages or have a dedicated thread which calls dispatch. + +### C++ high level API + +#### Callbacks + +In C++ API, the response is automatically tied to the corresponding `Request`, +`Dump` or `Event_registration` object. Optionally a callback might be specified, +which then gets called when the response is received. + +**Note**: responses take up shared memory space and should be freed either +manually (in case of result sets) or automatically (by destroying the object +owning them) when no longer needed. Once a Request or Dump object was executed, +it cannot be re-sent, since the request itself (stores in shared memory) +is consumed by vpp and inaccessible (set to nullptr) anymore. + +#### Usage + +#### Requests & dumps + +0. Create on object of `Connection` type and call `connect()` to connect to vpp. +1. Create an object of `Request` or `Dump` type using it's typedef (e.g. + `Show_version`) +2. Use `get_request()` to obtain and manipulate the underlying request if + required. +3. Issue `execute()` to send the request. +4. Use either `wait_for_response()` or `dispatch()` to wait for the response. +5. Use `get_response_state()` to get the state and `get_response()` to read + the response. + +#### Events + +0. Create a `Connection` and execute the appropriate `Request` to subscribe to + events (e.g. `Want_stats`) +1. Create an `Event_registration` with a template argument being the type of + event you are insterested in. +2. Call `dispatch()` or `wait_for_response()` to wait for the event. A callback + will be called when an event occurs (if passed to `Event_registration()` + constructor). Alternatively, read the result set. + +**Note**: events stored in the result set take up space in shared memory +and should be freed regularly (e.g. in the callback, once the event is +processed). diff --git a/src/vpp-api/vapi/vapi_internal.h b/src/vpp-api/vapi/vapi_internal.h index 5b85788db8a..2c51c673d9e 100644 --- a/src/vpp-api/vapi/vapi_internal.h +++ b/src/vpp-api/vapi/vapi_internal.h @@ -18,6 +18,7 @@ #ifndef VAPI_INTERNAL_H #define VAPI_INTERNAL_H +#include <endian.h> #include <string.h> #include <vppinfra/types.h> @@ -31,6 +32,10 @@ * time.. */ +#ifdef __cplusplus +extern "C" { +#endif + struct vapi_ctx_s; typedef struct __attribute__ ((__packed__)) @@ -71,7 +76,7 @@ vapi_type_msg_header2_t_ntoh (vapi_type_msg_header2_t * h) } -#include <vapi.h> +#include <vapi/vapi.h> typedef vapi_error_e (*vapi_cb_t) (struct vapi_ctx_s *, void *, vapi_error_e, bool, void *); @@ -85,8 +90,8 @@ typedef struct const char *name_with_crc; size_t name_with_crc_len; bool has_context; - size_t context_offset; - size_t payload_offset; + int context_offset; + int payload_offset; size_t size; generic_swap_fn_t swap_to_be; generic_swap_fn_t swap_to_host; @@ -102,12 +107,12 @@ typedef struct void (*swap_to_host) (void *payload); } vapi_event_desc_t; -extern bool *__vapi_msg_is_with_context; - vapi_msg_id_t vapi_register_msg (vapi_message_desc_t * msg); u16 vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id); +vapi_msg_id_t vapi_lookup_vapi_msg_id_t (vapi_ctx_t ctx, u16 vl_msg_id); int vapi_get_client_index (vapi_ctx_t ctx); bool vapi_is_nonblocking (vapi_ctx_t ctx); +bool vapi_requests_empty (vapi_ctx_t ctx); bool vapi_requests_full (vapi_ctx_t ctx); size_t vapi_get_request_count (vapi_ctx_t ctx); size_t vapi_get_max_request_count (vapi_ctx_t ctx); @@ -119,8 +124,15 @@ void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *payload); void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *payload); size_t vapi_get_message_size (vapi_msg_id_t id); size_t vapi_get_context_offset (vapi_msg_id_t id); +bool vapi_msg_is_with_context (vapi_msg_id_t id); +size_t vapi_get_message_count(); +const char *vapi_get_msg_name(vapi_msg_id_t id); vapi_error_e vapi_producer_lock (vapi_ctx_t ctx); vapi_error_e vapi_producer_unlock (vapi_ctx_t ctx); +#ifdef __cplusplus +} +#endif + #endif diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py index 57a2238322b..1e17c7a5b5a 100644 --- a/src/vpp-api/vapi/vapi_json_parser.py +++ b/src/vpp-api/vapi/vapi_json_parser.py @@ -90,6 +90,7 @@ class Message: def __init__(self, logger, definition, typedict, struct_type_class, simple_type_class, field_class): + self.request = None self.logger = logger m = definition logger.debug("Parsing message definition `%s'" % m) @@ -292,6 +293,7 @@ class JsonParser: if not m.is_reply(): try: m.reply = self.get_reply(n) + m.reply.request = m except: raise ParseError( "Cannot find reply to message `%s'" % n) diff --git a/test/ext/Makefile b/test/ext/Makefile index 4a45fef6782..a188427a62f 100644 --- a/test/ext/Makefile +++ b/test/ext/Makefile @@ -1,17 +1,31 @@ BINDIR = $(BR)/vapi_test/ -BIN = $(addprefix $(BINDIR), vapi_test) +CBIN = $(addprefix $(BINDIR), vapi_c_test) +CPPBIN = $(addprefix $(BINDIR), vapi_cpp_test) + LIBS = -L$(VPP_TEST_BUILD_DIR)/vpp/.libs/ -L$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/ -lvppinfra -lvlibmemoryclient -lsvm -lpthread -lcheck -lsubunit -lrt -lm -lvapiclient -CFLAGS = -ggdb -O0 -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi -I$(WS_ROOT)/src/vpp-api/vapi/ +CFLAGS = -std=gnu99 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR) +CPPFLAGS = -std=c++11 -g -Wall -pthread -I$(WS_ROOT)/src -I$(VPP_TEST_INSTALL_PATH)/vpp/include -I$(BINDIR) -all: $(BIN) +all: $(CBIN) $(CPPBIN) $(BINDIR): mkdir -p $(BINDIR) -SRC = vapi_test.c +CSRC = vapi_c_test.c + +fake.api.vapi.h: fake.api.json $(BINDIR) $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py + $(WS_ROOT)/src/vpp-api/vapi/vapi_c_gen.py --prefix $(BINDIR) $< + +fake.api.vapi.hpp: fake.api.json $(BINDIR) $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py + $(WS_ROOT)/src/vpp-api/vapi/vapi_cpp_gen.py --prefix $(BINDIR) $< + +$(CBIN): $(CSRC) $(BINDIR) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so fake.api.vapi.h + $(CC) -o $@ $(CFLAGS) $(CSRC) $(LIBS) + +CPPSRC = vapi_cpp_test.cpp -$(BIN): $(SRC) $(BINDIR) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so - gcc -ggdb -o $@ $(SRC) $(CFLAGS) $(LIBS) +$(CPPBIN): $(CPPSRC) $(BINDIR) $(VPP_TEST_BUILD_DIR)/vpp/vpp-api/vapi/.libs/libvapiclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvppinfra.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libvlibmemoryclient.so $(VPP_TEST_BUILD_DIR)/vpp/.libs/libsvm.so fake.api.vapi.hpp + $(CXX) -o $@ $(CPPFLAGS) $(CPPSRC) $(LIBS) clean: rm -rf $(BINDIR) diff --git a/test/ext/fake.api.json b/test/ext/fake.api.json new file mode 100644 index 00000000000..3e8d6a95a13 --- /dev/null +++ b/test/ext/fake.api.json @@ -0,0 +1,35 @@ +{ + "types" : [ + + ], + "messages" : [ + ["test_fake_msg", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ["u8", "dummy", 256], + {"crc" : "0xcafebafe"} + ], + ["test_fake_msg_reply", + ["u16", "_vl_msg_id"], + ["u32", "context"], + ["i32", "retval"], + {"crc" : "0xcafebafe"} + ], + ["test_fake_dump", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ["u32", "dummy"], + {"crc" : "0xcafebafe"} + ], + ["test_fake_details", + ["u16", "_vl_msg_id"], + ["u32", "client_index"], + ["u32", "context"], + ["u32", "dummy"], + {"crc" : "0xcafebafe"} + ] + ], +"vl_api_version" :"0x224c7aad" +} diff --git a/test/ext/vapi_test.c b/test/ext/vapi_c_test.c index eca6be7d6cc..622b617b394 100644 --- a/test/ext/vapi_test.c +++ b/test/ext/vapi_c_test.c @@ -22,16 +22,18 @@ #include <assert.h> #include <setjmp.h> #include <check.h> -#include <vpp-api/vapi/vapi.h> -#include <vpe.api.vapi.h> -#include <interface.api.vapi.h> -#include <l2.api.vapi.h> -#include <stats.api.vapi.h> +#include <vapi/vapi.h> +#include <vapi/vpe.api.vapi.h> +#include <vapi/interface.api.vapi.h> +#include <vapi/l2.api.vapi.h> +#include <vapi/stats.api.vapi.h> +#include <fake.api.vapi.h> DEFINE_VAPI_MSG_IDS_VPE_API_JSON; DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON; DEFINE_VAPI_MSG_IDS_L2_API_JSON; DEFINE_VAPI_MSG_IDS_STATS_API_JSON; +DEFINE_VAPI_MSG_IDS_FAKE_API_JSON; static char *app_name = NULL; static char *api_prefix = NULL; @@ -521,9 +523,8 @@ START_TEST (test_show_version_1) size_t size; rv = vapi_recv (ctx, (void *) &resp, &size); ck_assert_int_eq (VAPI_OK, rv); - vapi_payload_show_version_reply *payload = &resp->payload; int dummy; - show_version_cb (NULL, &dummy, VAPI_OK, true, payload); + show_version_cb (NULL, &dummy, VAPI_OK, true, &resp->payload); vapi_msg_free (ctx, resp); } @@ -1069,6 +1070,16 @@ START_TEST (test_no_response_2) } END_TEST; + +START_TEST (test_unsupported) +{ + printf ("--- Unsupported messages ---\n"); + bool available = vapi_is_msg_available (ctx, vapi_msg_id_test_fake_msg); + ck_assert_int_eq (false, available); +} + +END_TEST; + Suite * test_suite (void) { @@ -1115,6 +1126,11 @@ test_suite (void) tcase_add_test (tc_nonblock, test_no_response_2); suite_add_tcase (s, tc_nonblock); + TCase *tc_unsupported = tcase_create ("Unsupported message"); + tcase_add_checked_fixture (tc_unsupported, setup_blocking, teardown); + tcase_add_test (tc_unsupported, test_unsupported); + suite_add_tcase (s, tc_unsupported); + return s; } diff --git a/test/ext/vapi_cpp_test.cpp b/test/ext/vapi_cpp_test.cpp new file mode 100644 index 00000000000..14c35d5bedd --- /dev/null +++ b/test/ext/vapi_cpp_test.cpp @@ -0,0 +1,591 @@ +/* + *------------------------------------------------------------------ + * Copyright (c) 2017 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *------------------------------------------------------------------ + */ + +#include <memory> +#include <stdio.h> +#include <unistd.h> +#include <assert.h> +#include <setjmp.h> +#include <check.h> +#include <vapi/vapi.hpp> +#include <vapi/vpe.api.vapi.hpp> +#include <vapi/interface.api.vapi.hpp> +#include <vapi/stats.api.vapi.hpp> +#include <fake.api.vapi.hpp> + +DEFINE_VAPI_MSG_IDS_VPE_API_JSON; +DEFINE_VAPI_MSG_IDS_INTERFACE_API_JSON; +DEFINE_VAPI_MSG_IDS_STATS_API_JSON; +DEFINE_VAPI_MSG_IDS_FAKE_API_JSON; + +static char *app_name = nullptr; +static char *api_prefix = nullptr; +static const int max_outstanding_requests = 32; +static const int response_queue_size = 32; + +using namespace vapi; + +void verify_show_version_reply (const Show_version_reply &r) +{ + auto &p = r.get_payload (); + printf ("show_version_reply: program: `%s', version: `%s', build directory: " + "`%s', build date: `%s'\n", + p.program, p.version, p.build_directory, p.build_date); + ck_assert_str_eq ("vpe", (char *)p.program); +} + +Connection con; + +void setup (void) +{ + vapi_error_e rv = con.connect ( + app_name, api_prefix, max_outstanding_requests, response_queue_size); + ck_assert_int_eq (VAPI_OK, rv); +} + +void teardown (void) +{ + con.disconnect (); +} + +START_TEST (test_show_version_1) +{ + printf ("--- Show version by reading response associated to request ---\n"); + Show_version sv (con); + vapi_error_e rv = sv.execute (); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (sv); + ck_assert_int_eq (VAPI_OK, rv); + auto &r = sv.get_response (); + verify_show_version_reply (r); +} + +END_TEST; + +struct Show_version_cb +{ + Show_version_cb () : called{0} {}; + int called; + vapi_error_e operator() (Show_version &sv) + { + auto &r = sv.get_response (); + verify_show_version_reply (r); + ++called; + return VAPI_OK; + } +}; + +START_TEST (test_show_version_2) +{ + printf ("--- Show version by getting a callback ---\n"); + Show_version_cb cb; + Show_version sv (con, std::ref (cb)); + vapi_error_e rv = sv.execute (); + ck_assert_int_eq (VAPI_OK, rv); + con.dispatch (sv); + ck_assert_int_eq (1, cb.called); +} + +END_TEST; + +START_TEST (test_loopbacks_1) +{ + printf ("--- Create/delete loopbacks by waiting for response ---\n"); + const auto num_ifs = 5; + u8 mac_addresses[num_ifs][6]; + memset (&mac_addresses, 0, sizeof (mac_addresses)); + u32 sw_if_indexes[num_ifs]; + memset (&sw_if_indexes, 0xff, sizeof (sw_if_indexes)); + for (int i = 0; i < num_ifs; ++i) + { + memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6); + mac_addresses[i][5] = i; + } + for (int i = 0; i < num_ifs; ++i) + { + Create_loopback cl (con); + auto &p = cl.get_request ().get_payload (); + memcpy (p.mac_address, mac_addresses[i], sizeof (p.mac_address)); + auto e = cl.execute (); + ck_assert_int_eq (VAPI_OK, e); + vapi_error_e rv = con.wait_for_response (cl); + ck_assert_int_eq (VAPI_OK, rv); + auto &rp = cl.get_response ().get_payload (); + ck_assert_int_eq (0, rp.retval); + sw_if_indexes[i] = rp.sw_if_index; + } + for (int i = 0; i < num_ifs; ++i) + { + printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> " + "sw_if_index %u\n", + mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2], + mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5], + sw_if_indexes[i]); + } + + { // new context + bool seen[num_ifs] = {0}; + Sw_interface_dump d (con); + auto &p = d.get_request ().get_payload (); + p.name_filter_valid = 0; + memset (p.name_filter, 0, sizeof (p.name_filter)); + auto rv = d.execute (); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (d); + ck_assert_int_eq (VAPI_OK, rv); + auto &rs = d.get_result_set (); + for (auto &r : rs) + { + auto &p = r.get_payload (); + for (int i = 0; i < num_ifs; ++i) + { + if (sw_if_indexes[i] == p.sw_if_index) + { + ck_assert_int_eq (0, seen[i]); + seen[i] = true; + } + } + } + for (int i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, seen[i]); + } + } + + for (int i = 0; i < num_ifs; ++i) + { + Delete_loopback dl (con); + dl.get_request ().get_payload ().sw_if_index = sw_if_indexes[i]; + auto rv = dl.execute (); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (dl); + ck_assert_int_eq (VAPI_OK, rv); + auto &response = dl.get_response (); + auto rp = response.get_payload (); + ck_assert_int_eq (0, rp.retval); + printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]); + } + + { // new context + Sw_interface_dump d (con); + auto &p = d.get_request ().get_payload (); + p.name_filter_valid = 0; + memset (p.name_filter, 0, sizeof (p.name_filter)); + auto rv = d.execute (); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (d); + ck_assert_int_eq (VAPI_OK, rv); + auto &rs = d.get_result_set (); + for (auto &r : rs) + { + auto &p = r.get_payload (); + for (int i = 0; i < num_ifs; ++i) + { + ck_assert_int_ne (sw_if_indexes[i], p.sw_if_index); + } + } + } +} + +END_TEST; + +struct Create_loopback_cb +{ + Create_loopback_cb () : called{0}, sw_if_index{0} {}; + int called; + u32 sw_if_index; + bool seen; + vapi_error_e operator() (Create_loopback &cl) + { + auto &r = cl.get_response (); + sw_if_index = r.get_payload ().sw_if_index; + ++called; + return VAPI_OK; + } +}; + +struct Delete_loopback_cb +{ + Delete_loopback_cb () : called{0}, sw_if_index{0} {}; + int called; + u32 sw_if_index; + bool seen; + vapi_error_e operator() (Delete_loopback &dl) + { + auto &r = dl.get_response (); + ck_assert_int_eq (0, r.get_payload ().retval); + ++called; + return VAPI_OK; + } +}; + +template <int num_ifs> struct Sw_interface_dump_cb +{ + Sw_interface_dump_cb (std::array<Create_loopback_cb, num_ifs> &cbs) + : called{0}, cbs{cbs} {}; + int called; + std::array<Create_loopback_cb, num_ifs> &cbs; + vapi_error_e operator() (Sw_interface_dump &d) + { + for (auto &y : cbs) + { + y.seen = false; + } + for (auto &x : d.get_result_set ()) + { + auto &p = x.get_payload (); + for (auto &y : cbs) + { + if (p.sw_if_index == y.sw_if_index) + { + y.seen = true; + } + } + } + for (auto &y : cbs) + { + ck_assert_int_eq (true, y.seen); + } + ++called; + return VAPI_OK; + } +}; + +START_TEST (test_loopbacks_2) +{ + printf ("--- Create/delete loopbacks by getting a callback ---\n"); + const auto num_ifs = 5; + u8 mac_addresses[num_ifs][6]; + memset (&mac_addresses, 0, sizeof (mac_addresses)); + for (int i = 0; i < num_ifs; ++i) + { + memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6); + mac_addresses[i][5] = i; + } + std::array<Create_loopback_cb, num_ifs> ccbs; + std::array<std::unique_ptr<Create_loopback>, num_ifs> clcs; + for (int i = 0; i < num_ifs; ++i) + { + Create_loopback *cl = new Create_loopback (con, std::ref (ccbs[i])); + clcs[i].reset (cl); + auto &p = cl->get_request ().get_payload (); + memcpy (p.mac_address, mac_addresses[i], sizeof (p.mac_address)); + auto e = cl->execute (); + ck_assert_int_eq (VAPI_OK, e); + } + con.dispatch (); + for (int i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, ccbs[i].called); + printf ("Created loopback with MAC %02x:%02x:%02x:%02x:%02x:%02x --> " + "sw_if_index %u\n", + mac_addresses[i][0], mac_addresses[i][1], mac_addresses[i][2], + mac_addresses[i][3], mac_addresses[i][4], mac_addresses[i][5], + ccbs[i].sw_if_index); + } + + Sw_interface_dump_cb<num_ifs> swdcb (ccbs); + Sw_interface_dump d (con, std::ref (swdcb)); + auto &p = d.get_request ().get_payload (); + p.name_filter_valid = 0; + memset (p.name_filter, 0, sizeof (p.name_filter)); + auto rv = d.execute (); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (d); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_ne (0, swdcb.called); + std::array<Delete_loopback_cb, num_ifs> dcbs; + std::array<std::unique_ptr<Delete_loopback>, num_ifs> dlcs; + for (int i = 0; i < num_ifs; ++i) + { + Delete_loopback *dl = new Delete_loopback (con, std::ref (dcbs[i])); + dlcs[i].reset (dl); + auto &p = dl->get_request ().get_payload (); + p.sw_if_index = ccbs[i].sw_if_index; + dcbs[i].sw_if_index = ccbs[i].sw_if_index; + auto e = dl->execute (); + ck_assert_int_eq (VAPI_OK, e); + } + con.dispatch (); + for (auto &x : dcbs) + { + ck_assert_int_eq (true, x.called); + printf ("Deleted loopback with sw_if_index %u\n", x.sw_if_index); + } + + { // new context + Sw_interface_dump d (con); + auto &p = d.get_request ().get_payload (); + p.name_filter_valid = 0; + memset (p.name_filter, 0, sizeof (p.name_filter)); + auto rv = d.execute (); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (d); + ck_assert_int_eq (VAPI_OK, rv); + auto &rs = d.get_result_set (); + for (auto &r : rs) + { + auto &p = r.get_payload (); + for (int i = 0; i < num_ifs; ++i) + { + ck_assert_int_ne (ccbs[i].sw_if_index, p.sw_if_index); + } + } + } +} + +END_TEST; + +START_TEST (test_stats_1) +{ + printf ("--- Receive single stats by waiting for response ---\n"); + Want_stats ws (con); + auto &payload = ws.get_request ().get_payload (); + payload.enable_disable = 1; + payload.pid = getpid (); + auto rv = ws.execute (); + ck_assert_int_eq (VAPI_OK, rv); + Event_registration<Vnet_interface_simple_counters> sc (con); + rv = con.wait_for_response (sc); + ck_assert_int_eq (VAPI_OK, rv); + auto &rs = sc.get_result_set (); + int count = 0; + for (auto &r : rs) + { + printf ("simple counters: first_sw_if_index=%u\n", + r.get_payload ().first_sw_if_index); + ++count; + } + ck_assert_int_ne (0, count); +} + +END_TEST; + +struct Vnet_interface_simple_counters_cb +{ + Vnet_interface_simple_counters_cb () : called{0} {}; + int called; + vapi_error_e + operator() (Event_registration<Vnet_interface_simple_counters> &e) + { + ++called; + auto &rs = e.get_result_set (); + int count = 0; + for (auto &r : rs) + { + printf ("simple counters: first_sw_if_index=%u\n", + r.get_payload ().first_sw_if_index); + ++count; + } + ck_assert_int_ne (0, count); + return VAPI_OK; + } +}; + +START_TEST (test_stats_2) +{ + printf ("--- Receive single stats by getting a callback ---\n"); + Want_stats ws (con); + auto &payload = ws.get_request ().get_payload (); + payload.enable_disable = 1; + payload.pid = getpid (); + auto rv = ws.execute (); + ck_assert_int_eq (VAPI_OK, rv); + Vnet_interface_simple_counters_cb cb; + Event_registration<Vnet_interface_simple_counters> sc (con, std::ref (cb)); + rv = con.wait_for_response (sc); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_ne (0, cb.called); +} + +END_TEST; + +struct Vnet_interface_simple_counters_2_cb +{ + Vnet_interface_simple_counters_2_cb () : called{0}, total{0} {}; + int called; + int total; + vapi_error_e + operator() (Event_registration<Vnet_interface_simple_counters> &e) + { + ++called; + auto &rs = e.get_result_set (); + int count = 0; + for (auto &r : rs) + { + printf ("simple counters: first_sw_if_index=%u\n", + r.get_payload ().first_sw_if_index); + ++count; + } + rs.free_all_responses (); + ck_assert_int_ne (0, count); + total += count; + return VAPI_OK; + } +}; + +START_TEST (test_stats_3) +{ + printf ( + "--- Receive single stats by getting a callback - clear results ---\n"); + Want_stats ws (con); + auto &payload = ws.get_request ().get_payload (); + payload.enable_disable = 1; + payload.pid = getpid (); + auto rv = ws.execute (); + ck_assert_int_eq (VAPI_OK, rv); + Vnet_interface_simple_counters_2_cb cb; + Event_registration<Vnet_interface_simple_counters> sc (con, std::ref (cb)); + for (int i = 0; i < 5; ++i) + { + rv = con.wait_for_response (sc); + } + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (5, cb.called); + ck_assert_int_eq (5, cb.total); +} + +END_TEST; + +START_TEST (test_stats_4) +{ + printf ("--- Receive multiple stats by waiting for response ---\n"); + Want_stats ws (con); + auto &payload = ws.get_request ().get_payload (); + payload.enable_disable = 1; + payload.pid = getpid (); + auto rv = ws.execute (); + ck_assert_int_eq (VAPI_OK, rv); + Event_registration<Vnet_interface_simple_counters> sc (con); + Event_registration<Vnet_interface_combined_counters> cc (con); + rv = con.wait_for_response (sc); + ck_assert_int_eq (VAPI_OK, rv); + rv = con.wait_for_response (cc); + ck_assert_int_eq (VAPI_OK, rv); + int count = 0; + for (auto &r : sc.get_result_set ()) + { + printf ("simple counters: first_sw_if_index=%u\n", + r.get_payload ().first_sw_if_index); + ++count; + } + ck_assert_int_ne (0, count); + count = 0; + for (auto &r : cc.get_result_set ()) + { + printf ("combined counters: first_sw_if_index=%u\n", + r.get_payload ().first_sw_if_index); + ++count; + } + ck_assert_int_ne (0, count); +} + +END_TEST; + +START_TEST (test_unsupported) +{ + printf ("--- Unsupported messages ---\n"); + bool thrown = false; + try + { + Test_fake_msg fake (con); + } + catch (const Msg_not_available_exception &) + { + thrown = true; + printf ("Constructing unsupported msg not possible - test pass.\n"); + } + ck_assert_int_eq (true, thrown); + thrown = false; + try + { + Test_fake_dump fake (con); + } + catch (const Msg_not_available_exception &) + { + thrown = true; + printf ("Constructing unsupported dump not possible - test pass.\n"); + } + ck_assert_int_eq (true, thrown); + thrown = false; + try + { + Event_registration<Test_fake_details> fake (con); + } + catch (const Msg_not_available_exception &) + { + thrown = true; + printf ("Constructing unsupported event registration not possible - " + "test pass.\n"); + } + ck_assert_int_eq (true, thrown); +} + +END_TEST; + +Suite *test_suite (void) +{ + Suite *s = suite_create ("VAPI test"); + + TCase *tc_cpp_api = tcase_create ("C++ API"); + tcase_set_timeout (tc_cpp_api, 25); + tcase_add_checked_fixture (tc_cpp_api, setup, teardown); + tcase_add_test (tc_cpp_api, test_show_version_1); + tcase_add_test (tc_cpp_api, test_show_version_2); + tcase_add_test (tc_cpp_api, test_loopbacks_1); + tcase_add_test (tc_cpp_api, test_loopbacks_2); + tcase_add_test (tc_cpp_api, test_stats_1); + tcase_add_test (tc_cpp_api, test_stats_2); + tcase_add_test (tc_cpp_api, test_stats_3); + tcase_add_test (tc_cpp_api, test_stats_4); + tcase_add_test (tc_cpp_api, test_unsupported); + suite_add_tcase (s, tc_cpp_api); + + return s; +} + +int main (int argc, char *argv[]) +{ + if (3 != argc) + { + printf ("Invalid argc==`%d'\n", argc); + return EXIT_FAILURE; + } + app_name = argv[1]; + api_prefix = argv[2]; + printf ("App name: `%s', API prefix: `%s'\n", app_name, api_prefix); + + int number_failed; + Suite *s; + SRunner *sr; + + s = test_suite (); + sr = srunner_create (s); + + srunner_run_all (sr, CK_NORMAL); + number_failed = srunner_ntests_failed (sr); + srunner_free (sr); + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/test/test_vapi.py b/test/test_vapi.py index 86c1ee06fc7..d8e1ebe000f 100644 --- a/test/test_vapi.py +++ b/test/test_vapi.py @@ -45,17 +45,45 @@ class Worker(Thread): class VAPITestCase(VppTestCase): """ VAPI test """ - def test_vapi(self): - """ run VAPI tests """ + def test_vapi_c(self): + """ run C VAPI tests """ var = "BR" built_root = os.getenv(var, None) self.assertIsNotNone(built_root, "Environment variable `%s' not set" % var) - executable = "%s/vapi_test/vapi_test" % built_root + executable = "%s/vapi_test/vapi_c_test" % built_root worker = Worker( [executable, "vapi client", self.shm_prefix], self.logger) worker.start() - timeout = 45 + timeout = 60 + worker.join(timeout) + self.logger.info("Worker result is `%s'" % worker.result) + error = False + if worker.result is None: + try: + error = True + self.logger.error( + "Timeout! Worker did not finish in %ss" % timeout) + os.killpg(os.getpgid(worker.process.pid), signal.SIGTERM) + worker.join() + except: + raise Exception("Couldn't kill worker-spawned process") + if error: + raise Exception( + "Timeout! Worker did not finish in %ss" % timeout) + self.assert_equal(worker.result, 0, "Binary test return code") + + def test_vapi_cpp(self): + """ run C++ VAPI tests """ + var = "BR" + built_root = os.getenv(var, None) + self.assertIsNotNone(built_root, + "Environment variable `%s' not set" % var) + executable = "%s/vapi_test/vapi_cpp_test" % built_root + worker = Worker( + [executable, "vapi client", self.shm_prefix], self.logger) + worker.start() + timeout = 120 worker.join(timeout) self.logger.info("Worker result is `%s'" % worker.result) error = False |