From 799e26d5bdf6b74ab615644e0cd291de6e352989 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 13 Mar 2017 06:39:08 +0000 Subject: make test: add scripts for easy test looping Allows easy running of test(s) in a loop with configurable action (e.g. git pull) run between test runs and possible email notification on failure. Usage: test-loop.sh [-p ] [-m ] -- Example: Run 'make test-debug' in a loop until a failure is encountered, upon which an email is fired to ksekera@cisco.com. In between test runs, update the workspace using via 'git pull' and if anything changed, perform 'git clean' before running another 'make test-debug': test/scripts/test-loop.sh -p test/scripts/git_pull_or_clean.sh \ -m ksekera@cisco.com -- test-debug Change-Id: I114321c6c152d2c7e181e915fc8c51aab1ff3693 Signed-off-by: Klement Sekera --- test/scripts/git_pull_or_clean.sh | 3 + test/scripts/test-loop.sh | 119 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100755 test/scripts/git_pull_or_clean.sh create mode 100755 test/scripts/test-loop.sh (limited to 'test/scripts') diff --git a/test/scripts/git_pull_or_clean.sh b/test/scripts/git_pull_or_clean.sh new file mode 100755 index 00000000..489091f9 --- /dev/null +++ b/test/scripts/git_pull_or_clean.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +git pull | grep -q -v 'Already up-to-date.' || git clean -dfX */ diff --git a/test/scripts/test-loop.sh b/test/scripts/test-loop.sh new file mode 100755 index 00000000..9f228833 --- /dev/null +++ b/test/scripts/test-loop.sh @@ -0,0 +1,119 @@ +#!/bin/bash + +function usage() { + echo "$0" 1>&2 + echo "" 1>&2 + echo "Usage: $0 [-p ] [-m ] -- " 1>&2 + echo "" 1>&2 + echo "Parameters:" 1>&2 + echo " -p - run a command before each test loop (e.g. 'git pull')" 1>&2 + echo " -m - if set, email is sent to this address on failure" 1>&2 + echo "" 1>&2 + echo "Example:" 1>&2 + echo " $0 -m -- test-debug TEST=l2bd" + exit 1; +} + +PRE_EXEC_CMD="" +EMAIL="" + +while getopts "p:m:h" o; do + case "${o}" in + p) + PRE_EXEC_CMD=${OPTARG} + ;; + m) + regex="^[a-z0-9!#\$%&'*+/=?^_\`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_\`{|}~-]+)*@([a-z0-9]([a-z0-9-]*[a-z0-9])?\.)+[a-z0-9]([a-z0-9-]*[a-z0-9])?\$" + m=${OPTARG} + if [[ ! $m =~ $regex ]] + then + echo "Invalid -m parameter value: \`$m'" >&2 + usage + fi + EMAIL="$m" + ;; + h) + usage + ;; + ?) + usage + ;; +esac + done +shift $((OPTIND-1)) + +if ! echo $* | grep test >/dev/null +then + echo "Error: command line doesn't look right - should contain \`test' token..." >&2 + usage +fi + +function finish { + NOW=`date +%s` + RUNTIME=$((NOW - START)) + AVG=$(echo "scale=2; $RUNTIME/$COUNT" | bc) + OUT="*********************************************************************" + OUT="$OUT\n* tail -n 30 $TMP:" + OUT="$OUT\n*********************************************************************" + OUT="$OUT\n`tail -n 30 $TMP`" + OUT="$OUT\n*********************************************************************" + OUT="$OUT\n* Total runtime: ${RUNTIME}s" + OUT="$OUT\n* Iterations: ${COUNT}" + OUT="$OUT\n* Average time: ${AVG}s" + OUT="$OUT\n* Log file: ${TMP}" + OUT="$OUT\n*********************************************************************" + echo -e "$OUT" + if [[ "$EMAIL" != "" && "$REASON" != "" ]] + then + SUBJECT="test loop finished ($REASON)" + echo -e "$OUT" | mail -s "$SUBJECT" $EMAIL + fi +} + +trap "echo Caught signal, exiting...; REASON=\"received signal\"; finish; exit -1" SIGINT SIGTERM + +TMP=`mktemp` +START=`date +%s` +COUNT=0 + +if ! test -f "$TMP" +then + echo "Couldn't create temporary file!" + exit -1 +fi + +echo "Temporary file is $TMP" +CMD="make $*" +echo "Command line is \`$CMD'" + +REASON="" +while true +do + COUNT=$((COUNT+1)) + BEFORE=`date +%s` + if [[ "$PRE_EXEC_CMD" != "" ]] + then + echo "Executing \`$PRE_EXEC_CMD' before test.." + if ! ($PRE_EXEC_CMD 2>$TMP 1>$TMP) + then + echo "\`$PRE_EXEC_CMD' failed!" >&2 + REASON="$PRE_EXEC_CMD failed" + break + fi + fi + echo -n "Running test iteration #$COUNT..." + if ! ($CMD 2>$TMP 1>$TMP) + then + AFTER=`date +%s` + RUNTIME=$((AFTER-BEFORE)) + echo "FAILED! (after ${RUNTIME}s)" + REASON="test failed" + break + fi + AFTER=`date +%s` + RUNTIME=$((AFTER-BEFORE)) + echo "PASSED (after ${RUNTIME}s)" +done + +finish +exit 1 -- cgit 1.2.3-korg From a657e4e7c9cff967303a0396d7819073d8e17fea Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Mon, 3 Apr 2017 04:21:46 +0000 Subject: make test: tweak helper scripts Change-Id: Iee6016757e45c832e8868f0bdcfd4192dd3380c8 Signed-off-by: Klement Sekera --- test/scripts/git_pull_or_clean.sh | 7 ++++++- test/scripts/test-loop.sh | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) (limited to 'test/scripts') diff --git a/test/scripts/git_pull_or_clean.sh b/test/scripts/git_pull_or_clean.sh index 489091f9..b119a9cc 100755 --- a/test/scripts/git_pull_or_clean.sh +++ b/test/scripts/git_pull_or_clean.sh @@ -1,3 +1,8 @@ #!/bin/sh +CMD='git clean -dfX */' -git pull | grep -q -v 'Already up-to-date.' || git clean -dfX */ +if git pull | grep -v 'Already up-to-date.' +then + echo "Executing $CMD" + $CMD +fi diff --git a/test/scripts/test-loop.sh b/test/scripts/test-loop.sh index 9f228833..17dc7c39 100755 --- a/test/scripts/test-loop.sh +++ b/test/scripts/test-loop.sh @@ -94,7 +94,7 @@ do if [[ "$PRE_EXEC_CMD" != "" ]] then echo "Executing \`$PRE_EXEC_CMD' before test.." - if ! ($PRE_EXEC_CMD 2>$TMP 1>$TMP) + if ! ($PRE_EXEC_CMD 2>&1 | tee $TMP) then echo "\`$PRE_EXEC_CMD' failed!" >&2 REASON="$PRE_EXEC_CMD failed" @@ -102,7 +102,7 @@ do fi fi echo -n "Running test iteration #$COUNT..." - if ! ($CMD 2>$TMP 1>$TMP) + if ! ($CMD >$TMP 2>&1) then AFTER=`date +%s` RUNTIME=$((AFTER-BEFORE)) -- cgit 1.2.3-korg From 94384e4d3a4143578d5140c386188fd389754ebf Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Tue, 11 Jul 2017 07:29:37 +0200 Subject: make test: kill all remaining subprocesses on exit This change introduces a wrapper script which kills all processes in the same process group as itself (with the exception of the script). Using this script to run the unit tests should prevent stale processes left behind in some cases (e.g. when test framework crashes). Change-Id: If3b9201c06b87fa6be095721436893207d09b5e4 Signed-off-by: Klement Sekera --- test/Makefile | 2 +- test/scripts/run_with_cleanup.sh | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100755 test/scripts/run_with_cleanup.sh (limited to 'test/scripts') diff --git a/test/Makefile b/test/Makefile index 14c8cd26..7416afc2 100644 --- a/test/Makefile +++ b/test/Makefile @@ -76,7 +76,7 @@ $(PAPI_INSTALL_DONE): $(PIP_PATCH_DONE) @touch $@ define retest-func - @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS)" + @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && setsid scripts/run_with_cleanup.sh python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS)" endef .PHONY: sanity diff --git a/test/scripts/run_with_cleanup.sh b/test/scripts/run_with_cleanup.sh new file mode 100755 index 00000000..dcaa58b4 --- /dev/null +++ b/test/scripts/run_with_cleanup.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +rv=0 + +atexit() { + group_id=`ps -p $$ -o pgid=` + my_id=$$ + ids=`pgrep -g $group_id -d ' ' | sed "s/\b$my_id\b//g"` + echo "Killing possible remaining process IDs: $ids" + for id in $ids + do + if ps -p $id > /dev/null + then + kill -9 $id + fi + done + exit $rv +} + +trap "atexit" SIGINT SIGTERM + +$* +rv=$? +atexit +exit $rv -- cgit 1.2.3-korg From 543852a46ce107243ed92254bd88b87ca82bbd0b Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Thu, 3 Aug 2017 02:11:34 -0400 Subject: Add VPP Communications Library (VCL) - VCL library - client/server test application - test script (make test integration tbd) - gdb command file templates - vppcom test config file Change-Id: I21eab7aa09b4e5dc3412acf5c2eab07415c2fc0f Signed-off-by: Dave Wallace --- extras/gdb/gdb_cmdfile.vcl_client | 10 + extras/gdb/gdb_cmdfile.vcl_server | 10 + extras/gdb/gdb_cmdfile.vpp | 7 + src/uri.am | 41 +- src/uri/sock_test.h | 415 +++++++ src/uri/sock_test_client.c | 1070 ++++++++++++++++ src/uri/sock_test_server.c | 586 +++++++++ src/uri/vcl_test_client.c | 27 + src/uri/vcl_test_server.c | 27 + src/uri/vppcom.c | 2453 +++++++++++++++++++++++++++++++++++++ src/uri/vppcom.h | 152 +++ src/uri/vppcom_test.conf | 25 + test/scripts/socket_test.sh | 637 ++++++++++ 13 files changed, 5459 insertions(+), 1 deletion(-) create mode 100644 extras/gdb/gdb_cmdfile.vcl_client create mode 100644 extras/gdb/gdb_cmdfile.vcl_server create mode 100644 extras/gdb/gdb_cmdfile.vpp create mode 100644 src/uri/sock_test.h create mode 100644 src/uri/sock_test_client.c create mode 100644 src/uri/sock_test_server.c create mode 100644 src/uri/vcl_test_client.c create mode 100644 src/uri/vcl_test_server.c create mode 100644 src/uri/vppcom.c create mode 100644 src/uri/vppcom.h create mode 100644 src/uri/vppcom_test.conf create mode 100755 test/scripts/socket_test.sh (limited to 'test/scripts') diff --git a/extras/gdb/gdb_cmdfile.vcl_client b/extras/gdb/gdb_cmdfile.vcl_client new file mode 100644 index 00000000..a622f958 --- /dev/null +++ b/extras/gdb/gdb_cmdfile.vcl_client @@ -0,0 +1,10 @@ +echo \n\n====== gdb_cmdfile.vppcom_client ======\n + +# Here are some interesting vppcom breakpoints... +# Uncomment them out to set during gdb init. +# +#b vppcom_session_connect +#b vppcom_session_write +#b vppcom_session_read +#b vl_api_connect_sock_t_handler +#b vl_api_connect_sock_reply_t_handler diff --git a/extras/gdb/gdb_cmdfile.vcl_server b/extras/gdb/gdb_cmdfile.vcl_server new file mode 100644 index 00000000..8e441018 --- /dev/null +++ b/extras/gdb/gdb_cmdfile.vcl_server @@ -0,0 +1,10 @@ +echo \n\n====== gdb_cmdfile.vppcom_server ======\n + +# Here are some interesting vppcom breakpoints... +# Uncomment them out to set during gdb init. +# +#b vppcom_session_accept +#b vppcom_session_write +#b vppcom_session_read +#b vl_api_connect_sock_t_handler +#b vl_api_connect_sock_reply_t_handler diff --git a/extras/gdb/gdb_cmdfile.vpp b/extras/gdb/gdb_cmdfile.vpp new file mode 100644 index 00000000..a430ccb3 --- /dev/null +++ b/extras/gdb/gdb_cmdfile.vpp @@ -0,0 +1,7 @@ +echo \n\n====== gdb_cmdfile.vpp ======\n + +# Here are some interesting vpp-api breakpoints... +# Uncomment them out to set during gdb init. +# +#b vl_api_connect_sock_t_handler +#b vl_api_connect_sock_reply_t_handler diff --git a/src/uri.am b/src/uri.am index fb7570ff..ae6feb6f 100644 --- a/src/uri.am +++ b/src/uri.am @@ -11,7 +11,34 @@ # See the License for the specific language governing permissions and # limitations under the License. -noinst_PROGRAMS += uri_udp_test uri_tcp_test uri_socket_test uri_socket_server +lib_LTLIBRARIES += libvppcom.la + +libvppcom_la_SOURCES = +libvppcom_la_DEPENDENCIES = \ + libvppinfra.la \ + libvlib.la \ + libsvmdb.la \ + libsvm.la \ + libvlibmemory.la \ + libvlibmemoryclient.la + +libvppcom_la_LIBADD = $(libvppcom_la_DEPENDENCIES) -lpthread + +libvppcom_la_SOURCES += \ + uri/vppcom.c + +nobase_include_HEADERS += \ + uri/vppcom.h + +noinst_PROGRAMS += \ + uri_udp_test \ + uri_tcp_test \ + uri_socket_test \ + uri_socket_server \ + vcl_test_server \ + vcl_test_client \ + sock_test_server \ + sock_test_client uri_udp_test_SOURCES = uri/uri_udp_test.c uri_udp_test_LDADD = libvlibmemoryclient.la libsvm.la \ @@ -26,3 +53,15 @@ uri_socket_test_LDADD = libvppinfra.la -lpthread -lm -lrt uri_socket_server_SOURCES = uri/uri_socket_server.c uri_socket_server_LDADD = libvppinfra.la -lpthread -lm -lrt + +vcl_test_server_SOURCES = uri/vcl_test_server.c +vcl_test_server_LDADD = libvppcom.la + +vcl_test_client_SOURCES = uri/vcl_test_client.c +vcl_test_client_LDADD = libvppcom.la + +sock_test_server_SOURCES = uri/sock_test_server.c +sock_test_client_SOURCES = uri/sock_test_client.c + +nobase_include_HEADERS += \ + uri/sock_test.h diff --git a/src/uri/sock_test.h b/src/uri/sock_test.h new file mode 100644 index 00000000..281ba6fd --- /dev/null +++ b/src/uri/sock_test.h @@ -0,0 +1,415 @@ +/* + * 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 __sock_test_h__ +#define __sock_test_h__ + +#include +#include +#include +#include + +#define SOCK_TEST_TOKEN_HELP "#H" +#define SOCK_TEST_TOKEN_EXIT "#X" +#define SOCK_TEST_TOKEN_VERBOSE "#V" +#define SOCK_TEST_TOKEN_TXBUF_SIZE "#T:" +#define SOCK_TEST_TOKEN_NUM_TEST_SCKTS "#I:" +#define SOCK_TEST_TOKEN_NUM_WRITES "#N:" +#define SOCK_TEST_TOKEN_RXBUF_SIZE "#R:" +#define SOCK_TEST_TOKEN_SHOW_CFG "#C" +#define SOCK_TEST_TOKEN_RUN_UNI "#U" +#define SOCK_TEST_TOKEN_RUN_BI "#B" + +#define SOCK_TEST_BANNER_STRING \ + "============================================\n" +#define SOCK_TEST_SEPARATOR_STRING \ + " -----------------------------\n" + +#define ONE_GIG (1024*1024*1024) +#define SOCK_TEST_SERVER_PORT 22000 +#define SOCK_TEST_LOCALHOST_IPADDR "127.0.0.1" + +#define SOCK_TEST_CFG_CTRL_MAGIC 0xfeedface +#define SOCK_TEST_CFG_NUM_WRITES_DEF 1000000 +#define SOCK_TEST_CFG_TXBUF_SIZE_DEF 8192 +#define SOCK_TEST_CFG_RXBUF_SIZE_DEF (64*SOCK_TEST_CFG_TXBUF_SIZE_DEF) +#define SOCK_TEST_CFG_BUF_SIZE_MIN 128 +#define SOCK_TEST_CFG_MAX_TEST_SCKTS 5 + +typedef enum +{ + SOCK_TEST_TYPE_NONE, + SOCK_TEST_TYPE_ECHO, + SOCK_TEST_TYPE_UNI, + SOCK_TEST_TYPE_BI, + SOCK_TEST_TYPE_EXIT, +} sock_test_t; + +typedef struct __attribute__ ((packed)) +{ + uint32_t magic; + uint32_t test; + uint32_t ctrl_handle; + uint32_t num_test_sockets; + uint32_t verbose; + uint64_t rxbuf_size; + uint64_t txbuf_size; + uint64_t num_writes; + uint64_t total_bytes; +} sock_test_cfg_t; + +typedef struct +{ + uint64_t rx_xacts; + uint64_t rx_bytes; + uint32_t rx_eagain; + uint32_t rx_incomp; + uint64_t tx_xacts; + uint64_t tx_bytes; + uint32_t tx_eagain; + uint32_t tx_incomp; + struct timespec start; + struct timespec stop; +} sock_test_stats_t; + +typedef struct +{ + int fd; + uint32_t txbuf_size; + char *txbuf; + uint32_t rxbuf_size; + char *rxbuf; + sock_test_cfg_t cfg; + sock_test_stats_t stats; +} sock_test_socket_t; + +static inline void +sock_test_stats_accumulate (sock_test_stats_t * accum, + sock_test_stats_t * incr) +{ + accum->rx_xacts += incr->rx_xacts; + accum->rx_bytes += incr->rx_bytes; + accum->rx_eagain += incr->rx_eagain; + accum->rx_incomp += incr->rx_incomp; + accum->tx_xacts += incr->tx_xacts; + accum->tx_bytes += incr->tx_bytes; + accum->tx_eagain += incr->tx_eagain; + accum->tx_incomp += incr->tx_incomp; +} + +static inline void +sock_test_cfg_init (sock_test_cfg_t *cfg) +{ + cfg->magic = SOCK_TEST_CFG_CTRL_MAGIC; + cfg->test = SOCK_TEST_TYPE_NONE; + cfg->ctrl_handle = ~0; + cfg->num_test_sockets = 1; + cfg->verbose = 0; + cfg->rxbuf_size = SOCK_TEST_CFG_RXBUF_SIZE_DEF; + cfg->num_writes = SOCK_TEST_CFG_NUM_WRITES_DEF; + cfg->txbuf_size = SOCK_TEST_CFG_TXBUF_SIZE_DEF; + cfg->total_bytes = cfg->num_writes * cfg->txbuf_size; +} + +static inline int +sock_test_cfg_verify (sock_test_cfg_t *cfg, sock_test_cfg_t *valid_cfg) +{ + /* Note: txbuf & rxbuf on server are the same buffer, + * so txbuf_size is not included in this check. + */ + return ((cfg->magic == valid_cfg->magic) + && (cfg->test == valid_cfg->test) + && (cfg->verbose == valid_cfg->verbose) + && (cfg->rxbuf_size == valid_cfg->rxbuf_size) + && (cfg->num_writes == valid_cfg->num_writes) + && (cfg->total_bytes == valid_cfg->total_bytes)); +} + +static inline void +sock_test_buf_alloc (sock_test_cfg_t *cfg, uint8_t is_rxbuf, uint8_t **buf, + uint32_t *bufsize) +{ + uint32_t alloc_size = is_rxbuf ? cfg->rxbuf_size : cfg->txbuf_size; + uint8_t *lb = realloc (*buf, (size_t) alloc_size); + + if (lb) + { + if (is_rxbuf) + cfg->rxbuf_size = *bufsize = alloc_size; + else + cfg->txbuf_size = *bufsize = alloc_size; + + *buf = lb; + } + else + { + int errno_val = errno; + perror ("ERROR in sock_test_buf_alloc()"); + fprintf (stderr, "ERROR: Buffer allocation failed (errno = %d)!\n" + " Using buffer size %d instead of desired" + " size (%d)\n", errno_val, *bufsize, alloc_size); + } +} + +static inline void +sock_test_socket_buf_alloc (sock_test_socket_t *socket) +{ + socket->rxbuf_size = socket->cfg.rxbuf_size; + socket->txbuf_size = socket->cfg.txbuf_size; + sock_test_buf_alloc (&socket->cfg, 0 /* is_rxbuf */ , + (uint8_t **) &socket->txbuf, &socket->txbuf_size); + sock_test_buf_alloc (&socket->cfg, 1 /* is_rxbuf */ , + (uint8_t **) &socket->rxbuf, &socket->rxbuf_size); +} + +static inline char * +sock_test_type_str (sock_test_t t) +{ + switch (t) + { + case SOCK_TEST_TYPE_NONE: + return "NONE"; + + case SOCK_TEST_TYPE_ECHO: + return "ECHO"; + + case SOCK_TEST_TYPE_UNI: + return "UNI"; + + case SOCK_TEST_TYPE_BI: + return "BI"; + + case SOCK_TEST_TYPE_EXIT: + return "EXIT"; + + default: + return "Unknown"; + } +} + +static inline void +sock_test_cfg_dump (sock_test_cfg_t * cfg, uint8_t is_client) +{ + char *spc = " "; + + printf (" test config (%p):\n" + SOCK_TEST_SEPARATOR_STRING + " magic: 0x%08x\n" + "%-5s test: %s (%d)\n" + " ctrl handle: %d (0x%x)\n" + "%-5s num test sockets: %u (0x%08x)\n" + "%-5s verbose: %s (%d)\n" + "%-5s rxbuf size: %lu (0x%08lx)\n" + "%-5s txbuf size: %lu (0x%08lx)\n" + "%-5s num writes: %lu (0x%08lx)\n" + " client tx bytes: %lu (0x%08lx)\n" + SOCK_TEST_SEPARATOR_STRING, + (void *) cfg, cfg->magic, + is_client && (cfg->test == SOCK_TEST_TYPE_UNI) ? + "'"SOCK_TEST_TOKEN_RUN_UNI"'" : + is_client && (cfg->test == SOCK_TEST_TYPE_BI) ? + "'"SOCK_TEST_TOKEN_RUN_BI"'" : spc, + sock_test_type_str (cfg->test), cfg->test, + cfg->ctrl_handle, cfg->ctrl_handle, + is_client ? "'"SOCK_TEST_TOKEN_NUM_TEST_SCKTS"'" : spc, + cfg->num_test_sockets, cfg->num_test_sockets, + is_client ? "'"SOCK_TEST_TOKEN_VERBOSE"'" : spc, + cfg->verbose ? "on" : "off", cfg->verbose, + is_client ? "'"SOCK_TEST_TOKEN_RXBUF_SIZE"'" : spc, + cfg->rxbuf_size, cfg->rxbuf_size, + is_client ? "'"SOCK_TEST_TOKEN_TXBUF_SIZE"'" : spc, + cfg->txbuf_size, cfg->txbuf_size, + is_client ? "'"SOCK_TEST_TOKEN_NUM_WRITES"'" : spc, + cfg->num_writes, cfg->num_writes, + cfg->total_bytes, cfg->total_bytes); +} + +static inline void +sock_test_stats_dump (char * header, sock_test_stats_t * stats, + uint8_t show_rx, uint8_t show_tx, + uint8_t verbose) +{ + struct timespec diff; + double duration, rate; + uint64_t total_bytes; + + if ((stats->stop.tv_nsec - stats->start.tv_nsec) < 0) + { + diff.tv_sec = stats->stop.tv_sec - stats->start.tv_sec - 1; + diff.tv_nsec = stats->stop.tv_nsec - stats->start.tv_nsec + 1000000000; + } + else + { + diff.tv_sec = stats->stop.tv_sec - stats->start.tv_sec; + diff.tv_nsec = stats->stop.tv_nsec - stats->start.tv_nsec; + } + duration = (double) diff.tv_sec + (1e-9 * diff.tv_nsec); + + total_bytes = stats->tx_bytes + stats->rx_bytes; + rate = (double) total_bytes * 8 / duration / ONE_GIG; + printf ("\n%s: Streamed %lu bytes\n" + " in %lf seconds (%lf Gbps %s-duplex)!\n", + header, total_bytes, duration, rate, + (show_rx && show_tx) ? "full" : "half"); + + if (show_tx) + { + printf (SOCK_TEST_SEPARATOR_STRING + " tx stats (0x%p):\n" + SOCK_TEST_SEPARATOR_STRING + " writes: %lu (0x%08lx)\n" + " tx bytes: %lu (0x%08lx)\n" + " tx eagain: %u (0x%08x)\n" + " tx incomplete: %u (0x%08x)\n", + (void *)stats, stats->tx_xacts, stats->tx_xacts, + stats->tx_bytes, stats->tx_bytes, + stats->tx_eagain, stats->tx_eagain, + stats->tx_incomp, stats->tx_incomp); + } + if (show_rx) + { + printf (SOCK_TEST_SEPARATOR_STRING + " rx stats (0x%p):\n" + SOCK_TEST_SEPARATOR_STRING + " reads: %lu (0x%08lx)\n" + " rx bytes: %lu (0x%08lx)\n" + " rx eagain: %u (0x%08x)\n" + " rx incomplete: %u (0x%08x)\n", + (void *)stats, stats->rx_xacts, stats->rx_xacts, + stats->rx_bytes, stats->rx_bytes, + stats->rx_eagain, stats->rx_eagain, + stats->rx_incomp, stats->rx_incomp); + } + if (verbose) + printf (" start.tv_sec: %ld\n" + " start.tv_nsec: %ld\n" + " stop.tv_sec: %ld\n" + " stop.tv_nsec: %ld\n", + stats->start.tv_sec, stats->start.tv_nsec, + stats->stop.tv_sec, stats->stop.tv_nsec); + + printf (SOCK_TEST_SEPARATOR_STRING); +} + +static inline int +sock_test_read (int fd, uint8_t *buf, uint32_t nbytes, + sock_test_stats_t *stats) +{ + int rx_bytes, errno_val; + + do + { + if (stats) + stats->rx_xacts++; +#ifdef VCL_TEST + rx_bytes = vppcom_session_read (fd, buf, nbytes); + + if (rx_bytes < 0) + { + errno = -rx_bytes; + rx_bytes = -1; + } +#else + rx_bytes = read (fd, buf, nbytes); +#endif + if (stats) + { + if ((rx_bytes == 0) || + ((rx_bytes < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))) + stats->rx_eagain++; + else if (rx_bytes < nbytes) + stats->rx_incomp++; + } + } + while ((rx_bytes == 0) || + ((rx_bytes < 0) && ((errno == EAGAIN) || (errno == EWOULDBLOCK)))); + + if (rx_bytes < 0) + { + errno_val = errno; + perror ("ERROR in sock_test_read()"); + fprintf (stderr, "ERROR: socket read failed (errno = %d)!\n", + errno_val); + errno = errno_val; + } + else if (stats) + stats->rx_bytes += rx_bytes; + + return (rx_bytes); +} + +static inline int +sock_test_write (int fd, uint8_t *buf, uint32_t nbytes, + sock_test_stats_t *stats, uint32_t verbose) +{ + int tx_bytes = 0; + int nbytes_left = nbytes; + int rv, errno_val; + + do + { + if (stats) + stats->tx_xacts++; +#ifdef VCL_TEST + rv = vppcom_session_write (fd, buf, nbytes_left); + if (rv < 0) + { + errno = -rv; + rv = -1; + } +#else + rv = write (fd, buf, nbytes_left); +#endif + if (rv < 0) + { + if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) + { + if (stats) + stats->tx_eagain++; + continue; + } + else + break; + } + tx_bytes += rv; + + if (tx_bytes != nbytes) + { + nbytes_left = nbytes_left - rv; + if (stats) + stats->tx_incomp++; + if (verbose) + { + printf ("WARNING: bytes written (%d) != bytes to write (%d)!\n", + tx_bytes, nbytes); + } + } + + } while (tx_bytes != nbytes); + + if (tx_bytes < 0) + { + errno_val = errno; + perror ("ERROR in sock_test_write()"); + fprintf (stderr, "ERROR: socket write failed (errno = %d)!\n", + errno_val); + } + else if (stats) + stats->tx_bytes += tx_bytes; + + return (tx_bytes); +} + +#endif /* __sock_test_h__ */ diff --git a/src/uri/sock_test_client.c b/src/uri/sock_test_client.c new file mode 100644 index 00000000..26d096be --- /dev/null +++ b/src/uri/sock_test_client.c @@ -0,0 +1,1070 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct +{ +#ifdef VCL_TEST + vppcom_endpt_t server_endpt; +#endif + struct sockaddr_in server_addr; + sock_test_socket_t ctrl_socket; + sock_test_socket_t *test_socket; + uint32_t num_test_sockets; + uint8_t dump_cfg; +} sock_client_main_t; + +sock_client_main_t sock_client_main; + + +static int +sock_test_cfg_sync (sock_test_socket_t * socket) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + sock_test_cfg_t *rl_cfg = (sock_test_cfg_t *) socket->rxbuf; + int rx_bytes, tx_bytes; + + if (socket->cfg.verbose) + sock_test_cfg_dump (&socket->cfg, 1 /* is_client */ ); + + tx_bytes = sock_test_write (socket->fd, (uint8_t *) & ctrl->cfg, + sizeof (ctrl->cfg), NULL, ctrl->cfg.verbose); + if (tx_bytes < 0) + { + fprintf (stderr, "ERROR: write test cfg failed (%d)!\n", tx_bytes); + return tx_bytes; + } + + rx_bytes = sock_test_read (socket->fd, (uint8_t *) socket->rxbuf, + sizeof (sock_test_cfg_t), NULL); + if (rx_bytes < 0) + return rx_bytes; + + if (rl_cfg->magic != SOCK_TEST_CFG_CTRL_MAGIC) + { + fprintf (stderr, "ERROR: Bad server reply cfg -- aborting!\n"); + return -1; + } + if (socket->cfg.verbose) + { + printf ("CLIENT (fd %d): Got config back from server.\n", socket->fd); + sock_test_cfg_dump (rl_cfg, 1 /* is_client */ ); + } + if ((rx_bytes != sizeof (sock_test_cfg_t)) + || !sock_test_cfg_verify (rl_cfg, &ctrl->cfg)) + { + fprintf (stderr, + "ERROR: Invalid config received from server -- aborting!\n"); + sock_test_cfg_dump (rl_cfg, 1 /* is_client */ ); + return -1; + } + ctrl->cfg.ctrl_handle = ((ctrl->cfg.ctrl_handle == ~0) ? + rl_cfg->ctrl_handle : ctrl->cfg.ctrl_handle); + + return 0; +} + +static void +echo_test_client () +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + sock_test_socket_t *tsock; + int rx_bytes, tx_bytes, nbytes; + uint32_t i, n; + int rv; + int nfds = 0; + fd_set wr_fdset, rd_fdset; + fd_set _wfdset, *wfdset = &_wfdset; + fd_set _rfdset, *rfdset = &_rfdset; + + FD_ZERO (&wr_fdset); + FD_ZERO (&rd_fdset); + memset (&ctrl->stats, 0, sizeof (ctrl->stats)); + ctrl->cfg.total_bytes = nbytes = strlen (ctrl->txbuf) + 1; + for (n = 0; n != ctrl->cfg.num_test_sockets; n++) + { + tsock = &scm->test_socket[n]; + tsock->cfg = ctrl->cfg; + sock_test_socket_buf_alloc (tsock); + sock_test_cfg_sync (tsock); + + memcpy (tsock->txbuf, ctrl->txbuf, nbytes); + memset (&tsock->stats, 0, sizeof (tsock->stats)); + + FD_SET (tsock->fd, &wr_fdset); + FD_SET (tsock->fd, &rd_fdset); + nfds = ((tsock->fd + 1) > nfds) ? (tsock->fd + 1) : nfds; + } + + nfds++; + clock_gettime (CLOCK_REALTIME, &ctrl->stats.start); + while (n) + { + _wfdset = wr_fdset; + _rfdset = rd_fdset; + +#ifdef VCL_TEST + rv = vppcom_select (nfds, (uint64_t *) rfdset, (uint64_t *) wfdset, + NULL, 0); +#else + { + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + rv = select (nfds, rfdset, wfdset, NULL, &timeout); + } +#endif + if (rv < 0) + { + perror ("select()"); + fprintf (stderr, "\nERROR: select() failed -- aborting test!\n"); + return; + } + else if (rv == 0) + continue; + + for (i = 0; i < ctrl->cfg.num_test_sockets; i++) + { + tsock = &scm->test_socket[i]; + if (!((tsock->stats.stop.tv_sec == 0) && + (tsock->stats.stop.tv_nsec == 0))) + continue; + + if (FD_ISSET (tsock->fd, wfdset) && + (tsock->stats.tx_bytes < ctrl->cfg.total_bytes)) + + { + tx_bytes = + sock_test_write (tsock->fd, (uint8_t *) tsock->txbuf, nbytes, + &tsock->stats, ctrl->cfg.verbose); + if (tx_bytes < 0) + { + fprintf (stderr, "\nERROR: sock_test_write(%d) failed " + "-- aborting test!\n", tsock->fd); + return; + } + + printf ("CLIENT (fd %d): TX (%d bytes) - '%s'\n", + tsock->fd, tx_bytes, tsock->txbuf); + } + + if ((FD_ISSET (tsock->fd, rfdset)) && + (tsock->stats.rx_bytes < ctrl->cfg.total_bytes)) + { + rx_bytes = + sock_test_read (tsock->fd, (uint8_t *) tsock->rxbuf, + nbytes, &tsock->stats); + if (rx_bytes > 0) + { + printf ("CLIENT (fd %d): RX (%d bytes) - '%s'\n", + tsock->fd, rx_bytes, tsock->rxbuf); + + if (tsock->stats.rx_bytes != tsock->stats.tx_bytes) + printf + ("WARNING: bytes read (%lu) != bytes written (%lu)!\n", + tsock->stats.rx_bytes, tsock->stats.tx_bytes); + } + } + + if (tsock->stats.rx_bytes >= ctrl->cfg.total_bytes) + { + clock_gettime (CLOCK_REALTIME, &tsock->stats.stop); + n--; + } + } + } + clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop); + + for (i = 0; i < ctrl->cfg.num_test_sockets; i++) + { + tsock = &scm->test_socket[i]; + tsock->stats.start = ctrl->stats.start; + + if (ctrl->cfg.verbose) + { + static char buf[64]; + + sprintf (buf, "CLIENT (fd %d) RESULTS", tsock->fd); + sock_test_stats_dump (buf, &tsock->stats, + 1 /* show_rx */ , 1 /* show tx */ , + ctrl->cfg.verbose); + } + + sock_test_stats_accumulate (&ctrl->stats, &tsock->stats); + } + + if (ctrl->cfg.verbose) + { + sock_test_stats_dump ("CLIENT RESULTS", &ctrl->stats, + 1 /* show_rx */ , 1 /* show tx */ , + ctrl->cfg.verbose); + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + + if (ctrl->cfg.verbose > 1) + { + printf (" ctrl socket info\n" + SOCK_TEST_SEPARATOR_STRING + " fd: %d (0x%08x)\n" + " rxbuf: %p\n" + " rxbuf size: %u (0x%08x)\n" + " txbuf: %p\n" + " txbuf size: %u (0x%08x)\n" + SOCK_TEST_SEPARATOR_STRING, + ctrl->fd, (uint32_t) ctrl->fd, + ctrl->rxbuf, ctrl->rxbuf_size, ctrl->rxbuf_size, + ctrl->txbuf, ctrl->txbuf_size, ctrl->txbuf_size); + } + } +} + +static void +stream_test_client (sock_test_t test) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + sock_test_socket_t *tsock; + int tx_bytes; + uint32_t i, n; + int rv; + int nfds = 0; + fd_set wr_fdset, rd_fdset; + fd_set _wfdset, *wfdset = &_wfdset; + fd_set _rfdset, *rfdset = (test == SOCK_TEST_TYPE_BI) ? &_rfdset : 0; + + ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; + ctrl->cfg.ctrl_handle = ~0; + + printf ("\n" SOCK_TEST_BANNER_STRING + "CLIENT (fd %d): %s-directional Stream Test!\n\n" + "CLIENT (fd %d): Sending config to server on ctrl socket...\n", + ctrl->fd, test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni", ctrl->fd); + + if (sock_test_cfg_sync (ctrl)) + { + fprintf (stderr, "ERROR: test cfg sync failed -- aborting!"); + return; + } + + FD_ZERO (&wr_fdset); + FD_ZERO (&rd_fdset); + memset (&ctrl->stats, 0, sizeof (ctrl->stats)); + for (n = 0; n != ctrl->cfg.num_test_sockets; n++) + { + tsock = &scm->test_socket[n]; + tsock->cfg = ctrl->cfg; + sock_test_socket_buf_alloc (tsock); + printf ("CLIENT (fd %d): Sending config to server on " + "test socket %d...\n", tsock->fd, n); + sock_test_cfg_sync (tsock); + + /* Fill payload with incrementing uint32's */ + for (i = 0; i < tsock->txbuf_size; i++) + tsock->txbuf[i] = i & 0xff; + + memset (&tsock->stats, 0, sizeof (tsock->stats)); + FD_SET (tsock->fd, &wr_fdset); + FD_SET (tsock->fd, &rd_fdset); + nfds = ((tsock->fd + 1) > nfds) ? (tsock->fd + 1) : nfds; + } + + nfds++; + clock_gettime (CLOCK_REALTIME, &ctrl->stats.start); + while (n) + { + _wfdset = wr_fdset; + _rfdset = rd_fdset; + +#ifdef VCL_TEST + rv = vppcom_select (nfds, (uint64_t *) rfdset, (uint64_t *) wfdset, + NULL, 0); +#else + { + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = 0; + rv = select (nfds, rfdset, wfdset, NULL, &timeout); + } +#endif + if (rv < 0) + { + perror ("select()"); + fprintf (stderr, "\nERROR: select() failed -- aborting test!\n"); + return; + } + else if (rv == 0) + continue; + + for (i = 0; i < ctrl->cfg.num_test_sockets; i++) + { + tsock = &scm->test_socket[i]; + if (!((tsock->stats.stop.tv_sec == 0) && + (tsock->stats.stop.tv_nsec == 0))) + continue; + + if (FD_ISSET (tsock->fd, wfdset) && + (tsock->stats.tx_bytes < ctrl->cfg.total_bytes)) + { + tx_bytes = + sock_test_write (tsock->fd, (uint8_t *) tsock->txbuf, + ctrl->cfg.txbuf_size, &tsock->stats, + ctrl->cfg.verbose); + if (tx_bytes < 0) + { + fprintf (stderr, "\nERROR: sock_test_write(%d) failed " + "-- aborting test!\n", tsock->fd); + return; + } + } + + if ((test == SOCK_TEST_TYPE_BI) && + FD_ISSET (tsock->fd, rfdset) && + (tsock->stats.rx_bytes < ctrl->cfg.total_bytes)) + { + (void) sock_test_read (tsock->fd, + (uint8_t *) tsock->rxbuf, + tsock->rxbuf_size, &tsock->stats); + } + + if (((test == SOCK_TEST_TYPE_UNI) && + (tsock->stats.tx_bytes >= ctrl->cfg.total_bytes)) || + ((test == SOCK_TEST_TYPE_BI) && + (tsock->stats.rx_bytes >= ctrl->cfg.total_bytes))) + { + clock_gettime (CLOCK_REALTIME, &tsock->stats.stop); + n--; + } + } + } + clock_gettime (CLOCK_REALTIME, &ctrl->stats.stop); + + printf ("CLIENT (fd %d): Sending config to server on ctrl socket...\n", + ctrl->fd); + + if (sock_test_cfg_sync (ctrl)) + { + fprintf (stderr, "ERROR: test cfg sync failed -- aborting!"); + return; + } + + for (i = 0; i < ctrl->cfg.num_test_sockets; i++) + { + tsock = &scm->test_socket[i]; + + if (ctrl->cfg.verbose) + { + static char buf[64]; + + sprintf (buf, "CLIENT (fd %d) RESULTS", tsock->fd); + sock_test_stats_dump (buf, &tsock->stats, + test == SOCK_TEST_TYPE_BI /* show_rx */ , + 1 /* show tx */ , ctrl->cfg.verbose); + } + + sock_test_stats_accumulate (&ctrl->stats, &tsock->stats); + } + + sock_test_stats_dump ("CLIENT RESULTS", &ctrl->stats, + test == SOCK_TEST_TYPE_BI /* show_rx */ , + 1 /* show tx */ , ctrl->cfg.verbose); + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + + if (ctrl->cfg.verbose) + { + printf (" ctrl socket info\n" + SOCK_TEST_SEPARATOR_STRING + " fd: %d (0x%08x)\n" + " rxbuf: %p\n" + " rxbuf size: %u (0x%08x)\n" + " txbuf: %p\n" + " txbuf size: %u (0x%08x)\n" + SOCK_TEST_SEPARATOR_STRING, + ctrl->fd, (uint32_t) ctrl->fd, + ctrl->rxbuf, ctrl->rxbuf_size, ctrl->rxbuf_size, + ctrl->txbuf, ctrl->txbuf_size, ctrl->txbuf_size); + } + + ctrl->cfg.test = SOCK_TEST_TYPE_ECHO; + if (sock_test_cfg_sync (ctrl)) + fprintf (stderr, "ERROR: post-test cfg sync failed!"); + + printf ("CLIENT (fd %d): %s-directional Stream Test Complete!\n" + SOCK_TEST_BANNER_STRING "\n", ctrl->fd, + test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni"); +} + +static void +exit_client (void) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + sock_test_socket_t *tsock; + int i; + + for (i = 0; i < ctrl->cfg.num_test_sockets; i++) + { + tsock = &scm->test_socket[i]; + tsock->cfg.test = SOCK_TEST_TYPE_EXIT; + + if (ctrl->cfg.verbose) + { + printf ("\nCLIENT (fd %d): Sending exit cfg to server...\n", + tsock->fd); + sock_test_cfg_dump (&tsock->cfg, 1 /* is_client */ ); + } + (void) sock_test_write (tsock->fd, (uint8_t *) & tsock->cfg, + sizeof (tsock->cfg), &tsock->stats, + ctrl->cfg.verbose); + } + + ctrl->cfg.test = SOCK_TEST_TYPE_EXIT; + if (ctrl->cfg.verbose) + { + printf ("\nCLIENT (fd %d): Sending exit cfg to server...\n", ctrl->fd); + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + } + (void) sock_test_write (ctrl->fd, (uint8_t *) & ctrl->cfg, + sizeof (ctrl->cfg), &ctrl->stats, + ctrl->cfg.verbose); + printf ("\nCLIENT: So long and thanks for all the fish!\n\n"); + sleep (1); +} + +static int +sock_test_connect_test_sockets (uint32_t num_test_sockets) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + sock_test_socket_t *tsock; + int i, rv, errno_val; + + if (num_test_sockets < 1) + { + errno = EINVAL; + return -1; + } + + if (num_test_sockets < scm->num_test_sockets) + { + for (i = scm->num_test_sockets - 1; i >= num_test_sockets; i--) + { + tsock = &scm->test_socket[i]; +#ifdef VCL_TEST + vppcom_session_close (tsock->fd); +#else + close (tsock->fd); +#endif + free (tsock->txbuf); + free (tsock->rxbuf); + } + } + + else if (num_test_sockets > scm->num_test_sockets) + { + tsock = realloc (scm->test_socket, + sizeof (sock_test_socket_t) * num_test_sockets); + if (!tsock) + { + errno_val = errno; + perror ("ERROR in sock_test_connect_test_sockets()"); + fprintf (stderr, "ERROR: socket failed (errno = %d)!\n", errno_val); + return -1; + } + + scm->test_socket = tsock; + for (i = scm->num_test_sockets; i < num_test_sockets; i++) + { + tsock = &scm->test_socket[i]; +#ifdef VCL_TEST + tsock->fd = + vppcom_session_create (VPPCOM_VRF_DEFAULT, VPPCOM_PROTO_TCP, + 0 /* is_nonblocking */ ); + if (tsock->fd < 0) + { + errno = -tsock->fd; + tsock->fd = -1; + } +#else + tsock->fd = socket (AF_INET, SOCK_STREAM, 0); +#endif + if (tsock->fd < 0) + { + errno_val = errno; + perror ("ERROR in sock_test_connect_test_sockets()"); + fprintf (stderr, "ERROR: socket failed (errno = %d)!\n", + errno_val); + return tsock->fd; + } + +#ifdef VCL_TEST + rv = vppcom_session_connect (tsock->fd, &scm->server_endpt); +#else + rv = + connect (tsock->fd, (struct sockaddr *) &scm->server_addr, + sizeof (scm->server_addr)); +#endif + if (rv < 0) + { + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: connect failed (errno = %d)!\n", + errno_val); + } + tsock->cfg = ctrl->cfg; + sock_test_socket_buf_alloc (tsock); + sock_test_cfg_sync (tsock); + + printf ("CLIENT (fd %d): Test socket %d connected.\n", + tsock->fd, i); + } + } + + scm->num_test_sockets = num_test_sockets; + printf ("CLIENT: All sockets (%d) connected!\n", scm->num_test_sockets + 1); + return 0; +} + +static void +dump_help (void) +{ +#define INDENT "\n " + + printf ("Test configuration commands:" + INDENT SOCK_TEST_TOKEN_HELP + "\t\t\tDisplay help." + INDENT SOCK_TEST_TOKEN_EXIT + "\t\t\tExit test client & server." + INDENT SOCK_TEST_TOKEN_SHOW_CFG + "\t\t\tShow the current test cfg." + INDENT SOCK_TEST_TOKEN_RUN_UNI + "\t\t\tRun the Uni-directional test." + INDENT SOCK_TEST_TOKEN_RUN_BI + "\t\t\tRun the Bi-directional test." + INDENT SOCK_TEST_TOKEN_VERBOSE + "\t\t\tToggle verbose setting." + INDENT SOCK_TEST_TOKEN_RXBUF_SIZE + "\tRx buffer size (bytes)." + INDENT SOCK_TEST_TOKEN_TXBUF_SIZE + "\tTx buffer size (bytes)." + INDENT SOCK_TEST_TOKEN_NUM_WRITES + "<# of writes>\tNumber of txbuf writes to server." "\n"); +} + +static void +cfg_txbuf_size_set (void) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_TXBUF_SIZE); + uint64_t txbuf_size = strtoull ((const char *) p, NULL, 10); + + if (txbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN) + { + ctrl->cfg.txbuf_size = txbuf_size; + ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; + sock_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ , + (uint8_t **) & ctrl->txbuf, &ctrl->txbuf_size); + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + } + else + fprintf (stderr, + "ERROR: Invalid txbuf size (%lu) < minimum buf size (%u)!\n", + txbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN); +} + +static void +cfg_num_writes_set (void) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_NUM_WRITES); + uint32_t num_writes = strtoul ((const char *) p, NULL, 10); + + if (num_writes > 0) + { + ctrl->cfg.num_writes = num_writes; + ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + } + else + { + fprintf (stderr, "ERROR: invalid num writes: %u\n", num_writes); + } +} + +static void +cfg_num_test_sockets_set (void) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_NUM_TEST_SCKTS); + uint32_t num_test_sockets = strtoul ((const char *) p, NULL, 10); + + if ((num_test_sockets > 0) && + (num_test_sockets <= SOCK_TEST_CFG_MAX_TEST_SCKTS)) + { + ctrl->cfg.num_test_sockets = num_test_sockets; + sock_test_connect_test_sockets (num_test_sockets); + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + } + else + { + fprintf (stderr, "ERROR: invalid num test sockets: %u, (%d max)\n", + num_test_sockets, SOCK_TEST_CFG_MAX_TEST_SCKTS); + } +} + +static void +cfg_rxbuf_size_set (void) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + char *p = ctrl->txbuf + strlen (SOCK_TEST_TOKEN_RXBUF_SIZE); + uint64_t rxbuf_size = strtoull ((const char *) p, NULL, 10); + + if (rxbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN) + { + ctrl->cfg.rxbuf_size = rxbuf_size; + sock_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ , + (uint8_t **) & ctrl->rxbuf, &ctrl->rxbuf_size); + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + } + else + fprintf (stderr, + "ERROR: Invalid rxbuf size (%lu) < minimum buf size (%u)!\n", + rxbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN); +} + +static void +cfg_verbose_toggle (void) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + + ctrl->cfg.verbose = ctrl->cfg.verbose ? 0 : 1; + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + +} + +static sock_test_t +parse_input () +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + sock_test_t rv = SOCK_TEST_TYPE_NONE; + + if (!strcmp (SOCK_TEST_TOKEN_EXIT, ctrl->txbuf)) + rv = SOCK_TEST_TYPE_EXIT; + + else if (!strcmp (SOCK_TEST_TOKEN_HELP, ctrl->txbuf)) + dump_help (); + + else if (!strcmp (SOCK_TEST_TOKEN_SHOW_CFG, ctrl->txbuf)) + scm->dump_cfg = 1; + + else if (!strcmp (SOCK_TEST_TOKEN_VERBOSE, ctrl->txbuf)) + cfg_verbose_toggle (); + + else if (!strncmp (SOCK_TEST_TOKEN_TXBUF_SIZE, ctrl->txbuf, + strlen (SOCK_TEST_TOKEN_TXBUF_SIZE))) + cfg_txbuf_size_set (); + + else if (!strncmp (SOCK_TEST_TOKEN_NUM_TEST_SCKTS, ctrl->txbuf, + strlen (SOCK_TEST_TOKEN_NUM_TEST_SCKTS))) + cfg_num_test_sockets_set (); + + else if (!strncmp (SOCK_TEST_TOKEN_NUM_WRITES, ctrl->txbuf, + strlen (SOCK_TEST_TOKEN_NUM_WRITES))) + cfg_num_writes_set (); + + else if (!strncmp (SOCK_TEST_TOKEN_RXBUF_SIZE, ctrl->txbuf, + strlen (SOCK_TEST_TOKEN_RXBUF_SIZE))) + cfg_rxbuf_size_set (); + + else if (!strncmp (SOCK_TEST_TOKEN_RUN_UNI, ctrl->txbuf, + strlen (SOCK_TEST_TOKEN_RUN_UNI))) + rv = ctrl->cfg.test = SOCK_TEST_TYPE_UNI; + + else if (!strncmp (SOCK_TEST_TOKEN_RUN_BI, ctrl->txbuf, + strlen (SOCK_TEST_TOKEN_RUN_BI))) + rv = ctrl->cfg.test = SOCK_TEST_TYPE_BI; + + else + rv = SOCK_TEST_TYPE_ECHO; + + return rv; +} + +void +print_usage_and_exit (void) +{ + fprintf (stderr, + "sock_test_client [OPTIONS] \n" + " OPTIONS\n" + " -h Print this message and exit.\n" + " -c Print test config before test.\n" + " -w Write test results to .\n" + " -X Exit after running test.\n" + " -E Run Echo test.\n" + " -N Test Cfg: number of writes.\n" + " -R Test Cfg: rx buffer size.\n" + " -T Test Cfg: tx buffer size.\n" + " -U Run Uni-directional test.\n" + " -B Run Bi-directional test.\n" + " -V Verbose mode.\n"); + exit (1); +} + +int +main (int argc, char **argv) +{ + sock_client_main_t *scm = &sock_client_main; + sock_test_socket_t *ctrl = &scm->ctrl_socket; + int c, rv, errno_val; + sock_test_t post_test = SOCK_TEST_TYPE_NONE; + + sock_test_cfg_init (&ctrl->cfg); + sock_test_socket_buf_alloc (ctrl); + + opterr = 0; + while ((c = getopt (argc, argv, "chn:w:XE:I:N:R:T:UBV")) != -1) + switch (c) + { + case 'c': + scm->dump_cfg = 1; + break; + + case 's': + if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sockets) != 1) + if (sscanf (optarg, "%u", &ctrl->cfg.num_test_sockets) != 1) + { + fprintf (stderr, "ERROR: Invalid value for option -%c!\n", c); + print_usage_and_exit (); + } + if (!ctrl->cfg.num_test_sockets || + (ctrl->cfg.num_test_sockets > FD_SETSIZE)) + { + fprintf (stderr, "ERROR: Invalid number of sockets (%d)" + "specified for option -%c!\n" + " Valid range is 1 - %d\n", + ctrl->cfg.num_test_sockets, c, FD_SETSIZE); + print_usage_and_exit (); + } + break; + + case 'w': + fprintf (stderr, "Writing test results to files is TBD.\n"); + break; + + case 'X': + post_test = SOCK_TEST_TYPE_EXIT; + break; + + case 'E': + if (strlen (optarg) > ctrl->txbuf_size) + { + fprintf (stderr, + "ERROR: Option -%c value larger than txbuf size (%d)!\n", + optopt, ctrl->txbuf_size); + print_usage_and_exit (); + } + strcpy (ctrl->txbuf, optarg); + ctrl->cfg.test = SOCK_TEST_TYPE_ECHO; + break; + + case 'I': + if (sscanf (optarg, "0x%x", &ctrl->cfg.num_test_sockets) != 1) + if (sscanf (optarg, "%d", &ctrl->cfg.num_test_sockets) != 1) + { + fprintf (stderr, "ERROR: Invalid value for option -%c!\n", c); + print_usage_and_exit (); + } + if (ctrl->cfg.num_test_sockets > SOCK_TEST_CFG_MAX_TEST_SCKTS) + { + fprintf (stderr, "ERROR: value greater than max number test" + " sockets (%d)!", SOCK_TEST_CFG_MAX_TEST_SCKTS); + print_usage_and_exit (); + } + break; + + case 'N': + if (sscanf (optarg, "0x%lx", &ctrl->cfg.num_writes) != 1) + if (sscanf (optarg, "%ld", &ctrl->cfg.num_writes) != 1) + { + fprintf (stderr, "ERROR: Invalid value for option -%c!\n", c); + print_usage_and_exit (); + } + ctrl->cfg.total_bytes = ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; + break; + + case 'R': + if (sscanf (optarg, "0x%lx", &ctrl->cfg.rxbuf_size) != 1) + if (sscanf (optarg, "%ld", &ctrl->cfg.rxbuf_size) != 1) + { + fprintf (stderr, "ERROR: Invalid value for option -%c!\n", c); + print_usage_and_exit (); + } + if (ctrl->cfg.rxbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN) + { + ctrl->rxbuf_size = ctrl->cfg.rxbuf_size; + sock_test_buf_alloc (&ctrl->cfg, 1 /* is_rxbuf */ , + (uint8_t **) & ctrl->rxbuf, + &ctrl->rxbuf_size); + } + else + { + fprintf (stderr, + "ERROR: rxbuf size (%lu) less than minumum (%u)\n", + ctrl->cfg.rxbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN); + print_usage_and_exit (); + } + + break; + + case 'T': + if (sscanf (optarg, "0x%lx", &ctrl->cfg.txbuf_size) != 1) + if (sscanf (optarg, "%ld", &ctrl->cfg.txbuf_size) != 1) + { + fprintf (stderr, "ERROR: Invalid value for option -%c!\n", c); + print_usage_and_exit (); + } + if (ctrl->cfg.txbuf_size >= SOCK_TEST_CFG_BUF_SIZE_MIN) + { + ctrl->txbuf_size = ctrl->cfg.txbuf_size; + sock_test_buf_alloc (&ctrl->cfg, 0 /* is_rxbuf */ , + (uint8_t **) & ctrl->txbuf, + &ctrl->txbuf_size); + ctrl->cfg.total_bytes = + ctrl->cfg.num_writes * ctrl->cfg.txbuf_size; + } + else + { + fprintf (stderr, + "ERROR: txbuf size (%lu) less than minumum (%u)!\n", + ctrl->cfg.txbuf_size, SOCK_TEST_CFG_BUF_SIZE_MIN); + print_usage_and_exit (); + } + break; + + case 'U': + ctrl->cfg.test = SOCK_TEST_TYPE_UNI; + break; + + case 'B': + ctrl->cfg.test = SOCK_TEST_TYPE_BI; + break; + + case 'V': + ctrl->cfg.verbose = 1; + break; + + case '?': + switch (optopt) + { + case 'E': + case 'I': + case 'N': + case 'R': + case 'T': + case 'w': + fprintf (stderr, "ERROR: Option -%c requires an argument.\n", + optopt); + default: + if (isprint (optopt)) + fprintf (stderr, "ERROR: Unknown option `-%c'.\n", optopt); + else + fprintf (stderr, "ERROR: Unknown option character `\\x%x'.\n", + optopt); + } + /* fall thru */ + case 'h': + default: + print_usage_and_exit (); + } + + if (argc < (optind + 2)) + { + fprintf (stderr, "ERROR: Insufficient number of arguments!\n"); + print_usage_and_exit (); + } + +#ifdef VCL_TEST + ctrl->fd = vppcom_app_create ("vcl_test_client"); + if (ctrl->fd < 0) + { + errno = -ctrl->fd; + ctrl->fd = -1; + } + else + { + ctrl->fd = vppcom_session_create (VPPCOM_VRF_DEFAULT, VPPCOM_PROTO_TCP, + 0 /* is_nonblocking */ ); + if (ctrl->fd < 0) + { + errno = -ctrl->fd; + ctrl->fd = -1; + } + } +#else + ctrl->fd = socket (AF_INET, SOCK_STREAM, 0); +#endif + + if (ctrl->fd < 0) + { + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: socket failed (errno = %d)!\n", errno_val); + return ctrl->fd; + } + + memset (&scm->server_addr, 0, sizeof (scm->server_addr)); + + scm->server_addr.sin_family = AF_INET; + inet_pton (AF_INET, argv[optind++], &(scm->server_addr.sin_addr)); + scm->server_addr.sin_port = htons (atoi (argv[optind])); + +#ifdef VCL_TEST + scm->server_endpt.vrf = VPPCOM_VRF_DEFAULT; + scm->server_endpt.is_ip4 = (scm->server_addr.sin_family == AF_INET); + scm->server_endpt.ip = (uint8_t *) & scm->server_addr.sin_addr; + scm->server_endpt.port = (uint16_t) scm->server_addr.sin_port; +#endif + + do + { + printf ("\nCLIENT: Connecting to server...\n"); + +#ifdef VCL_TEST + rv = vppcom_session_connect (ctrl->fd, &scm->server_endpt); +#else + rv = + connect (ctrl->fd, (struct sockaddr *) &scm->server_addr, + sizeof (scm->server_addr)); +#endif + if (rv < 0) + { + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: connect failed (errno = %d)!\n", + errno_val); + } + + sock_test_cfg_sync (ctrl); + printf ("CLIENT (fd %d): Control socket connected.\n", ctrl->fd); + } + while (rv < 0); + + sock_test_connect_test_sockets (ctrl->cfg.num_test_sockets); + + while (ctrl->cfg.test != SOCK_TEST_TYPE_EXIT) + { + if (scm->dump_cfg) + { + sock_test_cfg_dump (&ctrl->cfg, 1 /* is_client */ ); + scm->dump_cfg = 0; + } + + switch (ctrl->cfg.test) + { + case SOCK_TEST_TYPE_ECHO: + echo_test_client (); + break; + + case SOCK_TEST_TYPE_UNI: + case SOCK_TEST_TYPE_BI: + stream_test_client (ctrl->cfg.test); + break; + + case SOCK_TEST_TYPE_EXIT: + continue; + + case SOCK_TEST_TYPE_NONE: + default: + break; + } + switch (post_test) + { + case SOCK_TEST_TYPE_EXIT: + switch (ctrl->cfg.test) + { + case SOCK_TEST_TYPE_EXIT: + case SOCK_TEST_TYPE_UNI: + case SOCK_TEST_TYPE_BI: + case SOCK_TEST_TYPE_ECHO: + ctrl->cfg.test = SOCK_TEST_TYPE_EXIT; + continue; + + case SOCK_TEST_TYPE_NONE: + default: + break; + } + break; + + case SOCK_TEST_TYPE_NONE: + case SOCK_TEST_TYPE_ECHO: + case SOCK_TEST_TYPE_UNI: + case SOCK_TEST_TYPE_BI: + default: + break; + } + + memset (ctrl->txbuf, 0, ctrl->txbuf_size); + memset (ctrl->rxbuf, 0, ctrl->rxbuf_size); + + printf ("\nType some characters and hit \n" + "('" SOCK_TEST_TOKEN_HELP "' for help): "); + + if (fgets (ctrl->txbuf, ctrl->txbuf_size, stdin) != NULL) + { + if (strlen (ctrl->txbuf) == 1) + { + printf ("\nCLIENT: Nothing to send! Please try again...\n"); + continue; + } + ctrl->txbuf[strlen (ctrl->txbuf) - 1] = 0; // chomp the newline. + + /* Parse input for keywords */ + ctrl->cfg.test = parse_input (); + } + } + + exit_client (); +#ifdef VCL_TEST + vppcom_session_close (ctrl->fd); + vppcom_app_destroy (); +#else + close (ctrl->fd); +#endif + return 0; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/uri/sock_test_server.c b/src/uri/sock_test_server.c new file mode 100644 index 00000000..52c60dc4 --- /dev/null +++ b/src/uri/sock_test_server.c @@ -0,0 +1,586 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct +{ + uint8_t is_alloc; + int fd; + uint8_t *buf; + uint32_t buf_size; + sock_test_cfg_t cfg; + sock_test_stats_t stats; +#ifdef VCL_TEST + vppcom_endpt_t endpt; + uint8_t ip[16]; +#endif +} sock_server_conn_t; + +#define SOCK_SERVER_MAX_TEST_CONN 10 +typedef struct +{ + int listen_fd; + size_t num_conn; + size_t conn_pool_size; + sock_server_conn_t *conn_pool; + int nfds; + fd_set rd_fdset; + fd_set wr_fdset; + struct timeval timeout; +} sock_server_main_t; + +sock_server_main_t sock_server_main; + +static inline int +get_nfds (void) +{ + sock_server_main_t *ssm = &sock_server_main; + int i, nfds; + + for (nfds = i = 0; i < FD_SETSIZE; i++) + { + if (FD_ISSET (i, &ssm->rd_fdset) || FD_ISSET (i, &ssm->wr_fdset)) + nfds = i + 1; + } + return nfds; +} + +static inline void +conn_fdset_set (sock_server_conn_t * conn, fd_set * fdset) +{ + sock_server_main_t *ssm = &sock_server_main; + + FD_SET (conn->fd, fdset); + ssm->nfds = get_nfds (); +} + +static inline void +conn_fdset_clr (sock_server_conn_t * conn, fd_set * fdset) +{ + sock_server_main_t *ssm = &sock_server_main; + + FD_CLR (conn->fd, fdset); + ssm->nfds = get_nfds (); +} + +static inline void +conn_pool_expand (size_t expand_size) +{ + sock_server_main_t *ssm = &sock_server_main; + sock_server_conn_t *conn_pool; + size_t new_size = ssm->conn_pool_size + expand_size; + int i; + + conn_pool = realloc (ssm->conn_pool, new_size * sizeof (*ssm->conn_pool)); + if (conn_pool) + { + for (i = ssm->conn_pool_size; i < new_size; i++) + { + sock_server_conn_t *conn = &conn_pool[i]; + memset (conn, 0, sizeof (*conn)); + sock_test_cfg_init (&conn->cfg); + sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ , + &conn->buf, &conn->buf_size); + conn->cfg.txbuf_size = conn->cfg.rxbuf_size; + } + + ssm->conn_pool = conn_pool; + ssm->conn_pool_size = new_size; + } + else + { + int errno_val = errno; + perror ("ERROR in conn_pool_expand()"); + fprintf (stderr, "ERROR: Memory allocation failed (errno = %d)!\n", + errno_val); + } +} + +static inline sock_server_conn_t * +conn_pool_alloc (void) +{ + sock_server_main_t *ssm = &sock_server_main; + int i; + + for (i = 0; i < ssm->conn_pool_size; i++) + { + if (!ssm->conn_pool[i].is_alloc) + { +#ifdef VCL_TEST + ssm->conn_pool[i].endpt.ip = ssm->conn_pool[i].ip; +#endif + ssm->conn_pool[i].is_alloc = 1; + return (&ssm->conn_pool[i]); + } + } + + return 0; +} + +static inline void +conn_pool_free (sock_server_conn_t * conn) +{ + sock_server_main_t *ssm = &sock_server_main; + + conn_fdset_clr (conn, &ssm->rd_fdset); + conn_fdset_clr (conn, &ssm->wr_fdset); + conn->fd = 0; + conn->is_alloc = 0; +} + +static inline void +sync_config_and_reply (sock_server_conn_t * conn, sock_test_cfg_t * rx_cfg) +{ + conn->cfg = *rx_cfg; + sock_test_buf_alloc (&conn->cfg, 1 /* is_rxbuf */ , + &conn->buf, &conn->buf_size); + conn->cfg.txbuf_size = conn->cfg.rxbuf_size; + + if (conn->cfg.verbose) + { + printf ("\nSERVER (fd %d): Replying to cfg message!\n", conn->fd); + sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ ); + } + (void) sock_test_write (conn->fd, (uint8_t *) & conn->cfg, + sizeof (conn->cfg), NULL, conn->cfg.verbose); +} + +static void +stream_test_server_start_stop (sock_server_conn_t * conn, + sock_test_cfg_t * rx_cfg) +{ + sock_server_main_t *ssm = &sock_server_main; + int client_fd = conn->fd; + sock_test_t test = rx_cfg->test; + + if (rx_cfg->ctrl_handle == conn->fd) + { + int i; + clock_gettime (CLOCK_REALTIME, &conn->stats.stop); + + for (i = 0; i < ssm->conn_pool_size; i++) + { + sock_server_conn_t *tc = &ssm->conn_pool[i]; + + if (tc->cfg.ctrl_handle == conn->fd) + { + sock_test_stats_accumulate (&conn->stats, &tc->stats); + + if (conn->cfg.verbose) + { + static char buf[64]; + + sprintf (buf, "SERVER (fd %d) RESULTS", tc->fd); + sock_test_stats_dump (buf, &tc->stats, 1 /* show_rx */ , + test == SOCK_TEST_TYPE_BI + /* show tx */ , + conn->cfg.verbose); + } + } + } + + sock_test_stats_dump ("SERVER RESULTS", &conn->stats, 1 /* show_rx */ , + (test == SOCK_TEST_TYPE_BI) /* show_tx */ , + conn->cfg.verbose); + sock_test_cfg_dump (&conn->cfg, 0 /* is_client */ ); + if (conn->cfg.verbose) + { + printf (" sock server main\n" + SOCK_TEST_SEPARATOR_STRING + " buf: %p\n" + " buf size: %u (0x%08x)\n" + SOCK_TEST_SEPARATOR_STRING, + conn->buf, conn->buf_size, conn->buf_size); + } + + sync_config_and_reply (conn, rx_cfg); + printf ("\nSERVER (fd %d): %s-directional Stream Test Complete!\n" + SOCK_TEST_BANNER_STRING "\n", conn->fd, + test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni"); + } + else + { + printf ("\n" SOCK_TEST_BANNER_STRING + "SERVER (fd %d): %s-directional Stream Test!\n" + " Sending client the test cfg to start streaming data...\n", + client_fd, test == SOCK_TEST_TYPE_BI ? "Bi" : "Uni"); + + rx_cfg->ctrl_handle = (rx_cfg->ctrl_handle == ~0) ? conn->fd : + rx_cfg->ctrl_handle; + + sync_config_and_reply (conn, rx_cfg); + + /* read the 1st chunk, record start time */ + memset (&conn->stats, 0, sizeof (conn->stats)); + clock_gettime (CLOCK_REALTIME, &conn->stats.start); + } +} + + +static inline void +stream_test_server (sock_server_conn_t * conn, int rx_bytes) +{ + int client_fd = conn->fd; + sock_test_t test = conn->cfg.test; + + if (test == SOCK_TEST_TYPE_BI) + (void) sock_test_write (client_fd, conn->buf, rx_bytes, &conn->stats, + conn->cfg.verbose); + + if (conn->stats.rx_bytes >= conn->cfg.total_bytes) + { + clock_gettime (CLOCK_REALTIME, &conn->stats.stop); + } +} + +static inline void +new_client (void) +{ + sock_server_main_t *ssm = &sock_server_main; + int client_fd; + sock_server_conn_t *conn; + + if (ssm->conn_pool_size < (ssm->num_conn + SOCK_SERVER_MAX_TEST_CONN + 1)) + conn_pool_expand (SOCK_SERVER_MAX_TEST_CONN + 1); + + conn = conn_pool_alloc (); + if (!conn) + { + fprintf (stderr, "\nERROR: No free connections!\n"); + return; + } + +#ifdef VCL_TEST + client_fd = vppcom_session_accept (ssm->listen_fd, &conn->endpt, + -1.0 /* wait forever */ ); +#else + client_fd = accept (ssm->listen_fd, (struct sockaddr *) NULL, NULL); +#endif + if (client_fd < 0) + { + int errno_val; + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: accept failed (errno = %d)!\n", errno_val); + } + + printf ("SERVER: Got a connection -- fd = %d (0x%08x)!\n", + client_fd, client_fd); + + conn->fd = client_fd; + conn_fdset_set (conn, &ssm->rd_fdset); +} + +int +main (int argc, char **argv) +{ + sock_server_main_t *ssm = &sock_server_main; + int client_fd, rv, main_rv = 0; + int tx_bytes, rx_bytes, nbytes; + sock_server_conn_t *conn; + sock_test_cfg_t *rx_cfg; + uint32_t xtra = 0; + uint64_t xtra_bytes = 0; + struct sockaddr_in servaddr; + int errno_val; + int v, i; + uint16_t port = SOCK_TEST_SERVER_PORT; + fd_set _rfdset, *rfdset = &_rfdset; +#ifdef VCL_TEST + vppcom_endpt_t endpt; +#else + fd_set _wfdset, *wfdset = &_wfdset; +#endif + + if ((argc == 2) && (sscanf (argv[1], "%d", &v) == 1)) + port = (uint16_t) v; + + conn_pool_expand (SOCK_SERVER_MAX_TEST_CONN + 1); + +#ifdef VCL_TEST + rv = vppcom_app_create ("vcl_test_server"); + if (rv) + { + errno = -rv; + ssm->listen_fd = -1; + } + else + { + ssm->listen_fd = + vppcom_session_create (VPPCOM_VRF_DEFAULT, VPPCOM_PROTO_TCP, + 0 /* is_nonblocking */ ); + } +#else + ssm->listen_fd = socket (AF_INET, SOCK_STREAM, 0); +#endif + if (ssm->listen_fd < 0) + { + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: socket() failed (errno = %d)!\n", errno_val); + return ssm->listen_fd; + } + + memset (&servaddr, 0, sizeof (servaddr)); + + servaddr.sin_family = AF_INET; + servaddr.sin_addr.s_addr = htons (INADDR_ANY); + servaddr.sin_port = htons (port); + +#ifdef VCL_TEST + endpt.vrf = VPPCOM_VRF_DEFAULT; + endpt.is_ip4 = (servaddr.sin_family == AF_INET); + endpt.ip = (uint8_t *) & servaddr.sin_addr; + endpt.port = (uint16_t) servaddr.sin_port; + + rv = vppcom_session_bind (ssm->listen_fd, &endpt); + if (rv) + { + errno = -rv; + rv = -1; + } +#else + rv = + bind (ssm->listen_fd, (struct sockaddr *) &servaddr, sizeof (servaddr)); +#endif + if (rv < 0) + { + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: bind failed (errno = %d)!\n", errno_val); + return rv; + } + +#ifdef VCL_TEST + rv = vppcom_session_listen (ssm->listen_fd, 10); + if (rv) + { + errno = -rv; + rv = -1; + } +#else + rv = listen (ssm->listen_fd, 10); +#endif + if (rv < 0) + { + errno_val = errno; + perror ("ERROR in main()"); + fprintf (stderr, "ERROR: listen failed (errno = %d)!\n", errno_val); + return rv; + } + + FD_ZERO (&ssm->wr_fdset); + FD_ZERO (&ssm->rd_fdset); + + FD_SET (ssm->listen_fd, &ssm->rd_fdset); + ssm->nfds = ssm->listen_fd + 1; + + printf ("\nSERVER: Waiting for a client to connect on port %d...\n", port); + + while (1) + { + _rfdset = ssm->rd_fdset; + +#ifdef VCL_TEST + rv = vppcom_select (ssm->nfds, (uint64_t *) rfdset, NULL, NULL, 0); +#else + { + struct timeval timeout; + timeout = ssm->timeout; + _wfdset = ssm->wr_fdset; + rv = select (ssm->nfds, rfdset, wfdset, NULL, &timeout); + } +#endif + if (rv < 0) + { + perror ("select()"); + fprintf (stderr, "\nERROR: select() failed -- aborting!\n"); + main_rv = -1; + goto done; + } + else if (rv == 0) + continue; + + if (FD_ISSET (ssm->listen_fd, rfdset)) + new_client (); + + for (i = 0; i < ssm->conn_pool_size; i++) + { + if (!ssm->conn_pool[i].is_alloc) + continue; + + conn = &ssm->conn_pool[i]; + client_fd = conn->fd; + + if (FD_ISSET (client_fd, rfdset)) + { + rx_bytes = sock_test_read (client_fd, conn->buf, + conn->buf_size, &conn->stats); + if (rx_bytes > 0) + { + rx_cfg = (sock_test_cfg_t *) conn->buf; + if (rx_cfg->magic == SOCK_TEST_CFG_CTRL_MAGIC) + { + if (rx_cfg->verbose) + { + printf ("SERVER (fd %d): Received a cfg message!\n", + client_fd); + sock_test_cfg_dump (rx_cfg, 0 /* is_client */ ); + } + + if (rx_bytes != sizeof (*rx_cfg)) + { + printf ("SERVER (fd %d): Invalid cfg message " + "size (%d)!\n Should be %lu bytes.\n", + client_fd, rx_bytes, sizeof (*rx_cfg)); + conn->cfg.rxbuf_size = 0; + conn->cfg.num_writes = 0; + if (conn->cfg.verbose) + { + printf ("SERVER (fd %d): Replying to " + "cfg message!\n", client_fd); + sock_test_cfg_dump (rx_cfg, 0 /* is_client */ ); + } + sock_test_write (client_fd, (uint8_t *) & conn->cfg, + sizeof (conn->cfg), NULL, + conn->cfg.verbose); + continue; + } + + switch (rx_cfg->test) + { + case SOCK_TEST_TYPE_NONE: + case SOCK_TEST_TYPE_ECHO: + sync_config_and_reply (conn, rx_cfg); + break; + + case SOCK_TEST_TYPE_BI: + case SOCK_TEST_TYPE_UNI: + stream_test_server_start_stop (conn, rx_cfg); + break; + + case SOCK_TEST_TYPE_EXIT: + printf ("SERVER: Have a great day, " + "connection %d!\n", client_fd); +#ifdef VCL_TEST + vppcom_session_close (client_fd); +#else + close (client_fd); +#endif + conn_pool_free (conn); + + if (ssm->nfds == (ssm->listen_fd + 1)) + { + printf ("SERVER: All client connections " + "closed.\n\nSERVER: " + "May the force be with you!\n\n"); + goto done; + } + break; + + default: + fprintf (stderr, "ERROR: Unknown test type!\n"); + sock_test_cfg_dump (rx_cfg, 0 /* is_client */ ); + break; + } + continue; + } + + else if ((conn->cfg.test == SOCK_TEST_TYPE_UNI) || + (conn->cfg.test == SOCK_TEST_TYPE_BI)) + { + stream_test_server (conn, rx_bytes); + continue; + } + + else if (strlen ((char *) conn->buf)) + printf ("\nSERVER (fd %d): RX (%d bytes) - '%s'\n", + conn->fd, rx_bytes, conn->buf); + } + else // rx_bytes < 0 + { + if (errno == ECONNRESET) + { + printf ("\nSERVER: Connection reset by remote peer.\n" + " Y'all have a great day now!\n\n"); + break; + } + else + continue; + } + + if (isascii (conn->buf[0]) && strlen ((const char *) conn->buf)) + { + if (xtra) + fprintf (stderr, + "ERROR: FIFO not drained in previous test!\n" + " extra chunks %u (0x%x)\n" + " extra bytes %lu (0x%lx)\n", + xtra, xtra, xtra_bytes, xtra_bytes); + + xtra = 0; + xtra_bytes = 0; + + if (conn->cfg.verbose) + printf ("SERVER (fd %d): Echoing back\n", client_fd); + + nbytes = strlen ((const char *) conn->buf) + 1; + + tx_bytes = sock_test_write (client_fd, conn->buf, + nbytes, &conn->stats, + conn->cfg.verbose); + if (tx_bytes >= 0) + printf ("SERVER (fd %d): TX (%d bytes) - '%s'\n", + conn->fd, tx_bytes, conn->buf); + } + + else // Extraneous read data from non-echo tests??? + { + xtra++; + xtra_bytes += rx_bytes; + } + } + } + } + +done: +#ifdef VCL_TEST + vppcom_session_close (ssm->listen_fd); + vppcom_app_destroy (); +#else + close (ssm->listen_fd); +#endif + if (ssm->conn_pool) + free (ssm->conn_pool); + + return main_rv; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/uri/vcl_test_client.c b/src/uri/vcl_test_client.c new file mode 100644 index 00000000..7ab8824f --- /dev/null +++ b/src/uri/vcl_test_client.c @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#define VCL_TEST + +#include +#include + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/uri/vcl_test_server.c b/src/uri/vcl_test_server.c new file mode 100644 index 00000000..660d705e --- /dev/null +++ b/src/uri/vcl_test_server.c @@ -0,0 +1,27 @@ +/* + * 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. + */ + +#define VCL_TEST + +#include +#include + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/uri/vppcom.c b/src/uri/vppcom.c new file mode 100644 index 00000000..44208cc0 --- /dev/null +++ b/src/uri/vppcom.c @@ -0,0 +1,2453 @@ +/* + * Copyright (c) 2016 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +/* declare message handlers for each api */ + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include +#undef vl_printfun + +#if (CLIB_DEBUG > 0) +/* Set VPPCOM_DEBUG 2 for connection debug, 3 for read/write debug output */ +#define VPPCOM_DEBUG 1 +#else +#define VPPCOM_DEBUG 0 +#endif + +/* + * VPPCOM Private definitions and functions. + */ +typedef enum +{ + STATE_APP_START, + STATE_APP_CONN_VPP, + STATE_APP_ENABLED, + STATE_APP_ATTACHED, +} app_state_t; + +typedef enum +{ + STATE_START, + STATE_CONNECT, + STATE_LISTEN, + STATE_ACCEPT, + STATE_DISCONNECT, + STATE_FAILED +} session_state_t; + +typedef struct +{ + volatile session_state_t state; + + svm_fifo_t *server_rx_fifo; + svm_fifo_t *server_tx_fifo; + u32 sm_seg_index; + u64 vpp_session_handle; + unix_shared_memory_queue_t *event_queue; + + /* Socket configuration state */ + u8 is_server; + u8 is_listen; + u8 is_cut_thru; + u8 is_nonblocking; + u32 vrf; + u8 is_ip4; + u8 ip[16]; + u16 port; + u8 proto; + u64 client_queue_address; + u64 options[16]; +} session_t; + +typedef struct vppcom_cfg_t_ +{ + u64 heapsize; + u64 segment_baseva; + u32 segment_size; + u32 add_segment_size; + u32 preallocated_fifo_pairs; + u32 rx_fifo_size; + u32 tx_fifo_size; + u32 event_queue_size; + u32 listen_queue_size; + f64 app_timeout; + f64 session_timeout; + f64 accept_timeout; +} vppcom_cfg_t; + +typedef struct vppcom_main_t_ +{ + u8 init; + u32 *client_session_index_fifo; + volatile u32 bind_session_index; + u32 tx_event_id; + int main_cpu; + + /* vpe input queue */ + unix_shared_memory_queue_t *vl_input_queue; + + /* API client handle */ + u32 my_client_index; + + /* Session pool */ + clib_spinlock_t sessions_lockp; + session_t *sessions; + + /* Hash table for disconnect processing */ + uword *session_index_by_vpp_handles; + + /* Select bitmaps */ + clib_bitmap_t *rd_bitmap; + clib_bitmap_t *wr_bitmap; + clib_bitmap_t *ex_bitmap; + + /* Our event queue */ + unix_shared_memory_queue_t *app_event_queue; + + /* unique segment name counter */ + u32 unique_segment_index; + + pid_t my_pid; + + /* For deadman timers */ + clib_time_t clib_time; + + /* State of the connection, shared between msg RX thread and main thread */ + volatile app_state_t app_state; + + vppcom_cfg_t cfg; + + /* VNET_API_ERROR_FOO -> "Foo" hash table */ + uword *error_string_by_error_number; +} vppcom_main_t; + +vppcom_main_t vppcom_main = {.my_client_index = ~0 }; + +static const char * +vppcom_app_state_str (app_state_t state) +{ + char *st; + + switch (state) + { + case STATE_APP_START: + st = "STATE_APP_START"; + break; + + case STATE_APP_CONN_VPP: + st = "STATE_APP_CONN_VPP"; + break; + + case STATE_APP_ENABLED: + st = "STATE_APP_ENABLED"; + break; + + case STATE_APP_ATTACHED: + st = "STATE_APP_ATTACHED"; + break; + + default: + st = "UNKNOWN_APP_STATE"; + break; + } + + return st; +} + +static const char * +vppcom_session_state_str (session_state_t state) +{ + char *st; + + switch (state) + { + case STATE_START: + st = "STATE_START"; + break; + + case STATE_CONNECT: + st = "STATE_CONNECT"; + break; + + case STATE_LISTEN: + st = "STATE_LISTEN"; + break; + + case STATE_ACCEPT: + st = "STATE_ACCEPT"; + break; + + case STATE_DISCONNECT: + st = "STATE_DISCONNECT"; + break; + + case STATE_FAILED: + st = "STATE_FAILED"; + break; + + default: + st = "UNKNOWN_STATE"; + break; + } + + return st; +} + +/* + * VPPCOM Utility Functions + */ +static inline int +vppcom_session_at_index (u32 session_index, session_t * volatile *sess) +{ + vppcom_main_t *vcm = &vppcom_main; + + /* Assumes that caller has acquired spinlock: vcm->sessions_lockp */ + if (PREDICT_FALSE ((session_index == ~0) || + pool_is_free_index (vcm->sessions, session_index))) + { + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return VPPCOM_EBADFD; + } + *sess = pool_elt_at_index (vcm->sessions, session_index); + return VPPCOM_OK; +} + +static int +vppcom_connect_to_vpp (char *app_name) +{ + api_main_t *am = &api_main; + vppcom_main_t *vcm = &vppcom_main; + + if (VPPCOM_DEBUG > 0) + printf ("\nConnecting to VPP api..."); + if (vl_client_connect_to_vlib ("/vpe-api", app_name, 32) < 0) + { + clib_warning ("[%d] connect to vpp (%s) failed!", + vcm->my_pid, app_name); + return VPPCOM_ECONNREFUSED; + } + + vcm->vl_input_queue = am->shmem_hdr->vl_input_queue; + vcm->my_client_index = am->my_client_index; + if (VPPCOM_DEBUG > 0) + printf (" connected!\n"); + + vcm->app_state = STATE_APP_CONN_VPP; + return VPPCOM_OK; +} + +static u8 * +format_api_error (u8 * s, va_list * args) +{ + vppcom_main_t *vcm = &vppcom_main; + i32 error = va_arg (*args, u32); + uword *p; + + p = hash_get (vcm->error_string_by_error_number, -error); + + if (p) + s = format (s, "%s (%d)", p[0], error); + else + s = format (s, "%d", error); + return s; +} + +static void +vppcom_init_error_string_table (void) +{ + vppcom_main_t *vcm = &vppcom_main; + + vcm->error_string_by_error_number = hash_create (0, sizeof (uword)); + +#define _(n,v,s) hash_set (vcm->error_string_by_error_number, -v, s); + foreach_vnet_api_error; +#undef _ + + hash_set (vcm->error_string_by_error_number, 99, "Misc"); +} + +static inline int +vppcom_wait_for_app_state_change (app_state_t app_state) +{ + vppcom_main_t *vcm = &vppcom_main; + f64 timeout = clib_time_now (&vcm->clib_time) + vcm->cfg.app_timeout; + + while (clib_time_now (&vcm->clib_time) < timeout) + { + if (vcm->app_state == app_state) + return VPPCOM_OK; + } + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] timeout waiting for state %s (%d)", vcm->my_pid, + vppcom_app_state_str (app_state), app_state); + return VPPCOM_ETIMEDOUT; +} + +static inline int +vppcom_wait_for_session_state_change (u32 session_index, + session_state_t state, + f64 wait_for_time) +{ + vppcom_main_t *vcm = &vppcom_main; + f64 timeout = clib_time_now (&vcm->clib_time) + wait_for_time; + session_t *volatile session; + int rv; + + do + { + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + return rv; + } + if (session->state == state) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + return VPPCOM_OK; + } + clib_spinlock_unlock (&vcm->sessions_lockp); + } + while (clib_time_now (&vcm->clib_time) < timeout); + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] timeout waiting for state %s (%d)", vcm->my_pid, + vppcom_session_state_str (state), state); + return VPPCOM_ETIMEDOUT; +} + +static inline int +vppcom_wait_for_client_session_index (f64 wait_for_time) +{ + vppcom_main_t *vcm = &vppcom_main; + f64 timeout = clib_time_now (&vcm->clib_time) + wait_for_time; + + do + { + if (clib_fifo_elts (vcm->client_session_index_fifo)) + return VPPCOM_OK; + } + while (clib_time_now (&vcm->clib_time) < timeout); + + if (wait_for_time == 0) + return VPPCOM_EAGAIN; + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] timeout waiting for client_session_index", + vcm->my_pid); + return VPPCOM_ETIMEDOUT; +} + +/* + * VPP-API message functions + */ +static void +vppcom_send_session_enable_disable (u8 is_enable) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_session_enable_disable_t *bmp; + bmp = vl_msg_api_alloc (sizeof (*bmp)); + memset (bmp, 0, sizeof (*bmp)); + + bmp->_vl_msg_id = ntohs (VL_API_SESSION_ENABLE_DISABLE); + bmp->client_index = vcm->my_client_index; + bmp->context = htonl (0xfeedface); + bmp->is_enable = is_enable; + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & bmp); +} + +static int +vppcom_app_session_enable (void) +{ + vppcom_main_t *vcm = &vppcom_main; + int rv; + + if (vcm->app_state != STATE_APP_ENABLED) + { + vppcom_send_session_enable_disable (1 /* is_enabled == TRUE */ ); + rv = vppcom_wait_for_app_state_change (STATE_APP_ENABLED); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] Session enable timed out, rv = %s (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + return rv; + } + } + return VPPCOM_OK; +} + +static void + vl_api_session_enable_disable_reply_t_handler + (vl_api_session_enable_disable_reply_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + + if (mp->retval) + { + clib_warning ("[%d] session_enable_disable failed: %U", vcm->my_pid, + format_api_error, ntohl (mp->retval)); + } + else + vcm->app_state = STATE_APP_ENABLED; +} + +static void +vppcom_app_send_attach (void) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_application_attach_t *bmp; + bmp = vl_msg_api_alloc (sizeof (*bmp)); + memset (bmp, 0, sizeof (*bmp)); + + bmp->_vl_msg_id = ntohs (VL_API_APPLICATION_ATTACH); + bmp->client_index = vcm->my_client_index; + bmp->context = htonl (0xfeedface); + bmp->options[APP_OPTIONS_FLAGS] = + APP_OPTIONS_FLAGS_USE_FIFO | APP_OPTIONS_FLAGS_ADD_SEGMENT; + bmp->options[SESSION_OPTIONS_SEGMENT_SIZE] = vcm->cfg.segment_size; + bmp->options[SESSION_OPTIONS_ADD_SEGMENT_SIZE] = vcm->cfg.add_segment_size; + bmp->options[SESSION_OPTIONS_RX_FIFO_SIZE] = vcm->cfg.rx_fifo_size; + bmp->options[SESSION_OPTIONS_TX_FIFO_SIZE] = vcm->cfg.tx_fifo_size; + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & bmp); +} + +static int +vppcom_app_attach (void) +{ + vppcom_main_t *vcm = &vppcom_main; + int rv; + + vppcom_app_send_attach (); + rv = vppcom_wait_for_app_state_change (STATE_APP_ATTACHED); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] application attach timed out, rv = %s (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + return rv; + } + return VPPCOM_OK; +} + +static void +vppcom_app_detach (void) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_application_detach_t *bmp; + bmp = vl_msg_api_alloc (sizeof (*bmp)); + memset (bmp, 0, sizeof (*bmp)); + + bmp->_vl_msg_id = ntohs (VL_API_APPLICATION_DETACH); + bmp->client_index = vcm->my_client_index; + bmp->context = htonl (0xfeedface); + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & bmp); +} + +static void +vl_api_application_attach_reply_t_handler (vl_api_application_attach_reply_t * + mp) +{ + vppcom_main_t *vcm = &vppcom_main; + static svm_fifo_segment_create_args_t _a; + svm_fifo_segment_create_args_t *a = &_a; + int rv; + + memset (a, 0, sizeof (*a)); + if (mp->retval) + { + clib_warning ("[%d] attach failed: %U", vcm->my_pid, + format_api_error, ntohl (mp->retval)); + return; + } + + if (mp->segment_name_length == 0) + { + clib_warning ("[%d] segment_name_length zero", vcm->my_pid); + return; + } + + a->segment_name = (char *) mp->segment_name; + a->segment_size = mp->segment_size; + + ASSERT (mp->app_event_queue_address); + + /* Attach to the segment vpp created */ + rv = svm_fifo_segment_attach (a); + vec_reset_length (a->new_segment_indices); + if (PREDICT_FALSE (rv)) + { + clib_warning ("[%d] svm_fifo_segment_attach ('%s') failed", vcm->my_pid, + mp->segment_name); + return; + } + + vcm->app_event_queue = + uword_to_pointer (mp->app_event_queue_address, + unix_shared_memory_queue_t *); + + vcm->app_state = STATE_APP_ATTACHED; +} + +static void +vl_api_application_detach_reply_t_handler (vl_api_application_detach_reply_t * + mp) +{ + vppcom_main_t *vcm = &vppcom_main; + + if (mp->retval) + clib_warning ("[%d] detach failed: %U", vcm->my_pid, format_api_error, + ntohl (mp->retval)); + + vcm->app_state = STATE_APP_ENABLED; +} + +static void +vl_api_disconnect_session_reply_t_handler (vl_api_disconnect_session_reply_t * + mp) +{ + vppcom_main_t *vcm = &vppcom_main; + uword *p; + + p = hash_get (vcm->session_index_by_vpp_handles, mp->handle); + if (p) + { + session_t *session = 0; + int rv; + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (p[0], &session); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, p[0]); + } + hash_unset (vcm->session_index_by_vpp_handles, mp->handle); + session->state = STATE_DISCONNECT; + clib_spinlock_unlock (&vcm->sessions_lockp); + } + else + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] couldn't find session key %llx", vcm->my_pid, + mp->handle); + } + + if (mp->retval) + clib_warning ("[%d] disconnect_session failed: %U", vcm->my_pid, + format_api_error, ntohl (mp->retval)); +} + +static void +vl_api_map_another_segment_t_handler (vl_api_map_another_segment_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + static svm_fifo_segment_create_args_t _a; + svm_fifo_segment_create_args_t *a = &_a; + int rv; + + memset (a, 0, sizeof (*a)); + a->segment_name = (char *) mp->segment_name; + a->segment_size = mp->segment_size; + /* Attach to the segment vpp created */ + rv = svm_fifo_segment_attach (a); + vec_reset_length (a->new_segment_indices); + if (PREDICT_FALSE (rv)) + { + clib_warning ("[%d] svm_fifo_segment_attach ('%s') failed", + vcm->my_pid, mp->segment_name); + return; + } + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] mapped new segment '%s' size %d", vcm->my_pid, + mp->segment_name, mp->segment_size); +} + +static void +vl_api_disconnect_session_t_handler (vl_api_disconnect_session_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + vl_api_disconnect_session_reply_t *rmp; + uword *p; + int rv = 0; + + p = hash_get (vcm->session_index_by_vpp_handles, mp->handle); + if (p) + { + int rval; + clib_spinlock_lock (&vcm->sessions_lockp); + rval = vppcom_session_at_index (p[0], &session); + if (PREDICT_FALSE (rval)) + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, p[0]); + } + else + pool_put (vcm->sessions, session); + clib_spinlock_unlock (&vcm->sessions_lockp); + hash_unset (vcm->session_index_by_vpp_handles, mp->handle); + } + else + { + clib_warning ("[%d] couldn't find session key %llx", vcm->my_pid, + mp->handle); + rv = -11; + } + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + + rmp->_vl_msg_id = ntohs (VL_API_DISCONNECT_SESSION_REPLY); + rmp->retval = htonl (rv); + rmp->handle = mp->handle; + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & rmp); +} + +static void +vl_api_reset_session_t_handler (vl_api_reset_session_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + vl_api_reset_session_reply_t *rmp; + uword *p; + int rv = 0; + + p = hash_get (vcm->session_index_by_vpp_handles, mp->handle); + if (p) + { + int rval; + clib_spinlock_lock (&vcm->sessions_lockp); + rval = vppcom_session_at_index (p[0], &session); + if (PREDICT_FALSE (rval)) + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, p[0]); + } + else + pool_put (vcm->sessions, session); + clib_spinlock_unlock (&vcm->sessions_lockp); + hash_unset (vcm->session_index_by_vpp_handles, mp->handle); + } + else + { + clib_warning ("[%d] couldn't find session key %llx", vcm->my_pid, + mp->handle); + rv = -11; + } + + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_RESET_SESSION_REPLY); + rmp->retval = htonl (rv); + rmp->handle = mp->handle; + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & rmp); +} + +static void +vl_api_connect_sock_reply_t_handler (vl_api_connect_sock_reply_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session; + u32 session_index; + svm_fifo_t *rx_fifo, *tx_fifo; + u8 is_cut_thru = 0; + int rv; + + if (mp->retval) + { + clib_warning ("[%d] connect failed: %U", vcm->my_pid, format_api_error, + ntohl (mp->retval)); + return; + } + + session_index = ntohl (mp->app_connect); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] app_connect = %d 0x%08x", vcm->my_pid, + session_index, session_index); + + clib_spinlock_lock (&vcm->sessions_lockp); + if (pool_is_free_index (vcm->sessions, session_index)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid %d is closed!", + vcm->my_pid, session_index); + return; + } + + /* We've been redirected */ + if (mp->segment_name_length > 0) + { + static svm_fifo_segment_create_args_t _a; + svm_fifo_segment_create_args_t *a = &_a; + + is_cut_thru = 1; + memset (a, 0, sizeof (*a)); + a->segment_name = (char *) mp->segment_name; + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] cut-thru segment: %s", vcm->my_pid, + a->segment_name); + rv = svm_fifo_segment_attach (a); + vec_reset_length (a->new_segment_indices); + if (PREDICT_FALSE (rv)) + { + clib_warning ("[%d] sm_fifo_segment_attach ('%s') failed", + vcm->my_pid, a->segment_name); + return; + } + } + + /* + * Setup session + */ + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] client sid %d", vcm->my_pid, session_index); + + session = pool_elt_at_index (vcm->sessions, session_index); + session->is_cut_thru = is_cut_thru; + session->event_queue = uword_to_pointer (mp->vpp_event_queue_address, + unix_shared_memory_queue_t *); + + rx_fifo = uword_to_pointer (mp->server_rx_fifo, svm_fifo_t *); + rx_fifo->client_session_index = session_index; + tx_fifo = uword_to_pointer (mp->server_tx_fifo, svm_fifo_t *); + tx_fifo->client_session_index = session_index; + + session->server_rx_fifo = rx_fifo; + session->server_tx_fifo = tx_fifo; + session->vpp_session_handle = mp->handle; + session->state = STATE_CONNECT; + + /* Add it to lookup table */ + hash_set (vcm->session_index_by_vpp_handles, mp->handle, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); +} + +static void +vppcom_send_connect_sock (session_t * session, u32 session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_connect_sock_t *cmp; + + /* Assumes caller as acquired the spinlock: vcm->sessions_lockp */ + session->is_server = 0; + cmp = vl_msg_api_alloc (sizeof (*cmp)); + memset (cmp, 0, sizeof (*cmp)); + cmp->_vl_msg_id = ntohs (VL_API_CONNECT_SOCK); + cmp->client_index = vcm->my_client_index; + cmp->context = htonl (0xfeedface); + cmp->app_connect = session_index; + + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] session_index = %d 0x%08x, app_connect = %d 0x%08x", + vcm->my_pid, session_index, session_index, + cmp->app_connect, cmp->app_connect); + + cmp->vrf = session->vrf; + cmp->is_ip4 = session->is_ip4; + clib_memcpy (cmp->ip, session->ip, (session->is_ip4 ? + sizeof (ip4_address_t) : + sizeof (ip6_address_t))); + cmp->port = session->port; + cmp->proto = session->proto; + clib_memcpy (cmp->options, session->options, sizeof (cmp->options)); + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & cmp); +} + +static int +vppcom_send_disconnect (u32 session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_disconnect_session_t *dmp; + session_t *session = 0; + int rv; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + + dmp = vl_msg_api_alloc (sizeof (*dmp)); + memset (dmp, 0, sizeof (*dmp)); + dmp->_vl_msg_id = ntohs (VL_API_DISCONNECT_SESSION); + dmp->client_index = vcm->my_client_index; + dmp->handle = session->vpp_session_handle; + clib_spinlock_unlock (&vcm->sessions_lockp); + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & dmp); + return VPPCOM_OK; +} + +static void +vl_api_bind_sock_reply_t_handler (vl_api_bind_sock_reply_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + int rv; + + if (mp->retval) + clib_warning ("[%d] bind failed: %U", vcm->my_pid, format_api_error, + ntohl (mp->retval)); + + ASSERT (vcm->bind_session_index != ~0); + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (vcm->bind_session_index, &session); + if (rv == VPPCOM_OK) + { + session->vpp_session_handle = mp->handle; + hash_set (vcm->session_index_by_vpp_handles, mp->handle, + vcm->bind_session_index); + session->state = mp->retval ? STATE_FAILED : STATE_LISTEN; + vcm->bind_session_index = ~0; + } + clib_spinlock_unlock (&vcm->sessions_lockp); +} + +static void +vl_api_unbind_sock_reply_t_handler (vl_api_unbind_sock_reply_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + int rv; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (vcm->bind_session_index, &session); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, vcm->bind_session_index); + } + + if (mp->retval) + clib_warning ("[%d] unbind failed: %U", vcm->my_pid, format_api_error, + ntohl (mp->retval)); + + vcm->bind_session_index = ~0; + session->state = STATE_START; + clib_spinlock_unlock (&vcm->sessions_lockp); +} + +u8 * +format_ip4_address (u8 * s, va_list * args) +{ + u8 *a = va_arg (*args, u8 *); + return format (s, "%d.%d.%d.%d", a[0], a[1], a[2], a[3]); +} + +u8 * +format_ip6_address (u8 * s, va_list * args) +{ + ip6_address_t *a = va_arg (*args, ip6_address_t *); + u32 i, i_max_n_zero, max_n_zeros, i_first_zero, n_zeros, last_double_colon; + + i_max_n_zero = ARRAY_LEN (a->as_u16); + max_n_zeros = 0; + i_first_zero = i_max_n_zero; + n_zeros = 0; + for (i = 0; i < ARRAY_LEN (a->as_u16); i++) + { + u32 is_zero = a->as_u16[i] == 0; + if (is_zero && i_first_zero >= ARRAY_LEN (a->as_u16)) + { + i_first_zero = i; + n_zeros = 0; + } + n_zeros += is_zero; + if ((!is_zero && n_zeros > max_n_zeros) + || (i + 1 >= ARRAY_LEN (a->as_u16) && n_zeros > max_n_zeros)) + { + i_max_n_zero = i_first_zero; + max_n_zeros = n_zeros; + i_first_zero = ARRAY_LEN (a->as_u16); + n_zeros = 0; + } + } + + last_double_colon = 0; + for (i = 0; i < ARRAY_LEN (a->as_u16); i++) + { + if (i == i_max_n_zero && max_n_zeros > 1) + { + s = format (s, "::"); + i += max_n_zeros - 1; + last_double_colon = 1; + } + else + { + s = format (s, "%s%x", + (last_double_colon || i == 0) ? "" : ":", + clib_net_to_host_u16 (a->as_u16[i])); + last_double_colon = 0; + } + } + + return s; +} + +/* Format an IP46 address. */ +u8 * +format_ip46_address (u8 * s, va_list * args) +{ + ip46_address_t *ip46 = va_arg (*args, ip46_address_t *); + ip46_type_t type = va_arg (*args, ip46_type_t); + int is_ip4 = 1; + + switch (type) + { + case IP46_TYPE_ANY: + is_ip4 = ip46_address_is_ip4 (ip46); + break; + case IP46_TYPE_IP4: + is_ip4 = 1; + break; + case IP46_TYPE_IP6: + is_ip4 = 0; + break; + } + + return is_ip4 ? + format (s, "%U", format_ip4_address, &ip46->ip4) : + format (s, "%U", format_ip6_address, &ip46->ip6); +} + +static void +vl_api_accept_session_t_handler (vl_api_accept_session_t * mp) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_accept_session_reply_t *rmp; + svm_fifo_t *rx_fifo, *tx_fifo; + session_t *session; + u32 session_index; + int rv = 0; + + if (!clib_fifo_free_elts (vcm->client_session_index_fifo)) + { + clib_warning ("[%d] client session queue is full!", vcm->my_pid); + rv = VNET_API_ERROR_QUEUE_FULL; + goto send_reply; + } + + if (VPPCOM_DEBUG > 1) + { + u8 *ip_str = format (0, "%U", format_ip46_address, &mp->ip, mp->is_ip4); + clib_warning ("[%d] accepted session from: %s:%d", vcm->my_pid, ip_str, + clib_net_to_host_u16 (mp->port)); + vec_free (ip_str); + } + + clib_spinlock_lock (&vcm->sessions_lockp); + /* Allocate local session and set it up */ + pool_get (vcm->sessions, session); + memset (session, 0, sizeof (*session)); + session_index = session - vcm->sessions; + + rx_fifo = uword_to_pointer (mp->server_rx_fifo, svm_fifo_t *); + rx_fifo->client_session_index = session_index; + tx_fifo = uword_to_pointer (mp->server_tx_fifo, svm_fifo_t *); + tx_fifo->client_session_index = session_index; + + session->server_rx_fifo = rx_fifo; + session->server_tx_fifo = tx_fifo; + session->event_queue = uword_to_pointer (mp->vpp_event_queue_address, + unix_shared_memory_queue_t *); + session->state = STATE_ACCEPT; + session->is_cut_thru = 0; + session->port = ntohs (mp->port); + session->is_ip4 = mp->is_ip4; + clib_memcpy (session->ip, mp->ip, (mp->is_ip4 ? + sizeof (ip4_address_t) : + sizeof (ip6_address_t))); + clib_spinlock_unlock (&vcm->sessions_lockp); + + /* Add it to lookup table */ + hash_set (vcm->session_index_by_vpp_handles, mp->handle, session_index); + + clib_fifo_add1 (vcm->client_session_index_fifo, session_index); + + /* + * Send accept reply to vpp + */ +send_reply: + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + rmp->_vl_msg_id = ntohs (VL_API_ACCEPT_SESSION_REPLY); + rmp->retval = htonl (rv); + rmp->handle = mp->handle; + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & rmp); +} + +/* + * Acting as server for redirected connect requests + */ +static void +vl_api_connect_sock_t_handler (vl_api_connect_sock_t * mp) +{ + static svm_fifo_segment_create_args_t _a; + svm_fifo_segment_create_args_t *a = &_a; + vppcom_main_t *vcm = &vppcom_main; + u32 session_index; + svm_fifo_segment_private_t *seg; + unix_shared_memory_queue_t *client_q; + vl_api_connect_sock_reply_t *rmp; + session_t *session = 0; + int rv = 0; + svm_fifo_t *rx_fifo; + svm_fifo_t *tx_fifo; + unix_shared_memory_queue_t *event_q = 0; + + if (!clib_fifo_free_elts (vcm->client_session_index_fifo)) + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] client session queue is full!", vcm->my_pid); + rv = VNET_API_ERROR_QUEUE_FULL; + goto send_reply; + } + + /* Create the segment */ + memset (a, 0, sizeof (*a)); + a->segment_name = (char *) format ((u8 *) a->segment_name, "%d:segment%d%c", + vcm->my_pid, vcm->unique_segment_index++, + 0); + a->segment_size = vcm->cfg.segment_size; + a->preallocated_fifo_pairs = vcm->cfg.preallocated_fifo_pairs; + a->rx_fifo_size = vcm->cfg.rx_fifo_size; + a->tx_fifo_size = vcm->cfg.tx_fifo_size; + + rv = svm_fifo_segment_create (a); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] svm_fifo_segment_create ('%s') failed", + vcm->my_pid, a->segment_name); + vec_reset_length (a->new_segment_indices); + rv = VNET_API_ERROR_URI_FIFO_CREATE_FAILED; + goto send_reply; + } + + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] created segment '%s'", vcm->my_pid, a->segment_name); + + clib_spinlock_lock (&vcm->sessions_lockp); + pool_get (vcm->sessions, session); + memset (session, 0, sizeof (*session)); + session_index = session - vcm->sessions; + + session->sm_seg_index = a->new_segment_indices[0]; + vec_reset_length (a->new_segment_indices); + + seg = svm_fifo_get_segment (session->sm_seg_index); + rx_fifo = session->server_rx_fifo = + svm_fifo_segment_alloc_fifo (seg, vcm->cfg.rx_fifo_size, + FIFO_SEGMENT_RX_FREELIST); + if (PREDICT_FALSE (!session->server_rx_fifo)) + { + svm_fifo_segment_delete (seg); + clib_warning ("[%d] rx fifo alloc failed, size %ld (0x%lx)", + vcm->my_pid, vcm->cfg.rx_fifo_size, + vcm->cfg.rx_fifo_size); + rv = VNET_API_ERROR_URI_FIFO_CREATE_FAILED; + clib_spinlock_unlock (&vcm->sessions_lockp); + goto send_reply; + } + + tx_fifo = session->server_tx_fifo = + svm_fifo_segment_alloc_fifo (seg, vcm->cfg.tx_fifo_size, + FIFO_SEGMENT_TX_FREELIST); + if (PREDICT_FALSE (!session->server_tx_fifo)) + { + svm_fifo_segment_delete (seg); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] tx fifo alloc failed, size %ld (0x%lx)", + vcm->my_pid, vcm->cfg.tx_fifo_size, + vcm->cfg.tx_fifo_size); + rv = VNET_API_ERROR_URI_FIFO_CREATE_FAILED; + clib_spinlock_unlock (&vcm->sessions_lockp); + goto send_reply; + } + + session->server_rx_fifo->master_session_index = session_index; + session->server_tx_fifo->master_session_index = session_index; + session->client_queue_address = mp->client_queue_address; + session->is_cut_thru = 1; + session->is_server = 1; + session->is_ip4 = mp->is_ip4; + session->port = mp->port; + { + void *oldheap; + ssvm_shared_header_t *sh = seg->ssvm.sh; + + ssvm_lock_non_recursive (sh, 1); + oldheap = ssvm_push_heap (sh); + event_q = session->event_queue = + unix_shared_memory_queue_init (vcm->cfg.event_queue_size, + sizeof (session_fifo_event_t), + vcm->my_pid, 0 /* signal not sent */ ); + ssvm_pop_heap (oldheap); + ssvm_unlock_non_recursive (sh); + } + clib_memcpy (session->ip, mp->ip, (mp->is_ip4 ? + sizeof (ip4_address_t) : + sizeof (ip6_address_t))); + session->state = STATE_ACCEPT; + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] Connected cut-thru to client: sid %d", + vcm->my_pid, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + clib_fifo_add1 (vcm->client_session_index_fifo, session_index); + +send_reply: + rmp = vl_msg_api_alloc (sizeof (*rmp)); + memset (rmp, 0, sizeof (*rmp)); + + rmp->_vl_msg_id = ntohs (VL_API_CONNECT_SOCK_REPLY); + rmp->context = mp->context; + rmp->app_connect = htonl (mp->app_connect); + rmp->retval = htonl (rv); + rmp->segment_name_length = vec_len (a->segment_name); + clib_memcpy (rmp->segment_name, a->segment_name, vec_len (a->segment_name)); + vec_reset_length (a->segment_name); + + if (event_q) + { + rmp->vpp_event_queue_address = pointer_to_uword (event_q); + rmp->server_rx_fifo = pointer_to_uword (rx_fifo); + rmp->server_tx_fifo = pointer_to_uword (tx_fifo); + } + client_q = + uword_to_pointer (mp->client_queue_address, unix_shared_memory_queue_t *); + + ASSERT (client_q); + vl_msg_api_send_shmem (client_q, (u8 *) & rmp); +} + +static void +vppcom_send_bind_sock (session_t * session) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_bind_sock_t *bmp; + + /* Assumes caller has acquired spinlock: vcm->sessions_lockp */ + session->is_server = 1; + bmp = vl_msg_api_alloc (sizeof (*bmp)); + memset (bmp, 0, sizeof (*bmp)); + + bmp->_vl_msg_id = ntohs (VL_API_BIND_SOCK); + bmp->client_index = vcm->my_client_index; + bmp->context = htonl (0xfeedface); + bmp->vrf = session->vrf; + bmp->is_ip4 = session->is_ip4; + clib_memcpy (bmp->ip, session->ip, (session->is_ip4 ? + sizeof (ip4_address_t) : + sizeof (ip6_address_t))); + bmp->port = session->port; + bmp->proto = session->proto; + clib_memcpy (bmp->options, session->options, sizeof (bmp->options)); + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & bmp); +} + +static void +vppcom_send_unbind_sock (u32 session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + vl_api_unbind_sock_t *ump; + session_t *session = 0; + int rv; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return; + } + + ump = vl_msg_api_alloc (sizeof (*ump)); + memset (ump, 0, sizeof (*ump)); + + ump->_vl_msg_id = ntohs (VL_API_UNBIND_SOCK); + ump->client_index = vcm->my_client_index; + ump->handle = session->vpp_session_handle; + clib_spinlock_unlock (&vcm->sessions_lockp); + vl_msg_api_send_shmem (vcm->vl_input_queue, (u8 *) & ump); +} + +static int +vppcom_session_unbind_cut_thru (session_t * session) +{ + svm_fifo_segment_main_t *sm = &svm_fifo_segment_main; + svm_fifo_segment_private_t *seg; + int rv = VPPCOM_OK; + + seg = vec_elt_at_index (sm->segments, session->sm_seg_index); + svm_fifo_segment_free_fifo (seg, session->server_rx_fifo, + FIFO_SEGMENT_RX_FREELIST); + svm_fifo_segment_free_fifo (seg, session->server_tx_fifo, + FIFO_SEGMENT_TX_FREELIST); + svm_fifo_segment_delete (seg); + + return rv; +} + +static int +vppcom_session_unbind (u32 session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + int rv; + + clib_spinlock_lock (&vcm->sessions_lockp); + if (PREDICT_FALSE (pool_is_free_index (vcm->sessions, session_index))) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return VPPCOM_EBADFD; + } + clib_spinlock_unlock (&vcm->sessions_lockp); + + vcm->bind_session_index = session_index; + vppcom_send_unbind_sock (session_index); + rv = vppcom_wait_for_session_state_change (session_index, STATE_START, + vcm->cfg.session_timeout); + if (PREDICT_FALSE (rv)) + { + vcm->bind_session_index = ~0; + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] server unbind timed out, rv = %s (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + return rv; + } + return VPPCOM_OK; +} + +static int +vppcom_session_disconnect (u32 session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + int rv; + + rv = vppcom_send_disconnect (session_index); + if (PREDICT_FALSE (rv)) + return rv; + + rv = vppcom_wait_for_session_state_change (session_index, STATE_DISCONNECT, + vcm->cfg.session_timeout); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] client disconnect timed out, rv = %s (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + return rv; + } + + clib_spinlock_lock (&vcm->sessions_lockp); + pool_put_index (vcm->sessions, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + return VPPCOM_OK; +} + +#define foreach_sock_msg \ +_(SESSION_ENABLE_DISABLE_REPLY, session_enable_disable_reply) \ +_(BIND_SOCK_REPLY, bind_sock_reply) \ +_(UNBIND_SOCK_REPLY, unbind_sock_reply) \ +_(ACCEPT_SESSION, accept_session) \ +_(CONNECT_SOCK, connect_sock) \ +_(CONNECT_SOCK_REPLY, connect_sock_reply) \ +_(DISCONNECT_SESSION, disconnect_session) \ +_(DISCONNECT_SESSION_REPLY, disconnect_session_reply) \ +_(RESET_SESSION, reset_session) \ +_(APPLICATION_ATTACH_REPLY, application_attach_reply) \ +_(APPLICATION_DETACH_REPLY, application_detach_reply) \ +_(MAP_ANOTHER_SEGMENT, map_another_segment) + +static void +vppcom_api_hookup (void) +{ +#define _(N,n) \ + vl_msg_api_set_handlers(VL_API_##N, #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 1); + foreach_sock_msg; +#undef _ +} + +static void +vppcom_cfg_init (vppcom_cfg_t * vcl_cfg) +{ + ASSERT (vcl_cfg); + + vcl_cfg->heapsize = (256ULL << 20); + vcl_cfg->segment_baseva = 0x200000000ULL; + vcl_cfg->segment_size = (256 << 20); + vcl_cfg->add_segment_size = (128 << 20); + vcl_cfg->preallocated_fifo_pairs = 8; + vcl_cfg->rx_fifo_size = (1 << 20); + vcl_cfg->tx_fifo_size = (1 << 20); + vcl_cfg->event_queue_size = 2048; + vcl_cfg->listen_queue_size = CLIB_CACHE_LINE_BYTES / sizeof (u32); + vcl_cfg->app_timeout = 10 * 60.0; + vcl_cfg->session_timeout = 10 * 60.0; + vcl_cfg->accept_timeout = 60.0; +} + +static void +vppcom_cfg_heapsize (char *conf_fname) +{ + vppcom_main_t *vcm = &vppcom_main; + vppcom_cfg_t *vcl_cfg = &vcm->cfg; + FILE *fp; + char inbuf[4096]; + int argc = 1; + char **argv = NULL; + char *arg = NULL; + char *p; + int i; + u8 *sizep; + u32 size; + + fp = fopen (conf_fname, "r"); + if (fp == NULL) + { + if (VPPCOM_DEBUG > 0) + fprintf (stderr, "open configuration file '%s' failed\n", conf_fname); + goto defaulted; + } + argv = calloc (1, sizeof (char *)); + if (argv == NULL) + goto defaulted; + + while (1) + { + if (fgets (inbuf, 4096, fp) == 0) + break; + p = strtok (inbuf, " \t\n"); + while (p != NULL) + { + if (*p == '#') + break; + argc++; + char **tmp = realloc (argv, argc * sizeof (char *)); + if (tmp == NULL) + { + fclose (fp); + goto defaulted; + } + argv = tmp; + arg = strndup (p, 1024); + if (arg == NULL) + { + fclose (fp); + goto defaulted; + } + argv[argc - 1] = arg; + p = strtok (NULL, " \t\n"); + } + } + + fclose (fp); + + char **tmp = realloc (argv, (argc + 1) * sizeof (char *)); + if (tmp == NULL) + goto defaulted; + argv = tmp; + argv[argc] = NULL; + + /* + * Look for and parse the "heapsize" config parameter. + * Manual since none of the clib infra has been bootstrapped yet. + * + * Format: heapsize [mM][gG] + */ + + for (i = 1; i < (argc - 1); i++) + { + if (!strncmp (argv[i], "heapsize", 8)) + { + sizep = (u8 *) argv[i + 1]; + size = 0; + while (*sizep >= '0' && *sizep <= '9') + { + size *= 10; + size += *sizep++ - '0'; + } + if (size == 0) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] parse error '%s %s', " + "using default heapsize %lld (0x%llx)", + vcm->my_pid, argv[i], argv[i + 1], + vcl_cfg->heapsize, vcl_cfg->heapsize); + goto defaulted; + } + + if (*sizep == 'g' || *sizep == 'G') + vcl_cfg->heapsize = size << 30; + else if (*sizep == 'm' || *sizep == 'M') + vcl_cfg->heapsize = size << 20; + else + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] parse error '%s %s', " + "using default heapsize %lld (0x%llx)", + vcm->my_pid, argv[i], argv[i + 1], + vcl_cfg->heapsize, vcl_cfg->heapsize); + goto defaulted; + } + } + } + +defaulted: + if (!clib_mem_init (0, vcl_cfg->heapsize)) + clib_warning ("[%d] vppcom heap allocation failure!", vcm->my_pid); + else if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] allocated vppcom heapsize %lld (0x%llx)", + vcm->my_pid, vcl_cfg->heapsize, vcl_cfg->heapsize); +} + +static void +vppcom_cfg_read (char *conf_fname) +{ + vppcom_main_t *vcm = &vppcom_main; + vppcom_cfg_t *vcl_cfg = &vcm->cfg; + int fd; + unformat_input_t _input, *input = &_input; + unformat_input_t _line_input, *line_input = &_line_input; + u8 vc_cfg_input = 0; + u8 *chroot_path; + struct stat s; + u32 uid, gid; + + fd = open (conf_fname, O_RDONLY); + if (fd < 0) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] open configuration file '%s' failed!", + vcm->my_pid, conf_fname); + goto file_done; + } + + if (fstat (fd, &s) < 0) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] failed to stat `%s'", vcm->my_pid, conf_fname); + goto file_done; + } + + if (!(S_ISREG (s.st_mode) || S_ISLNK (s.st_mode))) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] not a regular file `%s'", vcm->my_pid, + conf_fname); + goto file_done; + } + + unformat_init_unix_file (input, fd); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + unformat_user (input, unformat_line_input, line_input); + unformat_skip_white_space (line_input); + + if (unformat (line_input, "vppcom {")) + { + vc_cfg_input = 1; + continue; + } + + if (vc_cfg_input) + { + if (unformat (line_input, "heapsize %s", &chroot_path)) + { + vec_terminate_c_string (chroot_path); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured heapsize %s, " + "actual heapsize %lld (0x%llx)", + vcm->my_pid, chroot_path, vcl_cfg->heapsize, + vcl_cfg->heapsize); + vec_free (chroot_path); + } + else if (unformat (line_input, "api-prefix %s", &chroot_path)) + { + vec_terminate_c_string (chroot_path); + vl_set_memory_root_path ((char *) chroot_path); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured api-prefix %s", + vcm->my_pid, chroot_path); + chroot_path = 0; /* Don't vec_free() it! */ + } + else if (unformat (line_input, "uid %d", &uid)) + { + vl_set_memory_uid (uid); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured uid %d", vcm->my_pid, uid); + } + else if (unformat (line_input, "gid %d", &gid)) + { + vl_set_memory_gid (gid); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured gid %d", vcm->my_pid, gid); + } + else if (unformat (line_input, "segment-baseva 0x%llx", + &vcl_cfg->segment_baseva)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured segment_baseva 0x%llx", + vcm->my_pid, vcl_cfg->segment_baseva); + } + else if (unformat (line_input, "segment-size 0x%lx", + &vcl_cfg->segment_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured segment_size 0x%lx (%ld)", + vcm->my_pid, vcl_cfg->segment_size, + vcl_cfg->segment_size); + } + else if (unformat (line_input, "segment-size %ld", + &vcl_cfg->segment_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured segment_size %ld (0x%lx)", + vcm->my_pid, vcl_cfg->segment_size, + vcl_cfg->segment_size); + } + else if (unformat (line_input, "add-segment-size 0x%lx", + &vcl_cfg->add_segment_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning + ("[%d] configured add_segment_size 0x%lx (%ld)", + vcm->my_pid, vcl_cfg->add_segment_size, + vcl_cfg->add_segment_size); + } + else if (unformat (line_input, "add-segment-size %ld", + &vcl_cfg->add_segment_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning + ("[%d] configured add_segment_size %ld (0x%lx)", + vcm->my_pid, vcl_cfg->add_segment_size, + vcl_cfg->add_segment_size); + } + else if (unformat (line_input, "preallocated-fifo-pairs %d", + &vcl_cfg->preallocated_fifo_pairs)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured preallocated_fifo_pairs " + "%d (0x%x)", vcm->my_pid, + vcl_cfg->preallocated_fifo_pairs, + vcl_cfg->preallocated_fifo_pairs); + } + else if (unformat (line_input, "rx-fifo-size 0x%lx", + &vcl_cfg->rx_fifo_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured rx_fifo_size 0x%lx (%ld)", + vcm->my_pid, vcl_cfg->rx_fifo_size, + vcl_cfg->rx_fifo_size); + } + else if (unformat (line_input, "rx-fifo-size %ld", + &vcl_cfg->rx_fifo_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured rx_fifo_size %ld (0x%lx)", + vcm->my_pid, vcl_cfg->rx_fifo_size, + vcl_cfg->rx_fifo_size); + } + else if (unformat (line_input, "tx-fifo-size 0x%lx", + &vcl_cfg->tx_fifo_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured tx_fifo_size 0x%lx (%ld)", + vcm->my_pid, vcl_cfg->tx_fifo_size, + vcl_cfg->tx_fifo_size); + } + else if (unformat (line_input, "tx-fifo-size %ld", + &vcl_cfg->tx_fifo_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured tx_fifo_size %ld (0x%lx)", + vcm->my_pid, vcl_cfg->tx_fifo_size, + vcl_cfg->tx_fifo_size); + } + else if (unformat (line_input, "event-queue-size 0x%lx", + &vcl_cfg->event_queue_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured event_queue_size 0x%lx (%ld)", + vcm->my_pid, vcl_cfg->event_queue_size, + vcl_cfg->event_queue_size); + } + else if (unformat (line_input, "event-queue-size %ld", + &vcl_cfg->event_queue_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured event_queue_size %ld (0x%lx)", + vcm->my_pid, vcl_cfg->event_queue_size, + vcl_cfg->event_queue_size); + } + else if (unformat (line_input, "listen-queue-size 0x%lx", + &vcl_cfg->listen_queue_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured listen_queue_size 0x%lx (%ld)", + vcm->my_pid, vcl_cfg->listen_queue_size, + vcl_cfg->listen_queue_size); + } + else if (unformat (line_input, "listen-queue-size %ld", + &vcl_cfg->listen_queue_size)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured listen_queue_size %ld (0x%lx)", + vcm->my_pid, vcl_cfg->listen_queue_size, + vcl_cfg->listen_queue_size); + } + else if (unformat (line_input, "app-timeout %f", + &vcl_cfg->app_timeout)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured app_timeout %f", + vcm->my_pid, vcl_cfg->app_timeout); + } + else if (unformat (line_input, "session-timeout %f", + &vcl_cfg->session_timeout)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured session_timeout %f", + vcm->my_pid, vcl_cfg->session_timeout); + } + else if (unformat (line_input, "accept-timeout %f", + &vcl_cfg->accept_timeout)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] configured accept_timeout %f", + vcm->my_pid, vcl_cfg->accept_timeout); + } + else if (unformat (line_input, "}")) + { + vc_cfg_input = 0; + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] completed parsing vppcom config!", + vcm->my_pid); + goto input_done; + } + else + { + if (line_input->buffer[line_input->index] != '#') + { + clib_warning ("[%d] Unknown vppcom config option: '%s'", + vcm->my_pid, (char *) + &line_input->buffer[line_input->index]); + } + } + } + } + +input_done: + unformat_free (input); + +file_done: + if (fd > 0) + close (fd); +} + +/* + * VPPCOM Public API functions + */ +int +vppcom_app_create (char *app_name) +{ + vppcom_main_t *vcm = &vppcom_main; + vppcom_cfg_t *vcl_cfg = &vcm->cfg; + u8 *heap; + mheap_t *h; + int rv; + + if (!vcm->init) + { + char *conf_fname; + + vcm->init = 1; + vcm->my_pid = getpid (); + clib_fifo_validate (vcm->client_session_index_fifo, + vcm->cfg.listen_queue_size); + vppcom_cfg_init (vcl_cfg); + conf_fname = getenv (VPPCOM_CONF_ENV); + if (!conf_fname) + { + conf_fname = VPPCOM_CONF_DEFAULT; + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] getenv '%s' failed!", vcm->my_pid, + VPPCOM_CONF_ENV); + } + vppcom_cfg_heapsize (conf_fname); + vppcom_cfg_read (conf_fname); + vcm->bind_session_index = ~0; + vcm->main_cpu = os_get_thread_index (); + heap = clib_mem_get_per_cpu_heap (); + h = mheap_header (heap); + + /* make the main heap thread-safe */ + h->flags |= MHEAP_FLAG_THREAD_SAFE; + + vcm->session_index_by_vpp_handles = hash_create (0, sizeof (uword)); + + clib_time_init (&vcm->clib_time); + vppcom_init_error_string_table (); + svm_fifo_segment_init (vcl_cfg->segment_baseva, + 20 /* timeout in secs */ ); + clib_spinlock_init (&vcm->sessions_lockp); + vppcom_api_hookup (); + } + + if (vcm->my_client_index == ~0) + { + vcm->app_state = STATE_APP_START; + rv = vppcom_connect_to_vpp (app_name); + if (rv) + { + clib_warning ("[%s] couldn't connect to VPP.", vcm->my_pid); + return rv; + } + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sending session enable", vcm->my_pid); + + rv = vppcom_app_session_enable (); + if (rv) + { + clib_warning ("[%d] vppcom_app_session_enable() failed!", + vcm->my_pid); + return rv; + } + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sending app attach", vcm->my_pid); + + rv = vppcom_app_attach (); + if (rv) + { + clib_warning ("[%d] vppcom_app_attach() failed!", vcm->my_pid); + return rv; + } + } + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] app_name '%s', my_client_index %d (0x%x)", + vcm->my_pid, app_name, vcm->my_client_index, + vcm->my_client_index); + + return VPPCOM_OK; +} + +void +vppcom_app_destroy (void) +{ + vppcom_main_t *vcm = &vppcom_main; + int rv; + + if (vcm->my_client_index == ~0) + return; + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] detaching from VPP, my_client_index %d (0x%x)", + vcm->my_pid, vcm->my_client_index, vcm->my_client_index); + + vppcom_app_detach (); + rv = vppcom_wait_for_app_state_change (STATE_APP_ENABLED); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] application detach timed out, rv = %s (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + } + vl_client_disconnect_from_vlib (); + vcm->my_client_index = ~0; + vcm->app_state = STATE_APP_START; +} + +int +vppcom_session_create (u32 vrf, u8 proto, u8 is_nonblocking) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session; + u32 session_index; + + clib_spinlock_lock (&vcm->sessions_lockp); + pool_get (vcm->sessions, session); + session_index = session - vcm->sessions; + + session->vrf = vrf; + session->proto = proto; + session->state = STATE_START; + session->is_nonblocking = is_nonblocking; + clib_spinlock_unlock (&vcm->sessions_lockp); + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sid %d", vcm->my_pid, session_index); + + return (int) session_index; +} + +int +vppcom_session_close (uint32_t session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + int rv; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + clib_spinlock_unlock (&vcm->sessions_lockp); + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sid %d", vcm->my_pid, session_index); + + if (session->is_cut_thru) + { + if (session->is_server) + rv = vppcom_session_unbind_cut_thru (session); + } + else + { + rv = (session->is_server) ? + vppcom_session_unbind (session_index) : + vppcom_session_disconnect (session_index); + } + + clib_spinlock_lock (&vcm->sessions_lockp); + pool_put_index (vcm->sessions, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + return rv; +} + +int +vppcom_session_bind (uint32_t session_index, vppcom_endpt_t * ep) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + int rv; + + if (!ep || !ep->ip) + return VPPCOM_EINVAL; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sid %d", vcm->my_pid, session_index); + + session->vrf = ep->vrf; + session->is_ip4 = ep->is_ip4; + memset (session->ip, 0, sizeof (*session->ip)); + clib_memcpy (session->ip, ep->ip, (ep->is_ip4 ? + sizeof (ip4_address_t) : + sizeof (ip6_address_t))); + session->port = ep->port; + + clib_spinlock_unlock (&vcm->sessions_lockp); + return VPPCOM_OK; +} + +int +vppcom_session_listen (uint32_t session_index, uint32_t q_len) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + int rv; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sid %d", vcm->my_pid, session_index); + + ASSERT (vcm->bind_session_index == ~0); + vcm->bind_session_index = session_index; + vppcom_send_bind_sock (session); + clib_spinlock_unlock (&vcm->sessions_lockp); + rv = vppcom_wait_for_session_state_change (session_index, STATE_LISTEN, + vcm->cfg.session_timeout); + if (PREDICT_FALSE (rv)) + { + vcm->bind_session_index = ~0; + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] server listen timed out, rv = %d (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + return rv; + } + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + session->is_listen = 1; + clib_spinlock_unlock (&vcm->sessions_lockp); + clib_fifo_validate (vcm->client_session_index_fifo, q_len); + + return VPPCOM_OK; +} + +int +vppcom_session_accept (uint32_t listen_session_index, vppcom_endpt_t * ep, + double wait_for_time) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + u32 client_session_index; + int rv; + f64 wait_for; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (listen_session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, listen_session_index); + return rv; + } + + if (session->state != STATE_LISTEN) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] session not in listen state, state = %s", + vcm->my_pid, vppcom_session_state_str (session->state)); + return VPPCOM_EBADFD; + } + wait_for = session->is_nonblocking ? 0 : + (wait_for_time < 0) ? vcm->cfg.accept_timeout : wait_for_time; + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] sid %d, state %s (%d)", vcm->my_pid, + listen_session_index, + vppcom_session_state_str (session->state), session->state); + clib_spinlock_unlock (&vcm->sessions_lockp); + + while (1) + { + rv = vppcom_wait_for_client_session_index (wait_for); + if (rv) + { + if ((VPPCOM_DEBUG > 0)) + clib_warning ("[%d] sid %d, accept timed out, rv = %s (%d)", + vcm->my_pid, listen_session_index, + vppcom_retval_str (rv), rv); + if ((wait_for == 0) || (wait_for_time > 0)) + return rv; + } + else + break; + } + + clib_fifo_sub1 (vcm->client_session_index_fifo, client_session_index); + + session = 0; + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (client_session_index, &session); + ASSERT (rv == VPPCOM_OK); + ASSERT (session->is_server); + + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] Got a request: client sid %d", vcm->my_pid, + client_session_index); + + ep->vrf = session->vrf; + ep->is_cut_thru = session->is_cut_thru; + ep->is_ip4 = session->is_ip4; + ep->port = session->port; + memset (ep->ip, 0, sizeof (ip6_address_t)); + clib_memcpy (ep->ip, session->ip, (session->is_ip4 ? + sizeof (ip4_address_t) : + sizeof (ip6_address_t))); + session->state = STATE_LISTEN; + clib_spinlock_unlock (&vcm->sessions_lockp); + return (int) client_session_index; +} + +int +vppcom_session_connect (uint32_t session_index, vppcom_endpt_t * server_ep) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + int rv; + ip46_address_t *ip46; + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + + if (session->state == STATE_CONNECT) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] session, sid (%d) already connected!", + vcm->my_pid, session_index); + return VPPCOM_OK; + } + + session->vrf = server_ep->vrf; + session->is_ip4 = server_ep->is_ip4; + ip46 = (ip46_address_t *) session->ip; + *ip46 = to_ip46 (!server_ep->is_ip4, server_ep->ip); + session->port = server_ep->port; + + if (VPPCOM_DEBUG > 0) + { + u8 *ip_str = format (0, "%U", format_ip46_address, + &session->ip, session->is_ip4); + clib_warning ("[%d] connect sid %d to %s server port %d", + vcm->my_pid, session_index, ip_str, + clib_net_to_host_u16 (session->port)); + vec_free (ip_str); + } + + vppcom_send_connect_sock (session, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + rv = vppcom_wait_for_session_state_change (session_index, STATE_CONNECT, + vcm->cfg.session_timeout); + if (PREDICT_FALSE (rv)) + { + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] connect timed out, rv = %s (%d)", + vcm->my_pid, vppcom_retval_str (rv), rv); + return rv; + } + return VPPCOM_OK; +} + +int +vppcom_session_read (uint32_t session_index, void *buf, int n) +{ + session_fifo_event_t _e, *e = &_e; + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + svm_fifo_t *rx_fifo; + int n_read = 0; + int rv; + char *fifo_str; + + ASSERT (buf); + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + + if (session->is_cut_thru) + { + rx_fifo = session->is_server ? session->server_rx_fifo : + session->server_tx_fifo; + fifo_str = session->is_server ? "server_rx_fifo" : "server_tx_fifo"; + clib_spinlock_unlock (&vcm->sessions_lockp); + + n_read = svm_fifo_dequeue_nowait (rx_fifo, n, buf); + + if (n_read <= 0) + return VPPCOM_EAGAIN; + + } + else + { + rv = unix_shared_memory_queue_sub (session->event_queue, (u8 *) e, + 1 /* nowait */ ); + clib_spinlock_unlock (&vcm->sessions_lockp); + if (rv < 0) + return VPPCOM_EAGAIN; + + switch (e->event_type) + { + case FIFO_EVENT_APP_RX: + rx_fifo = e->fifo; + fifo_str = "app_rx_fifo"; + n_read = svm_fifo_dequeue_nowait (rx_fifo, n, buf); + break; + + case FIFO_EVENT_DISCONNECT: + return VPPCOM_ECONNRESET; + + default: + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] unknown event type %d", vcm->my_pid, + e->event_type); + return VPPCOM_EAGAIN; + } + } + + if (VPPCOM_DEBUG > 2) + clib_warning ("[%d] sid %d, read %d bytes from %s (%p)", vcm->my_pid, + session_index, n_read, fifo_str, rx_fifo); + return n_read; +} + +static inline int +vppcom_session_read_ready (session_t * session, u32 session_index) +{ + session_fifo_event_t _e, *e = &_e; + vppcom_main_t *vcm = &vppcom_main; + svm_fifo_t *rx_fifo; + int rv; + int ready = 0; + + /* Assumes caller has acquired spinlock: vcm->sessions_lockp */ + if (session->is_cut_thru) + { + rx_fifo = session->is_server ? session->server_rx_fifo : + session->server_tx_fifo; + + ready = svm_fifo_max_dequeue (rx_fifo); + } + else if (session->is_listen) + ready = clib_fifo_elts (vcm->client_session_index_fifo); + else + { + rv = unix_shared_memory_queue_sub (vcm->app_event_queue, (u8 *) e, + 1 /* nowait */ ); + if (rv >= 0) + { + switch (e->event_type) + { + case FIFO_EVENT_APP_RX: + rx_fifo = e->fifo; + ready = svm_fifo_max_dequeue (rx_fifo); + break; + + case FIFO_EVENT_DISCONNECT: + return VPPCOM_ECONNRESET; + + default: + clib_warning ("[%d] unknown event type %d", vcm->my_pid, + e->event_type); + } + } + } + + if (VPPCOM_DEBUG > 2) + clib_warning ("[%d] sid %d, peek %s (%p), ready = %d", vcm->my_pid, + session_index, + session->is_server ? "server_rx_fifo" : "server_tx_fifo", + rx_fifo, ready); + + return ready; +} + +int +vppcom_session_write (uint32_t session_index, void *buf, int n) +{ + vppcom_main_t *vcm = &vppcom_main; + session_t *session = 0; + svm_fifo_t *tx_fifo; + unix_shared_memory_queue_t *q; + session_fifo_event_t evt; + int rv; + char *fifo_str; + u8 is_nonblocking; + + ASSERT (buf); + + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rv)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rv; + } + + tx_fifo = ((!session->is_cut_thru || session->is_server) ? + session->server_tx_fifo : session->server_rx_fifo); + fifo_str = ((!session->is_cut_thru || session->is_server) ? + "server_tx_fifo" : "server_rx_fifo"); + is_nonblocking = session->is_nonblocking; + clib_spinlock_unlock (&vcm->sessions_lockp); + + do + { + rv = svm_fifo_enqueue_nowait (tx_fifo, n, buf); + } + while (!is_nonblocking && (rv <= 0)); + + /* If event wasn't set, add one */ + if ((rv > 0) && svm_fifo_set_event (tx_fifo)) + { + int rval; + + /* Fabricate TX event, send to vpp */ + evt.fifo = tx_fifo; + evt.event_type = FIFO_EVENT_APP_TX; + evt.event_id = vcm->tx_event_id++; + + clib_spinlock_lock (&vcm->sessions_lockp); + rval = vppcom_session_at_index (session_index, &session); + if (PREDICT_FALSE (rval)) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] invalid session, sid (%d) has been closed!", + vcm->my_pid, session_index); + return rval; + } + q = session->event_queue; + clib_spinlock_unlock (&vcm->sessions_lockp); + ASSERT (q); + unix_shared_memory_queue_add (q, (u8 *) & evt, + 0 /* do wait for mutex */ ); + } + + if (VPPCOM_DEBUG > 2) + clib_warning ("[%d] sid %d, wrote %d bytes to %s (%p)", vcm->my_pid, + session_index, rv, fifo_str, tx_fifo); + + return rv; +} + +static inline int +vppcom_session_write_ready (session_t * session, u32 session_index) +{ + vppcom_main_t *vcm = &vppcom_main; + svm_fifo_t *tx_fifo; + int rv; + + /* Assumes caller has acquired spinlock: vcm->sessions_lockp */ + tx_fifo = ((!session->is_cut_thru || session->is_server) ? + session->server_tx_fifo : session->server_rx_fifo); + + rv = svm_fifo_max_enqueue (tx_fifo); + + if (VPPCOM_DEBUG > 2) + clib_warning ("[%d] sid %d, peek %s (%p), ready = %d", vcm->my_pid, + session_index, + session->is_server ? "server_tx_fifo" : "server_rx_fifo", + tx_fifo, rv); + return rv; +} + +int +vppcom_select (unsigned long n_bits, unsigned long *read_map, + unsigned long *write_map, unsigned long *except_map, + double time_to_wait) +{ + vppcom_main_t *vcm = &vppcom_main; + u32 session_index; + session_t *session = 0; + int rv, bits_set = 0; + f64 timeout = clib_time_now (&vcm->clib_time) + time_to_wait; + u32 minbits = clib_max (n_bits, BITS (uword)); + + ASSERT (sizeof (clib_bitmap_t) == sizeof (long int)); + + if (read_map) + { + clib_bitmap_validate (vcm->rd_bitmap, minbits); + clib_memcpy (vcm->rd_bitmap, read_map, vec_len (vcm->rd_bitmap)); + memset (read_map, 0, vec_len (vcm->rd_bitmap)); + } + if (write_map) + { + clib_bitmap_validate (vcm->wr_bitmap, minbits); + clib_memcpy (vcm->wr_bitmap, write_map, vec_len (vcm->wr_bitmap)); + memset (write_map, 0, vec_len (vcm->wr_bitmap)); + } + if (except_map) + { + clib_bitmap_validate (vcm->ex_bitmap, minbits); + clib_memcpy (vcm->ex_bitmap, except_map, vec_len (vcm->ex_bitmap)); + memset (except_map, 0, vec_len (vcm->ex_bitmap)); + } + + do + { + /* *INDENT-OFF* */ + clib_bitmap_foreach (session_index, vcm->rd_bitmap, + ({ + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (rv < 0) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] session %d specified in " + "read_map is closed.", vcm->my_pid, + session_index); + bits_set = VPPCOM_EBADFD; + goto select_done; + } + + rv = vppcom_session_read_ready (session, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + if (vcm->ex_bitmap && + clib_bitmap_get (vcm->ex_bitmap, session_index) && (rv < 0)) + { + // TBD: clib_warning + clib_bitmap_set_no_check (except_map, session_index, 1); + bits_set++; + } + else if (rv > 0) + { + // TBD: clib_warning + clib_bitmap_set_no_check (read_map, session_index, 1); + bits_set++; + } + })); + + clib_bitmap_foreach (session_index, vcm->wr_bitmap, + ({ + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (rv < 0) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 0) + clib_warning ("[%d] session %d specified in " + "write_map is closed.", vcm->my_pid, + session_index); + bits_set = VPPCOM_EBADFD; + goto select_done; + } + + rv = vppcom_session_write_ready (session, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + if (rv > 0) + { + // TBD: clib_warning + clib_bitmap_set_no_check (write_map, session_index, 1); + bits_set++; + } + })); + + clib_bitmap_foreach (session_index, vcm->ex_bitmap, + ({ + clib_spinlock_lock (&vcm->sessions_lockp); + rv = vppcom_session_at_index (session_index, &session); + if (rv < 0) + { + clib_spinlock_unlock (&vcm->sessions_lockp); + if (VPPCOM_DEBUG > 1) + clib_warning ("[%d] session %d specified in " + "except_map is closed.", vcm->my_pid, + session_index); + bits_set = VPPCOM_EBADFD; + goto select_done; + } + + rv = vppcom_session_read_ready (session, session_index); + clib_spinlock_unlock (&vcm->sessions_lockp); + if (rv < 0) + { + // TBD: clib_warning + clib_bitmap_set_no_check (except_map, session_index, 1); + bits_set++; + } + })); + /* *INDENT-ON* */ + } + while (clib_time_now (&vcm->clib_time) < timeout); + +select_done: + return (bits_set); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/uri/vppcom.h b/src/uri/vppcom.h new file mode 100644 index 00000000..4b048e03 --- /dev/null +++ b/src/uri/vppcom.h @@ -0,0 +1,152 @@ +/* + * 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 included_vppcom_h +#define included_vppcom_h + +#include +#include + +/* + * VPPCOM Public API Definitions, Enums, and Data Structures + */ +#define INVALID_SESSION_ID (~0) +#define VPPCOM_VRF_DEFAULT 0 +#define VPPCOM_CONF_ENV "VPPCOM_CONF" +#define VPPCOM_CONF_DEFAULT "/etc/vpp/vppcom.conf" + +typedef enum +{ + VPPCOM_PROTO_TCP = 0, + VPPCOM_PROTO_UDP, +} vppcom_proto_t; + +typedef enum +{ + VPPCOM_IS_IP6 = 0, + VPPCOM_IS_IP4, +} vppcom_is_ip4_t; + +typedef struct vppcom_endpt_t_ +{ + uint32_t vrf; + uint8_t is_cut_thru; + uint8_t is_ip4; + uint8_t *ip; + uint16_t port; +} vppcom_endpt_t; + +typedef enum +{ + VPPCOM_OK = 0, + VPPCOM_EAGAIN = -EAGAIN, + VPPCOM_EINVAL = -EINVAL, + VPPCOM_EBADFD = -EBADFD, + VPPCOM_EAFNOSUPPORT = -EAFNOSUPPORT, + VPPCOM_ECONNRESET = -ECONNRESET, + VPPCOM_ECONNREFUSED = -ECONNREFUSED, + VPPCOM_ETIMEDOUT = -ETIMEDOUT, +} vppcom_error_t; + +/* + * VPPCOM Public API Functions + */ +static inline const char * +vppcom_retval_str (int retval) +{ + char *st; + + switch (retval) + { + case VPPCOM_OK: + st = "VPPCOM_OK"; + break; + + case VPPCOM_EAGAIN: + st = "VPPCOM_EAGAIN"; + break; + + case VPPCOM_EINVAL: + st = "VPPCOM_EINVAL"; + break; + + case VPPCOM_EBADFD: + st = "VPPCOM_EBADFD"; + break; + + case VPPCOM_EAFNOSUPPORT: + st = "VPPCOM_EAFNOSUPPORT"; + break; + + case VPPCOM_ECONNRESET: + st = "VPPCOM_ECONNRESET"; + break; + + case VPPCOM_ECONNREFUSED: + st = "VPPCOM_ECONNREFUSED"; + break; + + case VPPCOM_ETIMEDOUT: + st = "VPPCOM_ETIMEDOUT"; + break; + + default: + st = "UNKNOWN_STATE"; + break; + } + + return st; +} + +static inline int +is_vcom_fd (int fd) +{ +#define VPPCOM_FD_OFFSET (1 << 30) + return (fd >= VPPCOM_FD_OFFSET); +} + +/* TBD: make these constructor/destructor function */ +extern int vppcom_app_create (char *app_name); +extern void vppcom_app_destroy (void); + +extern int vppcom_session_create (uint32_t vrf, uint8_t proto, + uint8_t is_nonblocking); +extern int vppcom_session_close (uint32_t session_index); + +extern int vppcom_session_bind (uint32_t session_index, vppcom_endpt_t * ep); +extern int vppcom_session_listen (uint32_t session_index, uint32_t q_len); +extern int vppcom_session_accept (uint32_t session_index, + vppcom_endpt_t * client_ep, + double wait_for_time); + +extern int vppcom_session_connect (uint32_t session_index, + vppcom_endpt_t * server_ep); +extern int vppcom_session_read (uint32_t session_index, void *buf, int n); +extern int vppcom_session_write (uint32_t session_index, void *buf, int n); + +extern int vppcom_select (unsigned long n_bits, + unsigned long *read_map, + unsigned long *write_map, + unsigned long *except_map, double wait_for_time); + +#endif /* included_vppcom_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/uri/vppcom_test.conf b/src/uri/vppcom_test.conf new file mode 100644 index 00000000..e5ac4636 --- /dev/null +++ b/src/uri/vppcom_test.conf @@ -0,0 +1,25 @@ +# Test VPPCOM config file +vppcom { + heapsize 1 + api-prefix daw # this is a comment + uid 1020 this is also a comment. + gid 1020 +# This is yet another comment! + segment-baseva 0x300000000 + segment-size 0x10000000 + segment-size 268435456 + add-segment-size 0x1000000 + add-segment-size 134217728 + preallocated-fifo-pairs 16 + rx-fifo-size 3145728 + rx-fifo-size 0x300000 + tx-fifo-size 3145728 + tx-fifo-size 0x300000 + event-queue-size 1024 + event-queue-size 0x400 + listen-queue-size 32 + listen-queue-size 0x20 + app-timeout 54.3 + session-timeout 66.6 + accept-timeout 0.1 +} diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh new file mode 100755 index 00000000..e5676466 --- /dev/null +++ b/test/scripts/socket_test.sh @@ -0,0 +1,637 @@ +#! /bin/bash +# +# socket_test.sh -- script to run socket tests. +# +script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +vpp_dir="$WS_ROOT/build-root/install-vpp-native/vpp/bin/" +vpp_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/bin/" +vpp_shm_dir="/dev/shm/" +lib64_dir="$WS_ROOT/build-root/install-vpp-native/vpp/lib64/" +lib64_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/lib64/" +docker_vpp_dir="/vpp/" +docker_lib64_dir="/vpp-lib64/" +docker_os="ubuntu" +preload_lib="libvppsocketwrapper.so.0.0.0" +vpp_app="vpp" +sock_srvr_app="sock_test_server" +sock_clnt_app="sock_test_client" +sock_srvr_addr="127.0.0.1" +sock_srvr_port="22000" +iperf_srvr_app="iperf3 -V4d1 -s" +iperf_clnt_app="iperf3 -V4d -c localhost" +gdb_in_emacs="gdb_in_emacs" +vppcom_conf="vppcom.conf" +vppcom_conf_dir="$WS_ROOT/src/uri/" +docker_vppcom_conf_dir="/etc/vpp/" +xterm_geom="60x40" +bash_header="#! /bin/bash" +tmp_cmdfile_prefix="/tmp/socket_test_cmd" +cmd1_file="${tmp_cmdfile_prefix}1.$$" +cmd2_file="${tmp_cmdfile_prefix}2.$$" +cmd3_file="${tmp_cmdfile_prefix}3.$$" +tmp_gdb_cmdfile_prefix="/tmp/gdb_cmdfile" +def_gdb_cmdfile_prefix="$WS_ROOT/extras/gdb/gdb_cmdfile" +tmp_gdb_cmdfile_vpp="${tmp_gdb_cmdfile_prefix}_vpp.$$" +tmp_gdb_cmdfile_client="${tmp_gdb_cmdfile_prefix}_vcl_client.$$" +tmp_gdb_cmdfile_server="${tmp_gdb_cmdfile_prefix}_vcl_server.$$" +get_docker_server_ip4addr='srvr_addr=$(docker network inspect bridge | grep IPv4Address | awk -e '\''{print $2}'\'' | sed -e '\''s,/16,,'\'' -e '\''s,",,g'\'' -e '\''s/,//'\'')' +#' single quote to fix the confused emacs colorizer. +trap_signals="SIGINT SIGTERM EXIT" + +# Set default values for imported environment variables if they don't exist. +# +VPP_GDB_CMDFILE="${VPP_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vpp}" +VPPCOM_CLIENT_GDB_CMDFILE="${VPPCOM_CLIENT_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vppcom_client}" +VPPCOM_SERVER_GDB_CMDFILE="${VPPCOM_SERVER_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vppcom_server}" + +usage() { + cat < Server IP address. + -P Server Port number. + -E Run Echo test. + -N Test Cfg: number of writes. + -R Test Cfg: rx buffer size. + -T Test Cfg: tx buffer size. + -U Run Uni-directional test. + -B Run Bi-directional test. + -I Send data over multiple test sockets in parallel. + -V Test Cfg: Verbose mode. + -X Exit client/server after running test. + +Environment variables: + VPPCOM_CONF Pathname of vppcom configuration file. + VPP_GDB_CMDFILE Pathname of gdb command file for vpp. + VPPCOM_CLIENT_GDB_CMDFILE Pathname of gdb command file for client. + VPPCOM_SERVER_GDB_CMDFILE Pathname of gdb command file for server. +EOF + exit 1 +} + +declare -i emacs_vpp=0 +declare -i emacs_client=0 +declare -i emacs_server=0 +declare -i gdb_vpp=0 +declare -i gdb_client=0 +declare -i gdb_server=0 +declare -i perf_vpp=0 +declare -i perf_client=0 +declare -i perf_server=0 +declare -i leave_tmp_files=0 +declare -i bash_after_exit=0 +declare -i iperf3=0 + +while getopts ":hitlbcde:g:p:E:I:N:P:R:S:T:UBVX" opt; do + case $opt in + h) usage ;; + l) leave_tmp_files=1 + ;; + b) bash_after_exit=1 + ;; + i) iperf3=1 + ;; + t) xterm_geom="180x40" + use_tabs="true" + ;; + c) VPPCOM_CONF="${vppcom_conf_dir}vppcom_test.conf" + ;; + d) title_dbg="-DEBUG" + vpp_dir=$vpp_debug_dir + lib64_dir=$lib64_debug_dir + ;; + e) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then + emacs_client=1 + emacs_server=1 + emacs_vpp=1 + elif [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then + emacs_client=1 + elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then + emacs_server=1 + elif [ $OPTARG = "v" ] || [ $OPTARG = "vpp" ] ; then + emacs_vpp=1 + else + echo "ERROR: Option -e unknown argument \'$OPTARG\'" >&2 + usage + fi + title_dbg="-DEBUG" + vpp_dir=$vpp_debug_dir + lib64_dir=$lib64_debug_dir + ;; + g) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then + gdb_client=1 + gdb_server=1 + gdb_vpp=1 + elif [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then + gdb_client=1 + elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then + gdb_server=1 + elif [ $OPTARG = "v" ] || [ $OPTARG = "vpp" ] ; then + gdb_vpp=1 + else + echo "ERROR: Option -g unknown argument \'$OPTARG\'" >&2 + usage + fi + title_dbg="-DEBUG" + vpp_dir=$vpp_debug_dir + lib64_dir=$lib64_debug_dir + ;; + p) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then + perf_client=1 + perf_server=1 + perf_vpp=1 + elif [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then + perf_client=1 + elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then + perf_server=1 + elif [ $OPTARG = "v" ] || [ $OPTARG = "vpp" ] ; then + perf_vpp=1 + else + echo "ERROR: Option -p unknown argument \'$OPTARG\'" >&2 + usage + fi + echo "WARNING: -p options TBD" + ;; + S) sock_srvr_addr="$OPTARG" + ;; + P) sock_srvr_port="$OPTARG" + ;; +E|I|N|R|T) sock_clnt_options="$sock_clnt_options -$opt \"$OPTARG\"" + ;; + U|B|V|X) sock_clnt_options="$sock_clnt_options -$opt" + ;; + \?) + echo "ERROR: Invalid option: -$OPTARG" >&2 + usage + ;; + :) + echo "ERROR: Option -$OPTARG requires an argument." >&2 + usage + ;; + esac +done + +shift $(( $OPTIND-1 )) +while ! [[ $run_test ]] && (( $# > 0 )) ; do + case $1 in + "nk" | "native-kernel") + run_test="native_kernel" ;; + "np" | "native-preload") + run_test="native_preload" ;; + "nv" | "native-vcl") + sock_srvr_app="vcl_test_server" + sock_clnt_app="vcl_test_client" + run_test="native_vcl" ;; + "dk" | "docker-kernel") + run_test="docker_kernel" ;; + "dp" | "docker-preload") + run_test="docker_preload" ;; + "dv" | "docker-vcl") + sock_srvr_app="vcl_test_server" + sock_clnt_app="vcl_test_client" + run_test="docker_vcl" ;; + *) + echo "ERROR: Unknown option '$1'!" >&2 + usage ;; + esac + shift +done + +if [ -z "$WS_ROOT" ] ; then + echo "ERROR: WS_ROOT environment variable not set!" >&2 + echo " Please set WS_ROOT to VPP workspace root directory." >&2 + env_test_failed="true" +fi + +if [ ! -d $vpp_dir ] ; then + echo "ERROR: Missing VPP$DEBUG bin directory!" >&2 + echo " $vpp_dir" >&2 + env_test_failed="true" +fi + +if [[ $run_test =~ .*"_preload" ]] ; then + if [ ! -d $lib64_dir ] ; then + echo "ERROR: Missing VPP$DEBUG lib64 directory!" >&2 + echo " $lib64_dir" >&2 + env_test_failed="true" + elif [ ! -f $lib64_dir$preload_lib ] ; then + echo "ERROR: Missing VPP$DEBUG PRE_LOAD library!" >&2 + echo " $lib64_dir$preload_lib" >&2 + env_test_failed="true" + fi +fi + +if [ ! -f $vpp_dir$vpp_app ] ; then + echo "ERROR: Missing VPP$DEBUG Application!" >&2 + echo " $vpp_dir$vpp_app" >&2 + env_test_failed="true" +fi + +if [ ! -f $vpp_dir$sock_srvr_app ] ; then + echo "ERROR: Missing$DEBUG Socket Server Application!" >&2 + echo " $vpp_dir$sock_srvr_app" >&2 + env_test_failed="true" +fi + +if [ ! -f $vpp_dir$sock_clnt_app ] ; then + echo "ERROR: Missing$DEBUG Socket Client Application!" >&2 + echo " $vpp_dir$sock_clnt_app" >&2 + env_test_failed="true" +fi + +if [[ $run_test =~ "docker_".* ]] ; then + if [ $emacs_client -eq 1 ] || [ $emacs_server -eq 1 ] || [ $gdb_client -eq 1 ] || [ $gdb_server -eq 1 ] ; then + + echo "WARNING: gdb is not currently supported in docker." + echo " Ignoring client/server gdb options." + emacs_client=0 + emacs_server=0 + gdb_client=0 + gdb_server=0 + fi +fi + +if [ -n "$env_test_failed" ] ; then + exit 1 +fi + +if [ -f "$VPPCOM_CONF" ] ; then + vppcom_conf="$(basename $VPPCOM_CONF)" + vppcom_conf_dir="$(dirname $VPPCOM_CONF)/" + api_prefix="$(egrep -s '^\s*api-prefix \w+' $VPPCOM_CONF | awk -e '{print $2}')" + if [ -n "$api_prefix" ] ; then + api_segment=" api-segment { prefix $api_prefix }" + fi +fi +vpp_args="unix { interactive cli-listen /run/vpp/cli.sock }${api_segment}" + +if [ $iperf3 -eq 1 ] && [[ ! $run_test =~ "docker_".* ]] ; then + app_dir="$(dirname $(which iperf3))/" + srvr_app=$iperf_srvr_app + clnt_app=$iperf_clnt_app +else + app_dir="$vpp_dir" + srvr_app="$sock_srvr_app $sock_srvr_port" + clnt_app="$sock_clnt_app${sock_clnt_options} \$srvr_addr $sock_srvr_port" +fi + +verify_no_vpp() { + local running_vpp="ps -eaf|grep -v grep|grep \"bin/vpp\"" + if [ "$(eval $running_vpp)" != "" ] ; then + echo "ERROR: Please kill all running vpp instances:" + echo + eval $running_vpp + echo + exit 1 + fi + clean_devshm="$vpp_shm_dir*db $vpp_shm_dir*global_vm $vpp_shm_dir*vpe-api $vpp_shm_dir[0-9]*-[0-9]* $vpp_shm_dir*:segment[0-9]*" + rm -f $clean_devshm + devshm_files="$(ls -l $clean_devshm 2>/dev/null | grep $(whoami))" + if [ "$devshm_files" != "" ] ; then + echo "ERROR: Please remove the following $vpp_shm_dir files:" + for file in "$devshm_files" ; do + echo " $file" + done + exit 1 + fi +} + +verify_no_docker_containers() { + if (( $(which docker | wc -l) < 1 )) ; then + echo "ERROR: docker is not installed!" + echo "See https://docs.docker.com/engine/installation/linux/ubuntu/" + echo " or https://docs.docker.com/engine/installation/linux/centos/" + exit 1 + fi + if (( $(docker ps | wc -l) > 1 )) ; then + echo "ERROR: Run the following to kill all docker containers:" + echo "docker kill \$(docker ps -q)" + echo + docker ps + exit 1 + fi +} + +set_pre_cmd() { + # arguments + # $1 : emacs flag + # $2 : gdb flag + # $3 : optional LD_PRELOAD library pathname + local -i emacs=$1 + local -i gdb=$2 + + if [ $emacs -eq 1 ] ; then + write_gdb_cmdfile $tmp_gdb_cmdfile $gdb_cmdfile $emacs $3 + pre_cmd="$gdb_in_emacs " + elif [ $gdb -eq 1 ] ; then + write_gdb_cmdfile $tmp_gdb_cmdfile $gdb_cmdfile $emacs $3 + pre_cmd="gdb -x $tmp_gdb_cmdfile -i=mi --args " + elif [ -z $3 ] ; then + unset -v pre_cmd + else + docker_ld_preload="-e LD_PRELOAD=$3 " + pre_cmd="LD_PRELOAD=$3 " + fi +} + +write_script_header() { + # arguments + # $1 : command script file + # $2 : gdb command file + # $3 : title + # $4 : optional command string (typically "sleep 2") + echo "$bash_header" > $1 + echo -e "#\n# $1 generated on $(date)\n#" >> $1 + if [ $leave_tmp_files -eq 0 ] ; then + echo "trap \"rm -f $1 $2\" $trap_signals" >> $1 + fi + echo "export VPPCOM_CONF=${vppcom_conf_dir}${vppcom_conf}" >> $1 + if [ "$pre_cmd" = "$gdb_in_emacs " ] ; then + cat <> $1 +$gdb_in_emacs() { + emacs --eval "(gdb \"gdb -x $2 -i=mi --args \$*\")" --eval "(setq frame-title-format \"$3\")" +} +EOF + fi + if [ -n "$4" ] ; then + echo "$4" >> $1 + fi +} + +write_script_footer() { + # arguments + # $1 : command script file + # $2 : perf flag indicating to run bash before exit + local -i perf=$2 + if [ $bash_after_exit -eq 1 ] || [ $perf -eq 1 ] ; then + echo "bash" >> $1 + fi +} + +write_gdb_cmdfile() { + # arguments + # $1 : gdb command file + # $2 : User specified gdb cmdfile + # $3 : emacs flag + # $4 : optional LD_PRELOAD library pathname. + local -i emacs=$3 + + echo "# $1 generated on $(date)" > $1 + echo "#" >> $1 + echo "set confirm off" >> $1 + if [ -n "$4" ] ; then + echo "set exec-wrapper env LD_PRELOAD=$4" >> $1 + echo "start" >> $1 + fi + + if [ ! -f $2 ] ; then + echo -n "# " >> $1 + fi + echo "source $2" >> $1 + if [ $emacs -eq 0 ] ; then + echo "run" >> $1 + fi +} + +native_kernel() { + banner="Running NATIVE-KERNEL socket test" + + title1="SERVER$title_dbg (Native-Kernel Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd1_file + write_script_footer $cmd1_file $perf_server + + title2="CLIENT$title_dbg (Native-Kernel Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd2_file + echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_client + + chmod +x $cmd1_file $cmd2_file +} + +native_preload() { + verify_no_vpp + banner="Running NATIVE-PRELOAD socket test" + ld_preload="$lib64_dir$preload_lib " + + title1="VPP$title_dbg (Native-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp + gdb_cmdfile=$VPP_GDB_CMDFILE + set_pre_cmd $emacs_vpp $gdb_vpp + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args " >> $cmd1_file + write_script_footer $cmd1_file $perf_vpp + + title2="SERVER$title_dbg (Native-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server $ld_preload + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + + title3="CLIENT$title_dbg (Native-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client $ld_preload + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file + echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + + chmod +x $cmd1_file $cmd2_file $cmd3_file +} + +native_vcl() { + verify_no_vpp + banner="Running NATIVE-VCL socket test" + + title1="VPP$title_dbg (Native-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp + gdb_cmdfile=$VPP_GDB_CMDFILE + set_pre_cmd $emacs_vpp $gdb_vpp + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args " >> $cmd1_file + write_script_footer $cmd1_file $perf_vpp + + title2="SERVER$title_dbg (Native-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd2_file + echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + + title3="CLIENT$title_dbg (Native-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd3_file + echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file + echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + + chmod +x $cmd1_file $cmd2_file $cmd3_file +} + +docker_kernel() { + verify_no_docker_containers + banner="Running DOCKER-KERNEL socket test" + + title1="SERVER$title_dbg (Docker-Native Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "docker run -it -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd1_file + write_script_footer $cmd1_file $perf_server + + title2="CLIENT$title_dbg (Docker-Native Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "$get_docker_server_ip4addr" >> $cmd2_file + echo "docker run -it -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_client + + chmod +x $cmd1_file $cmd2_file +} + +docker_preload() { + verify_no_vpp + verify_no_docker_containers + banner="Running DOCKER-PRELOAD socket test" + ld_preload="$docker_lib64_dir$preload_lib " + + title1="VPP$title_dbg (Docker-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp + gdb_cmdfile=$VPP_GDB_CMDFILE + set_pre_cmd $emacs_vpp $gdb_vpp + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args" >> $cmd1_file + write_script_footer $cmd1_file $perf_vpp + + title2="SERVER$title_dbg (Docker-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server $ld_preload + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + + title3="CLIENT$title_dbg (Docker-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client $ld_preload + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "$get_docker_server_ip4addr" >> $cmd3_file + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + + chmod +x $cmd1_file $cmd2_file $cmd3_file +} + +docker_vcl() { + verify_no_vpp + verify_no_docker_containers + banner="Running DOCKER-VCL socket test" + + title1="VPP$title_dbg (Docker-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp + gdb_cmdfile=$VPP_GDB_CMDFILE + set_pre_cmd $emacs_vpp $gdb_vpp + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args" >> $cmd1_file + write_script_footer $cmd1_file $perf_vpp + + title2="SERVER$title_dbg (Docker-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + + title3="CLIENT$title_dbg (Docker-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "$get_docker_server_ip4addr" >> $cmd3_file + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + + chmod +x $cmd1_file $cmd2_file $cmd3_file +} + +if [[ $run_test ]] ; then + eval $run_test +else + echo "ERROR: Please specify a test to run!" >&2 + usage; +fi + +if (( $(which xfce4-terminal | wc -l) > 0 )) ; then + xterm_cmd="xfce4-terminal --geometry $xterm_geom" + if [[ $use_tabs ]] ; then + if [ -x "$cmd3_file" ] ; then + $xterm_cmd --title "$title1" --command "$cmd1_file" --tab --title "$title2" --command "$cmd2_file" --tab --title "$title3" --command "$cmd3_file" + else + $xterm_cmd --title "$title1" --command "$cmd1_file" --tab --title "$title2" --command "$cmd2_file" + fi + else + ($xterm_cmd --title "$title1" --command "$cmd1_file" &) + ($xterm_cmd --title "$title2" --command "$cmd2_file" &) + if [ -x "$cmd3_file" ] ; then + ($xterm_cmd --title "$title3" --command "$cmd3_file" &) + fi + fi + +else + if [[ $use_tabs ]] ; then + echo "Sorry, plain ol' xterm doesn't support tabs." + fi + xterm_cmd="xterm -fs 10 -geometry $xterm_geom" + ($xterm_cmd -title "$title1" -e "$cmd1_file" &) + ($xterm_cmd -title "$title2" -e "$cmd2_file" &) + if [ -x "$cmd3_file" ] ; then + ($xterm_cmd -title "$title3" -e "$cmd3_file" &) + fi +fi + +sleep 1 -- cgit 1.2.3-korg From db4e84cf2f8de0909c3483c8cadb25ac72fb3367 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Fri, 11 Aug 2017 10:06:15 +0200 Subject: make test: properly handle ctrl-c Change-Id: Iab88886ebc1582626813777ea45ce97fc8e36443 Signed-off-by: Klement Sekera --- test/Makefile | 10 ++++++++- test/scripts/run_in_venv_with_cleanup.sh | 38 ++++++++++++++++++++++++++++++++ test/scripts/run_with_cleanup.sh | 25 --------------------- test/scripts/setsid_wrapper.sh | 12 ++++++++++ 4 files changed, 59 insertions(+), 26 deletions(-) create mode 100755 test/scripts/run_in_venv_with_cleanup.sh delete mode 100755 test/scripts/run_with_cleanup.sh create mode 100755 test/scripts/setsid_wrapper.sh (limited to 'test/scripts') diff --git a/test/Makefile b/test/Makefile index d48a6b5a..33779dce 100644 --- a/test/Makefile +++ b/test/Makefile @@ -13,6 +13,14 @@ else VPP_PIDS=$(shell pgrep -d, -x vpp_main) endif +ifeq ($(DEBUG),gdb) +FORCE_FOREGROUND=1 +else ifeq ($(DEBUG),gdbserver) +FORCE_FOREGROUND=1 +else +FORCE_FOREGROUND=0 +endif + verify-no-running-vpp: @if [ "$(VPP_PIDS)" != "" ]; then \ echo; \ @@ -76,7 +84,7 @@ $(PAPI_INSTALL_DONE): $(PIP_PATCH_DONE) @touch $@ define retest-func - @bash -c "source $(PYTHON_VENV_PATH)/bin/activate && setsid scripts/run_with_cleanup.sh python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS)" + @scripts/setsid_wrapper.sh $(FORCE_FOREGROUND) $(PYTHON_VENV_PATH)/bin/activate python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS) endef .PHONY: sanity diff --git a/test/scripts/run_in_venv_with_cleanup.sh b/test/scripts/run_in_venv_with_cleanup.sh new file mode 100755 index 00000000..541f584a --- /dev/null +++ b/test/scripts/run_in_venv_with_cleanup.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +rv=0 + +atexit() { + group_id=`ps -p $$ -o pgid=` + my_id=$$ + ids=`pgrep -g $group_id -d ' ' | sed "s/\b$my_id\b//g"` + echo "Killing possible remaining process IDs: $ids" + for id in $ids + do + if ps -p $id > /dev/null + then + kill -9 $id + fi + done + exit $rv +} + +trap "atexit;" SIGINT SIGTERM + +FORCE_FOREGROUND=$1 +shift + +source $1 +shift + +if [[ "${FORCE_FOREGROUND}" == "1" ]] +then + $* +else + $* & + wait +fi + +rv=$? +atexit +exit $rv diff --git a/test/scripts/run_with_cleanup.sh b/test/scripts/run_with_cleanup.sh deleted file mode 100755 index dcaa58b4..00000000 --- a/test/scripts/run_with_cleanup.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash - -rv=0 - -atexit() { - group_id=`ps -p $$ -o pgid=` - my_id=$$ - ids=`pgrep -g $group_id -d ' ' | sed "s/\b$my_id\b//g"` - echo "Killing possible remaining process IDs: $ids" - for id in $ids - do - if ps -p $id > /dev/null - then - kill -9 $id - fi - done - exit $rv -} - -trap "atexit" SIGINT SIGTERM - -$* -rv=$? -atexit -exit $rv diff --git a/test/scripts/setsid_wrapper.sh b/test/scripts/setsid_wrapper.sh new file mode 100755 index 00000000..e18b6ad5 --- /dev/null +++ b/test/scripts/setsid_wrapper.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +if [[ "$1" == "1" ]] +then + setsid scripts/run_in_venv_with_cleanup.sh $* + pid=$! +else + setsid scripts/run_in_venv_with_cleanup.sh $* & + pid=$! + trap "echo setsid_wrapper.sh: got signal, killing child pid ${pid}; kill ${pid}; sleep .1;" SIGINT SIGTERM + wait +fi -- cgit 1.2.3-korg From d69f4f416d291fd03ba423af4eb6f6c416f328a4 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Mon, 14 Aug 2017 18:26:46 -0400 Subject: Fix VCL LD_PRELOAD lib location in socket_test.sh - Use VCL_LDPRELOAD_LIB_DIR env. var if set. - Default to /usr/local/lib where it will be installed. - Change library name to libvcl_ldpreload.so.0.0.0 Change-Id: I4fc30b581c8406c5895f875d859aa44bb9ef19b5 Signed-off-by: Dave Wallace --- test/scripts/socket_test.sh | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) (limited to 'test/scripts') diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh index e5676466..f134f817 100755 --- a/test/scripts/socket_test.sh +++ b/test/scripts/socket_test.sh @@ -11,7 +11,7 @@ lib64_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/lib64/" docker_vpp_dir="/vpp/" docker_lib64_dir="/vpp-lib64/" docker_os="ubuntu" -preload_lib="libvppsocketwrapper.so.0.0.0" +vcl_ldpreload_lib="libvcl_ldpreload.so.0.0.0" vpp_app="vpp" sock_srvr_app="sock_test_server" sock_clnt_app="sock_test_client" @@ -43,6 +43,7 @@ trap_signals="SIGINT SIGTERM EXIT" VPP_GDB_CMDFILE="${VPP_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vpp}" VPPCOM_CLIENT_GDB_CMDFILE="${VPPCOM_CLIENT_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vppcom_client}" VPPCOM_SERVER_GDB_CMDFILE="${VPPCOM_SERVER_GDB_CMDFILE:-${def_gdb_cmdfile_prefix}.vppcom_server}" +VCL_LDPRELOAD_LIB_DIR="${VCL_LDPRELOAD_LIB_DIR:-/usr/local/lib}" usage() { cat <&2 echo " $lib64_dir" >&2 + elif [ ! -d $VCL_LDPRELOAD_LIB_DIR ] ; then + echo "ERROR: Missing VCL LD_PRELOAD Library directory!" >&2 + echo " $VCL_LDPRELOAD_LIB_DIR" >&2 env_test_failed="true" - elif [ ! -f $lib64_dir$preload_lib ] ; then - echo "ERROR: Missing VPP$DEBUG PRE_LOAD library!" >&2 - echo " $lib64_dir$preload_lib" >&2 + elif [ ! -f $VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib ] ; then + echo "ERROR: Missing VCL LD_PRELOAD library!" >&2 + echo " $VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib" >&2 env_test_failed="true" fi fi @@ -368,7 +372,7 @@ write_script_header() { echo "$bash_header" > $1 echo -e "#\n# $1 generated on $(date)\n#" >> $1 if [ $leave_tmp_files -eq 0 ] ; then - echo "trap \"rm -f $1 $2\" $trap_signals" >> $1 + echo "trap \"rm -f $1 $2 $dup_vcl_ldpreload_lib\" $trap_signals" >> $1 fi echo "export VPPCOM_CONF=${vppcom_conf_dir}${vppcom_conf}" >> $1 if [ "$pre_cmd" = "$gdb_in_emacs " ] ; then @@ -444,7 +448,7 @@ native_kernel() { native_preload() { verify_no_vpp banner="Running NATIVE-PRELOAD socket test" - ld_preload="$lib64_dir$preload_lib " + ld_preload="$VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib " title1="VPP$title_dbg (Native-Preload Socket Test)" tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp @@ -536,7 +540,10 @@ docker_preload() { verify_no_vpp verify_no_docker_containers banner="Running DOCKER-PRELOAD socket test" - ld_preload="$docker_lib64_dir$preload_lib " + ld_preload="$VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib " + docker_ld_preload_lib="$docker_lib64_dir$vcl_ldpreload_lib " + dup_vcl_ldpreload_lib="$lib64_dir$vcl_ldpreload_lib" + cp $ld_preload $dup_vcl_ldpreload_lib title1="VPP$title_dbg (Docker-Preload Socket Test)" tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp @@ -549,7 +556,7 @@ docker_preload() { title2="SERVER$title_dbg (Docker-Preload Socket Test)" tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server $ld_preload + set_pre_cmd $emacs_server $gdb_server $docker_ld_preload_lib write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server @@ -557,7 +564,7 @@ docker_preload() { title3="CLIENT$title_dbg (Docker-Preload Socket Test)" tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client $ld_preload + set_pre_cmd $emacs_client $gdb_client $docker_ld_preload_lib write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" echo "$get_docker_server_ip4addr" >> $cmd3_file echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd3_file -- cgit 1.2.3-korg From f413bef1358e014c9a6cb75bd2ec3e1f351e64ff Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Tue, 15 Aug 2017 07:09:02 +0200 Subject: make test: collect symlinks to failed tests Compress files in temporary directories of failed tests and symlink the directories under /tmp/vpp-failed-unittests location - preparation for jenkins archivation. Automatically cleanup the directory at start of test run. The compression is performed only when environment variable COMPRESS_FAILED_TEST_LOGS is set to one of "yes", "y", "1". This is set in verify target, but left unset by default, so when invoking make test by hand, files won't be compressed. Change-Id: I84c8f1c6aa79aa9c0b753357022b1f195f17a283 Signed-off-by: Klement Sekera --- Makefile | 2 +- test/Makefile | 7 ++++++- test/framework.py | 22 +++++++++++++++++++++- test/scripts/compress_failed.sh | 21 +++++++++++++++++++++ 4 files changed, 49 insertions(+), 3 deletions(-) create mode 100755 test/scripts/compress_failed.sh (limited to 'test/scripts') diff --git a/Makefile b/Makefile index 1cce928a..c1a7cbb5 100644 --- a/Makefile +++ b/Makefile @@ -501,7 +501,7 @@ endif $(call banner,"Building $(PKG) packages") @make pkg-$(PKG) ifeq ($(OS_ID)-$(OS_VERSION_ID),ubuntu-16.04) - @make test + @make COMPRESS_FAILED_TEST_LOGS=yes test endif diff --git a/test/Makefile b/test/Makefile index 33779dce..72b4dac7 100644 --- a/test/Makefile +++ b/test/Makefile @@ -1,5 +1,7 @@ .PHONY: verify-python-path +VPP_TEST_FAILED_DIR=/tmp/vpp-failed-unittests/ + verify-python-path: ifndef VPP_PYTHON_PREFIX $(error VPP_PYTHON_PREFIX is not set) @@ -84,7 +86,8 @@ $(PAPI_INSTALL_DONE): $(PIP_PATCH_DONE) @touch $@ define retest-func - @scripts/setsid_wrapper.sh $(FORCE_FOREGROUND) $(PYTHON_VENV_PATH)/bin/activate python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS) + @env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) scripts/setsid_wrapper.sh $(FORCE_FOREGROUND) $(PYTHON_VENV_PATH)/bin/activate python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS) + @env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) scripts/compress_failed.sh endef .PHONY: sanity @@ -129,6 +132,8 @@ shell: verify-python-path $(PAPI_INSTALL_DONE) reset: @rm -f /dev/shm/vpp-unittest-* @rm -rf /tmp/vpp-unittest-* + @rm -rf $(VPP_TEST_FAILED_DIR) + @mkdir $(VPP_TEST_FAILED_DIR) wipe: reset @rm -rf $(PYTHON_VENV_PATH) diff --git a/test/framework.py b/test/framework.py index 89d95cb3..008bda3b 100644 --- a/test/framework.py +++ b/test/framework.py @@ -275,7 +275,7 @@ class VppTestCase(unittest.TestCase): gc.collect() # run garbage collection first cls.logger = getLogger(cls.__name__) cls.tempdir = tempfile.mkdtemp( - prefix='vpp-unittest-' + cls.__name__ + '-') + prefix='vpp-unittest-%s-' % cls.__name__) cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir) cls.file_handler.setFormatter( Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s', @@ -781,6 +781,24 @@ class VppTestResult(unittest.TestResult): unittest.TestResult.addSkip(self, test, reason) self.result_string = colorize("SKIP", YELLOW) + def symlink_failed(self, test): + logger = None + if hasattr(test, 'logger'): + logger = test.logger + if hasattr(test, 'tempdir'): + try: + failed_dir = os.getenv('VPP_TEST_FAILED_DIR') + link_path = '%s/%s-FAILED' % (failed_dir, + test.tempdir.split("/")[-1]) + if logger: + logger.debug("creating a link to the failed test") + logger.debug("os.symlink(%s, %s)" % + (test.tempdir, link_path)) + os.symlink(test.tempdir, link_path) + except Exception as e: + if logger: + logger.error(e) + def addFailure(self, test, err): """ Record a test failed result @@ -800,6 +818,7 @@ class VppTestResult(unittest.TestResult): if hasattr(test, 'tempdir'): self.result_string = colorize("FAIL", RED) + \ ' [ temp dir used by test case: ' + test.tempdir + ' ]' + self.symlink_failed(test) else: self.result_string = colorize("FAIL", RED) + ' [no temp dir]' @@ -822,6 +841,7 @@ class VppTestResult(unittest.TestResult): if hasattr(test, 'tempdir'): self.result_string = colorize("ERROR", RED) + \ ' [ temp dir used by test case: ' + test.tempdir + ' ]' + self.symlink_failed(test) else: self.result_string = colorize("ERROR", RED) + ' [no temp dir]' diff --git a/test/scripts/compress_failed.sh b/test/scripts/compress_failed.sh new file mode 100755 index 00000000..6076b3b3 --- /dev/null +++ b/test/scripts/compress_failed.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +if [ "$(ls -A ${VPP_TEST_FAILED_DIR})" ] +then + if [ "${COMPRESS_FAILED_TEST_LOGS}" == "yes" ] + then + echo -n "Compressing files in temporary directories from failed test runs..." + cd ${VPP_TEST_FAILED_DIR} + for d in * + do + cd ${d} + find . ! -path . -print0 | xargs -0 -n1 gzip + cd ${VPP_TEST_FAILED_DIR} + done + echo "done." + else + echo "Not compressing files in temporary directories from failed test runs." + fi +else + echo "No symlinks to failed tests' temporary directories found in ${VPP_TEST_FAILED_DIR}." +fi -- cgit 1.2.3-korg From 30fb4a2f43cb1c47c8fbe986f88e7d8faad86f23 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Thu, 17 Aug 2017 16:22:04 -0400 Subject: Fix socket_test.sh to run iperf3 in docker. Change-Id: I47018fee4283b7b257f16e21b82bf7e497a7d985 Signed-off-by: Dave Wallace --- test/scripts/socket_test.sh | 47 ++++++++++++++++++++++++++++++--------------- 1 file changed, 31 insertions(+), 16 deletions(-) (limited to 'test/scripts') diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh index f134f817..d93ece2c 100755 --- a/test/scripts/socket_test.sh +++ b/test/scripts/socket_test.sh @@ -9,6 +9,7 @@ vpp_shm_dir="/dev/shm/" lib64_dir="$WS_ROOT/build-root/install-vpp-native/vpp/lib64/" lib64_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/lib64/" docker_vpp_dir="/vpp/" +docker_app_dir="/vpp/" docker_lib64_dir="/vpp-lib64/" docker_os="ubuntu" vcl_ldpreload_lib="libvcl_ldpreload.so.0.0.0" @@ -17,8 +18,8 @@ sock_srvr_app="sock_test_server" sock_clnt_app="sock_test_client" sock_srvr_addr="127.0.0.1" sock_srvr_port="22000" -iperf_srvr_app="iperf3 -V4d1 -s" -iperf_clnt_app="iperf3 -V4d -c localhost" +iperf_srvr_app="iperf3 -V4d -s" +iperf_clnt_app="iperf3 -V4d -c \$srvr_addr" gdb_in_emacs="gdb_in_emacs" vppcom_conf="vppcom.conf" vppcom_conf_dir="$WS_ROOT/src/uri/" @@ -256,13 +257,13 @@ if [ ! -f $vpp_dir$vpp_app ] ; then env_test_failed="true" fi -if [ ! -f $vpp_dir$sock_srvr_app ] ; then +if [ ! -f $vpp_dir$sock_srvr_app ] && [ ! $iperf3 -eq 1 ] ; then echo "ERROR: Missing$DEBUG Socket Server Application!" >&2 echo " $vpp_dir$sock_srvr_app" >&2 env_test_failed="true" fi -if [ ! -f $vpp_dir$sock_clnt_app ] ; then +if [ ! -f $vpp_dir$sock_clnt_app ] && [ ! $iperf3 -eq 1 ] ; then echo "ERROR: Missing$DEBUG Socket Client Application!" >&2 echo " $vpp_dir$sock_clnt_app" >&2 env_test_failed="true" @@ -280,6 +281,12 @@ if [[ $run_test =~ "docker_".* ]] ; then fi fi +if [[ $run_test =~ .*"_vcl" ]] && [ $iperf3 -eq 1 ] ; then + echo "ERROR: Invalid option 'i' for test $run_test!" + echo " iperf3 is not compiled with the VCL library." + env_test_failed="true" +fi + if [ -n "$env_test_failed" ] ; then exit 1 fi @@ -294,10 +301,16 @@ if [ -f "$VPPCOM_CONF" ] ; then fi vpp_args="unix { interactive cli-listen /run/vpp/cli.sock }${api_segment}" -if [ $iperf3 -eq 1 ] && [[ ! $run_test =~ "docker_".* ]] ; then +if [ $iperf3 -eq 1 ] ; then app_dir="$(dirname $(which iperf3))/" srvr_app=$iperf_srvr_app clnt_app=$iperf_clnt_app + if [[ $run_test =~ "docker_".* ]] ; then + unset -v app_dir + sock_srvr_port=5201 + docker_app_dir="networkstatic/" + unset -v docker_os + fi else app_dir="$vpp_dir" srvr_app="$sock_srvr_app $sock_srvr_port" @@ -372,7 +385,7 @@ write_script_header() { echo "$bash_header" > $1 echo -e "#\n# $1 generated on $(date)\n#" >> $1 if [ $leave_tmp_files -eq 0 ] ; then - echo "trap \"rm -f $1 $2 $dup_vcl_ldpreload_lib\" $trap_signals" >> $1 + echo "trap \"rm -f $1 $2\" $trap_signals" >> $1 fi echo "export VPPCOM_CONF=${vppcom_conf_dir}${vppcom_conf}" >> $1 if [ "$pre_cmd" = "$gdb_in_emacs " ] ; then @@ -463,6 +476,7 @@ native_preload() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server $ld_preload write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd2_file echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server @@ -471,6 +485,7 @@ native_preload() { gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE set_pre_cmd $emacs_client $gdb_client $ld_preload write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd3_file echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file write_script_footer $cmd3_file $perf_client @@ -521,7 +536,7 @@ docker_kernel() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" - echo "docker run -it -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd1_file + echo "docker run -it -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_app_dir}${srvr_app}" >> $cmd1_file write_script_footer $cmd1_file $perf_server title2="CLIENT$title_dbg (Docker-Native Socket Test)" @@ -530,7 +545,7 @@ docker_kernel() { set_pre_cmd $emacs_client $gdb_client write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" echo "$get_docker_server_ip4addr" >> $cmd2_file - echo "docker run -it -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd2_file + echo "docker run -it -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_client chmod +x $cmd1_file $cmd2_file @@ -540,10 +555,10 @@ docker_preload() { verify_no_vpp verify_no_docker_containers banner="Running DOCKER-PRELOAD socket test" - ld_preload="$VCL_LDPRELOAD_LIB_DIR/$vcl_ldpreload_lib " - docker_ld_preload_lib="$docker_lib64_dir$vcl_ldpreload_lib " - dup_vcl_ldpreload_lib="$lib64_dir$vcl_ldpreload_lib" - cp $ld_preload $dup_vcl_ldpreload_lib + docker_ld_preload_dir="/vcl-ldpreload/" + ld_preload_dir="$VCL_LDPRELOAD_LIB_DIR" + ld_preload="$docker_ld_preload_dir$vcl_ldpreload_lib " + docker_ld_preload_lib="$docker_ld_preload_dir$vcl_ldpreload_lib " title1="VPP$title_dbg (Docker-Preload Socket Test)" tmp_gdb_cmdfile=$tmp_gdb_cmdfile_vpp @@ -558,7 +573,7 @@ docker_preload() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server $docker_ld_preload_lib write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd2_file + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir:$docker_ld_preload_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server title3="CLIENT$title_dbg (Docker-Preload Socket Test)" @@ -567,7 +582,7 @@ docker_preload() { set_pre_cmd $emacs_client $gdb_client $docker_ld_preload_lib write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" echo "$get_docker_server_ip4addr" >> $cmd3_file - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd3_file + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file write_script_footer $cmd3_file $perf_client chmod +x $cmd1_file $cmd2_file $cmd3_file @@ -591,7 +606,7 @@ docker_vcl() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_vpp_dir}${srvr_app}" >> $cmd2_file + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server title3="CLIENT$title_dbg (Docker-VCL Socket Test)" @@ -600,7 +615,7 @@ docker_vcl() { set_pre_cmd $emacs_client $gdb_client write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" echo "$get_docker_server_ip4addr" >> $cmd3_file - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_vpp_dir}${clnt_app}" >> $cmd3_file + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file write_script_footer $cmd3_file $perf_client chmod +x $cmd1_file $cmd2_file $cmd3_file -- cgit 1.2.3-korg From 8712ada6c3a84b2f75a25457ec6d477f3683787c Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Wed, 16 Aug 2017 16:38:10 +0200 Subject: make test: fix broken passing of return value Change-Id: I2cb83caaf55ca9a29c06d71c6d20f8273ec062b3 Signed-off-by: Klement Sekera --- test/scripts/run_in_venv_with_cleanup.sh | 7 ++++--- test/scripts/setsid_wrapper.sh | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) (limited to 'test/scripts') diff --git a/test/scripts/run_in_venv_with_cleanup.sh b/test/scripts/run_in_venv_with_cleanup.sh index 541f584a..35b6737e 100755 --- a/test/scripts/run_in_venv_with_cleanup.sh +++ b/test/scripts/run_in_venv_with_cleanup.sh @@ -14,7 +14,7 @@ atexit() { kill -9 $id fi done - exit $rv + exit ${rv} } trap "atexit;" SIGINT SIGTERM @@ -30,9 +30,10 @@ then $* else $* & - wait + pid=$! + wait ${pid} fi rv=$? atexit -exit $rv +exit ${rv} diff --git a/test/scripts/setsid_wrapper.sh b/test/scripts/setsid_wrapper.sh index e18b6ad5..6d63426b 100755 --- a/test/scripts/setsid_wrapper.sh +++ b/test/scripts/setsid_wrapper.sh @@ -3,10 +3,10 @@ if [[ "$1" == "1" ]] then setsid scripts/run_in_venv_with_cleanup.sh $* - pid=$! else setsid scripts/run_in_venv_with_cleanup.sh $* & pid=$! trap "echo setsid_wrapper.sh: got signal, killing child pid ${pid}; kill ${pid}; sleep .1;" SIGINT SIGTERM - wait + wait ${pid} + exit $? fi -- cgit 1.2.3-korg From 9e482bb9bc3deca07955be6098e0ab6661aaf20b Mon Sep 17 00:00:00 2001 From: "Keith Burns (alagalah)" Date: Wed, 30 Aug 2017 14:06:52 -0700 Subject: Improvements to socket_test.sh script Change-Id: I10c59dc32edb7336a56722b1de8cca1d0ae31c60 Signed-off-by: Keith Burns (alagalah) --- test/scripts/socket_test.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'test/scripts') diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh index d93ece2c..d8eb75e4 100755 --- a/test/scripts/socket_test.sh +++ b/test/scripts/socket_test.sh @@ -24,7 +24,7 @@ gdb_in_emacs="gdb_in_emacs" vppcom_conf="vppcom.conf" vppcom_conf_dir="$WS_ROOT/src/uri/" docker_vppcom_conf_dir="/etc/vpp/" -xterm_geom="60x40" +xterm_geom="100x60" bash_header="#! /bin/bash" tmp_cmdfile_prefix="/tmp/socket_test_cmd" cmd1_file="${tmp_cmdfile_prefix}1.$$" @@ -299,7 +299,7 @@ if [ -f "$VPPCOM_CONF" ] ; then api_segment=" api-segment { prefix $api_prefix }" fi fi -vpp_args="unix { interactive cli-listen /run/vpp/cli.sock }${api_segment}" +vpp_args="unix { interactive }${api_segment}" if [ $iperf3 -eq 1 ] ; then app_dir="$(dirname $(which iperf3))/" @@ -536,7 +536,7 @@ docker_kernel() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" - echo "docker run -it -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_app_dir}${srvr_app}" >> $cmd1_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_app_dir}${srvr_app}" >> $cmd1_file write_script_footer $cmd1_file $perf_server title2="CLIENT$title_dbg (Docker-Native Socket Test)" @@ -545,7 +545,7 @@ docker_kernel() { set_pre_cmd $emacs_client $gdb_client write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" echo "$get_docker_server_ip4addr" >> $cmd2_file - echo "docker run -it -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd2_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_client chmod +x $cmd1_file $cmd2_file @@ -573,7 +573,7 @@ docker_preload() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server $docker_ld_preload_lib write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir:$docker_ld_preload_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir:$docker_ld_preload_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server title3="CLIENT$title_dbg (Docker-Preload Socket Test)" @@ -582,7 +582,7 @@ docker_preload() { set_pre_cmd $emacs_client $gdb_client $docker_ld_preload_lib write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" echo "$get_docker_server_ip4addr" >> $cmd3_file - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file write_script_footer $cmd3_file $perf_client chmod +x $cmd1_file $cmd2_file $cmd3_file @@ -606,7 +606,7 @@ docker_vcl() { gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server title3="CLIENT$title_dbg (Docker-VCL Socket Test)" @@ -615,7 +615,7 @@ docker_vcl() { set_pre_cmd $emacs_client $gdb_client write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" echo "$get_docker_server_ip4addr" >> $cmd3_file - echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file write_script_footer $cmd3_file $perf_client chmod +x $cmd1_file $cmd2_file $cmd3_file -- cgit 1.2.3-korg From 3079a64e19e997e6735b633377285bb5718f4067 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Wed, 6 Sep 2017 01:59:43 -0400 Subject: Fix socket_test.sh vagrant based multi-host tests. Change-Id: I8ef75a0c702098030c6814c127d3443820122327 Signed-off-by: Dave Wallace --- extras/vagrant/Vagrantfile | 4 +- extras/vagrant/update.sh | 2 +- extras/vagrant/vcl_test.sh | 23 +++ test/scripts/socket_test.sh | 406 ++++++++++++++++++++++++++++++-------------- 4 files changed, 307 insertions(+), 128 deletions(-) create mode 100644 extras/vagrant/vcl_test.sh (limited to 'test/scripts') diff --git a/extras/vagrant/Vagrantfile b/extras/vagrant/Vagrantfile index b463d646..07db660c 100644 --- a/extras/vagrant/Vagrantfile +++ b/extras/vagrant/Vagrantfile @@ -8,8 +8,6 @@ Vagrant.configure(2) do |config| if distro == 'centos7' config.vm.box = "puppetlabs/centos-7.2-64-nocm" config.ssh.insert_key = false - elsif distro == 'ubuntu1404' - config.vm.box = "puppetlabs/ubuntu-14.04-64-nocm" else config.vm.box = "puppetlabs/ubuntu-16.04-64-nocm" end @@ -25,6 +23,8 @@ Vagrant.configure(2) do |config| config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"install.sh"), :args => "/vpp" config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"clearinterfaces.sh") config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"run.sh") + elsif post_build == "vcl-test" + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"vcl_test.sh"), :args => "/vpp vagrant" end # Add .gnupg dir in so folks can sign patches diff --git a/extras/vagrant/update.sh b/extras/vagrant/update.sh index f4139d74..b6304492 100755 --- a/extras/vagrant/update.sh +++ b/extras/vagrant/update.sh @@ -40,7 +40,7 @@ if [ $DISTRIB_ID == "Ubuntu" ]; then update-alternatives --install /bin/sh sh /bin/bash 100 # Install useful but non-mandatory tools - apt-get install -y emacs x11-utils git-review gdb gdbserver + apt-get install -y emacs x11-utils git-review gdb gdbserver xfce4-terminal iperf3 elif [ $DISTRIB_ID == "CentOS" ]; then # Standard update + upgrade dance yum check-update diff --git a/extras/vagrant/vcl_test.sh b/extras/vagrant/vcl_test.sh new file mode 100644 index 00000000..9fb924ae --- /dev/null +++ b/extras/vagrant/vcl_test.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Get Command Line arguements if present +VPP_DIR=$1 +if [ "x$1" != "x" ]; then + VPP_DIR=$1 +else + VPP_DIR=`dirname $0`/../../ +fi + +if [ "x$2" != "x" ]; then + SUDOCMD="sudo -H -u $2" +fi + +echo 'Building VCL test apps' +cd $VPP_DIR +$SUDOCMD perl -pi -e 's/noinst_PROGRAMS/bin_PROGRAMS/g' $VPP_DIR/src/uri.am +$SUDOCMD make build-release +echo "export WS_ROOT=$VPP_DIR" | sudo -H -u vagrant tee /home/vagrant/.bash_aliases +source /home/vagrant/.bash_aliases +sudo cp $VPP_DIR/src/vpp/conf/80-vpp.conf /etc/sysctl.d +sudo sysctl -p/etc/sysctl.d/80-vpp.conf +sudo modprobe uio_pci_generic diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh index d8eb75e4..8c3f20a2 100755 --- a/test/scripts/socket_test.sh +++ b/test/scripts/socket_test.sh @@ -6,13 +6,16 @@ script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" vpp_dir="$WS_ROOT/build-root/install-vpp-native/vpp/bin/" vpp_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/bin/" vpp_shm_dir="/dev/shm/" +vpp_run_dir="/run/vpp" lib64_dir="$WS_ROOT/build-root/install-vpp-native/vpp/lib64/" lib64_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/lib64/" +dpdk_devbind="$WS_ROOT/build-root/install-vpp-native/dpdk/share/dpdk/usertools/dpdk-devbind.py" docker_vpp_dir="/vpp/" docker_app_dir="/vpp/" docker_lib64_dir="/vpp-lib64/" docker_os="ubuntu" vcl_ldpreload_lib="libvcl_ldpreload.so.0.0.0" +user_gid="$(id -g)" vpp_app="vpp" sock_srvr_app="sock_test_server" sock_clnt_app="sock_test_client" @@ -30,6 +33,7 @@ tmp_cmdfile_prefix="/tmp/socket_test_cmd" cmd1_file="${tmp_cmdfile_prefix}1.$$" cmd2_file="${tmp_cmdfile_prefix}2.$$" cmd3_file="${tmp_cmdfile_prefix}3.$$" +tmp_vpp_exec_file="/tmp/vpp_config.$$" tmp_gdb_cmdfile_prefix="/tmp/gdb_cmdfile" def_gdb_cmdfile_prefix="$WS_ROOT/extras/gdb/gdb_cmdfile" tmp_gdb_cmdfile_vpp="${tmp_gdb_cmdfile_prefix}_vpp.$$" @@ -64,6 +68,8 @@ OPTIONS: -d Run the vpp_debug version of all apps. -c Set VPPCOM_CONF to use the vppcom_test.conf file. -i Run iperf3 for client/server app in native tests. + -m c[lient] Run client in multi-host cfg (server on remote host) + s[erver] Run server in multi-host cfg (client on remote host) -e a[ll] Run all in emacs+gdb. c[lient] Run client in emacs+gdb. s[erver] Run server in emacs+gdb. @@ -109,7 +115,7 @@ declare -i leave_tmp_files=0 declare -i bash_after_exit=0 declare -i iperf3=0 -while getopts ":hitlbcde:g:p:E:I:N:P:R:S:T:UBVX" opt; do +while getopts ":hitlbcdm:e:g:p:E:I:N:P:R:S:T:UBVX" opt; do case $opt in h) usage ;; l) leave_tmp_files=1 @@ -145,6 +151,15 @@ while getopts ":hitlbcde:g:p:E:I:N:P:R:S:T:UBVX" opt; do vpp_dir=$vpp_debug_dir lib64_dir=$lib64_debug_dir ;; + m) if [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then + multi_host="client" + elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then + multi_host="server" + else + echo "ERROR: Option -e unknown argument \'$OPTARG\'" >&2 + usage + fi + ;; g) if [ $OPTARG = "a" ] || [ $OPTARG = "all" ] ; then gdb_client=1 gdb_server=1 @@ -227,7 +242,7 @@ done if [ -z "$WS_ROOT" ] ; then echo "ERROR: WS_ROOT environment variable not set!" >&2 echo " Please set WS_ROOT to VPP workspace root directory." >&2 - env_test_failed="true" + exit 1 fi if [ ! -d $vpp_dir ] ; then @@ -296,10 +311,17 @@ if [ -f "$VPPCOM_CONF" ] ; then vppcom_conf_dir="$(dirname $VPPCOM_CONF)/" api_prefix="$(egrep -s '^\s*api-prefix \w+' $VPPCOM_CONF | awk -e '{print $2}')" if [ -n "$api_prefix" ] ; then - api_segment=" api-segment { prefix $api_prefix }" + api_segment=" api-segment { gid $user_gid prefix $api_prefix }" fi fi -vpp_args="unix { interactive }${api_segment}" +if [ -z "$api_segment" ] ; then + api_segment=" api-segment { gid $user_gid }" +fi +if [ -n "$multi_host" ] ; then + vpp_args="unix { interactive exec $tmp_vpp_exec_file}${api_segment}" +else + vpp_args="unix { interactive }${api_segment}" +fi if [ $iperf3 -eq 1 ] ; then app_dir="$(dirname $(which iperf3))/" @@ -336,6 +358,57 @@ verify_no_vpp() { done exit 1 fi + if [ ! -d "$vpp_run_dir" ] ; then + sudo mkdir $vpp_run_dir + sudo chown root:$USER $vpp_run_dir + fi + if [ -n "$multi_host" ] ; then + vpp_eth_name="enp0s8" + vpp_eth_pci_id="$(ls -ld /sys/class/net/$vpp_eth_name/device | awk '{print $11}' | cut -d/ -f4)" + if [ -z "$vpp_eth_pci_id" ] ; then + echo "ERROR: Missing ethernet interface $vpp_eth_name!" + usage + fi + printf -v bus "%x" "0x$(echo $vpp_eth_pci_id | cut -d: -f2)" + printf -v slot "%x" "0x$(echo $vpp_eth_pci_id | cut -d: -f3 | cut -d. -f1)" + printf -v func "%x" "0x$(echo $vpp_eth_pci_id | cut -d. -f2)" + + vpp_eth_kernel_driver="$(basename $(ls -l /sys/bus/pci/devices/$vpp_eth_pci_id/driver | awk '{print $11}'))" + if [ -z "$vpp_eth_kernel_driver" ] ; then + echo "ERROR: Missing kernel driver for $vpp_eth_name!" + usage + fi + case $vpp_eth_kernel_driver in + e1000) + vpp_eth_ifname="GigabitEthernet$bus/$slot/$func" ;; + ixgbe) + vpp_eth_ifname="TenGigabitEthernet$bus/$slot/$func" ;; + *) + echo "ERROR: Unknown ethernet kernel driver $vpp_eth_kernel_driver!" + usage ;; + esac + + vpp_eth_ip4_addr="$(ip -4 -br addr show $vpp_eth_name | awk '{print $3}')" + if [ -z "$vpp_eth_ip4_addr" ] ; then + echo "ERROR: No inet address configured for $vpp_eth_name!" + usage + fi + vpp_eth_ip6_addr="$(ip -6 -br addr show $vpp_eth_name | awk '{print $3}')" + if [ -z "$vpp_eth_ip6_addr" ] ; then + echo "ERROR: No inet6 address configured for $vpp_eth_name!" + usage + fi + vpp_args="$vpp_args plugins { path $lib64_dir/vpp_plugins } dpdk { dev $vpp_eth_pci_id }" + + sudo ifdown $vpp_eth_name 2> /dev/null + echo "Configuring VPP to use $vpp_eth_name ($vpp_eth_pci_id), inet addr $vpp_eth_ip4_addr" + + cat <> $tmp_vpp_exec_file +set int state $vpp_eth_ifname up +set int ip addr $vpp_eth_ifname $vpp_eth_ip4_addr +EOF + + fi } verify_no_docker_containers() { @@ -385,15 +458,27 @@ write_script_header() { echo "$bash_header" > $1 echo -e "#\n# $1 generated on $(date)\n#" >> $1 if [ $leave_tmp_files -eq 0 ] ; then - echo "trap \"rm -f $1 $2\" $trap_signals" >> $1 + if [ -n "$multi_host" ] ; then + echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file; sudo $dpdk_devbind -e $vpp_eth_kernel_driver $vpp_eth_pci_id; sudo ifup $vpp_eth_name\" $trap_signals" >> $1 + else + echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file\" $trap_signals" >> $1 + fi fi echo "export VPPCOM_CONF=${vppcom_conf_dir}${vppcom_conf}" >> $1 if [ "$pre_cmd" = "$gdb_in_emacs " ] ; then - cat <> $1 + if [ -n "$multi_host" ] ; then + cat <> $1 +$gdb_in_emacs() { + sudo emacs --eval "(gdb \"gdb -x $2 -i=mi --args \$*\")" --eval "(setq frame-title-format \"$3\")" +} +EOF + else + cat <> $1 $gdb_in_emacs() { emacs --eval "(gdb \"gdb -x $2 -i=mi --args \$*\")" --eval "(setq frame-title-format \"$3\")" } EOF + fi fi if [ -n "$4" ] ; then echo "$4" >> $1 @@ -437,25 +522,29 @@ write_gdb_cmdfile() { native_kernel() { banner="Running NATIVE-KERNEL socket test" + if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then + title1="SERVER$title_dbg (Native-Kernel Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd1_file + write_script_footer $cmd1_file $perf_server + chmod +x $cmd1_file + fi - title1="SERVER$title_dbg (Native-Kernel Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server - gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server - write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" - echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd1_file - write_script_footer $cmd1_file $perf_server - - title2="CLIENT$title_dbg (Native-Kernel Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client - gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd2_file - echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd2_file - write_script_footer $cmd2_file $perf_client - - chmod +x $cmd1_file $cmd2_file + if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then + title2="CLIENT$title_dbg (Native-Kernel Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd2_file + echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_client + chmod +x $cmd2_file + + fi } native_preload() { @@ -468,29 +557,37 @@ native_preload() { gdb_cmdfile=$VPP_GDB_CMDFILE set_pre_cmd $emacs_vpp $gdb_vpp write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + if [ -n "$multi_host" ] && [ $emacs_vpp -eq 0 ] ; then + echo -n "sudo " >> $cmd1_file + fi echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args " >> $cmd1_file write_script_footer $cmd1_file $perf_vpp + chmod +x $cmd1_file + + if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then + title2="SERVER$title_dbg (Native-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server $ld_preload + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd2_file + echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + chmod +x $cmd2_file + fi - title2="SERVER$title_dbg (Native-Preload Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server - gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server $ld_preload - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd2_file - echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file - write_script_footer $cmd2_file $perf_server - - title3="CLIENT$title_dbg (Native-Preload Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client - gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client $ld_preload - write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" - echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd3_file - echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file - echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file - write_script_footer $cmd3_file $perf_client - - chmod +x $cmd1_file $cmd2_file $cmd3_file + if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then + title3="CLIENT$title_dbg (Native-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client $ld_preload + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$VCL_LDPRELOAD_LIB_DIR:$LD_LIBRARY_PATH\"" >> $cmd3_file + echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file + echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + chmod +x $cmd3_file + fi } native_vcl() { @@ -502,53 +599,65 @@ native_vcl() { gdb_cmdfile=$VPP_GDB_CMDFILE set_pre_cmd $emacs_vpp $gdb_vpp write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + if [ -n "$multi_host" ] && [ $emacs_vpp -eq 0 ] ; then + echo -n "sudo " >> $cmd1_file + fi echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args " >> $cmd1_file write_script_footer $cmd1_file $perf_vpp + chmod +x $cmd1_file + + if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then + title2="SERVER$title_dbg (Native-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd2_file + echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + chmod +x $cmd2_file + fi - title2="SERVER$title_dbg (Native-VCL Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server - gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd2_file - echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file - write_script_footer $cmd2_file $perf_server - - title3="CLIENT$title_dbg (Native-VCL Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client - gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client - write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" - echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd3_file - echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file - echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file - write_script_footer $cmd3_file $perf_client - - chmod +x $cmd1_file $cmd2_file $cmd3_file + if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then + title3="CLIENT$title_dbg (Native-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd3_file + echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file + echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + chmod +x $cmd3_file + fi } docker_kernel() { verify_no_docker_containers banner="Running DOCKER-KERNEL socket test" - title1="SERVER$title_dbg (Docker-Native Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server - gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server - write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" - echo "docker run -it --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_app_dir}${srvr_app}" >> $cmd1_file - write_script_footer $cmd1_file $perf_server - - title2="CLIENT$title_dbg (Docker-Native Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client - gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "$get_docker_server_ip4addr" >> $cmd2_file - echo "docker run -it --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd2_file - write_script_footer $cmd2_file $perf_client + if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then + title1="SERVER$title_dbg (Docker-Native Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + echo "docker run -it --cpuset-cpus='4-7' --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir -p $sock_srvr_port:$sock_srvr_port $docker_os ${docker_app_dir}${srvr_app}" >> $cmd1_file + write_script_footer $cmd1_file $perf_server + chmod +x $cmd1_file + fi - chmod +x $cmd1_file $cmd2_file + if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then + title2="CLIENT$title_dbg (Docker-Native Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "$get_docker_server_ip4addr" >> $cmd2_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_dir:$docker_vpp_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_client + chmod +x $cmd2_file + fi } docker_preload() { @@ -565,27 +674,35 @@ docker_preload() { gdb_cmdfile=$VPP_GDB_CMDFILE set_pre_cmd $emacs_vpp $gdb_vpp write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + if [ -n "$multi_host" ] ; then + echo -n "sudo " >> $cmd1_file + fi echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args" >> $cmd1_file write_script_footer $cmd1_file $perf_vpp - - title2="SERVER$title_dbg (Docker-Preload Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server - gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server $docker_ld_preload_lib - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir:$docker_ld_preload_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file - write_script_footer $cmd2_file $perf_server - - title3="CLIENT$title_dbg (Docker-Preload Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client - gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client $docker_ld_preload_lib - write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" - echo "$get_docker_server_ip4addr" >> $cmd3_file - echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file - write_script_footer $cmd3_file $perf_client - - chmod +x $cmd1_file $cmd2_file $cmd3_file + chmod +x $cmd1_file + + if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then + title2="SERVER$title_dbg (Docker-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server $docker_ld_preload_lib + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "docker run -it -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir:$docker_ld_preload_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + chmod +x $cmd2_file + fi + + if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then + title3="CLIENT$title_dbg (Docker-Preload Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client $docker_ld_preload_lib + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "$get_docker_server_ip4addr" >> $cmd3_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $ld_preload_dir:$docker_ld_preload_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir ${docker_ld_preload}$docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + chmod +x $cmd3_file + fi } docker_vcl() { @@ -598,27 +715,35 @@ docker_vcl() { gdb_cmdfile=$VPP_GDB_CMDFILE set_pre_cmd $emacs_vpp $gdb_vpp write_script_header $cmd1_file $tmp_gdb_cmdfile "$title1" + if [ -n "$multi_host" ] ; then + echo -n "sudo " >> $cmd1_file + fi echo "${pre_cmd}$vpp_dir$vpp_app $vpp_args" >> $cmd1_file write_script_footer $cmd1_file $perf_vpp - - title2="SERVER$title_dbg (Docker-VCL Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server - gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE - set_pre_cmd $emacs_server $gdb_server - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" - echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file - write_script_footer $cmd2_file $perf_server - - title3="CLIENT$title_dbg (Docker-VCL Socket Test)" - tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client - gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE - set_pre_cmd $emacs_client $gdb_client - write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" - echo "$get_docker_server_ip4addr" >> $cmd3_file - echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file - write_script_footer $cmd3_file $perf_client - - chmod +x $cmd1_file $cmd2_file $cmd3_file + chmod +x $cmd1_file + + if [ -z "$multi_host" ] || [ "$multi_host" = "server" ] ; then + title2="SERVER$title_dbg (Docker-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server + gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE + set_pre_cmd $emacs_server $gdb_server + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -p $sock_srvr_port:$sock_srvr_port -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${srvr_app}" >> $cmd2_file + write_script_footer $cmd2_file $perf_server + chmod +x $cmd2_file + fi + + if [ -z "$multi_host" ] || [ "$multi_host" = "client" ] ; then + title3="CLIENT$title_dbg (Docker-VCL Socket Test)" + tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client + gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE + set_pre_cmd $emacs_client $gdb_client + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + echo "$get_docker_server_ip4addr" >> $cmd3_file + echo "docker run -it --cpuset-cpus='4-7' -v $vpp_shm_dir:$vpp_shm_dir -v $vpp_dir:$docker_vpp_dir -v $lib64_dir:$docker_lib64_dir -v $vppcom_conf_dir:$docker_vppcom_conf_dir -e VPPCOM_CONF=${docker_vppcom_conf_dir}/$vppcom_conf -e LD_LIBRARY_PATH=$docker_lib64_dir $docker_os ${docker_app_dir}${clnt_app}" >> $cmd3_file + write_script_footer $cmd3_file $perf_client + chmod +x $cmd3_file + fi } if [[ $run_test ]] ; then @@ -631,14 +756,41 @@ fi if (( $(which xfce4-terminal | wc -l) > 0 )) ; then xterm_cmd="xfce4-terminal --geometry $xterm_geom" if [[ $use_tabs ]] ; then + declare -a tab_cmd_files + declare -a tab_titles + declare -i i=0 + + if [ -x "$cmd1_file" ] ; then + tab_cmd_files[$i]="$cmd1_file" + tab_titles[$i]="$title1" + (( i++ )) + fi + if [ -x "$cmd2_file" ] ; then + tab_cmd_files[$i]="$cmd2_file" + tab_titles[$i]="$title2" + (( i++ )) + fi if [ -x "$cmd3_file" ] ; then - $xterm_cmd --title "$title1" --command "$cmd1_file" --tab --title "$title2" --command "$cmd2_file" --tab --title "$title3" --command "$cmd3_file" + tab_cmd_files[$i]="$cmd3_file" + tab_titles[$i]="$title3" + fi + + if [ -n "${tab_cmd_files[2]}" ] ; then + $xterm_cmd --title "${tab_titles[0]}" --command "${tab_cmd_files[0]}" --tab --title "${tab_titles[1]}" --command "${tab_cmd_files[1]}" --tab --title "${tab_titles[2]}" --command "${tab_cmd_files[2]}" + elif [ -n "${tab_cmd_files[1]}" ] ; then + $xterm_cmd --title "${tab_titles[0]}" --command "${tab_cmd_files[0]}" --tab --title "${tab_titles[1]}" --command "${tab_cmd_files[1]}" + else - $xterm_cmd --title "$title1" --command "$cmd1_file" --tab --title "$title2" --command "$cmd2_file" + $xterm_cmd --title "${tab_titles[0]}" --command "${tab_cmd_files[0]}" fi + else - ($xterm_cmd --title "$title1" --command "$cmd1_file" &) - ($xterm_cmd --title "$title2" --command "$cmd2_file" &) + if [ -x "$cmd1_file" ] ; then + ($xterm_cmd --title "$title1" --command "$cmd1_file" &) + fi + if [ -x "$cmd2_file" ] ; then + ($xterm_cmd --title "$title2" --command "$cmd2_file" &) + fi if [ -x "$cmd3_file" ] ; then ($xterm_cmd --title "$title3" --command "$cmd3_file" &) fi @@ -649,8 +801,12 @@ else echo "Sorry, plain ol' xterm doesn't support tabs." fi xterm_cmd="xterm -fs 10 -geometry $xterm_geom" - ($xterm_cmd -title "$title1" -e "$cmd1_file" &) - ($xterm_cmd -title "$title2" -e "$cmd2_file" &) + if [ -x "$cmd1_file" ] ; then + ($xterm_cmd -title "$title1" -e "$cmd1_file" &) + fi + if [ -x "$cmd2_file" ] ; then + ($xterm_cmd -title "$title2" -e "$cmd2_file" &) + fi if [ -x "$cmd3_file" ] ; then ($xterm_cmd -title "$title3" -e "$cmd3_file" &) fi -- cgit 1.2.3-korg From 1948161b65aa89613f3b6d3714158048091a903c Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Fri, 15 Sep 2017 18:47:44 -0400 Subject: Add multi-vm Vagrantfile for vcl-test. - Existing Vagrantfile is symbolic link to the default Vagrantfile. - In order to run the multi-host vcl test, change Vagrantfile -> Vagrantfile.vcl_test - Fix socket_test.sh & vppcom bugs. Change-Id: I965b7f799135c86e989c08bf6c5909677ef38dea Signed-off-by: Dave Wallace --- extras/vagrant/Vagrantfile | 114 +----------------------------------- extras/vagrant/Vagrantfile.default | 111 +++++++++++++++++++++++++++++++++++ extras/vagrant/Vagrantfile.vcl_test | 110 ++++++++++++++++++++++++++++++++++ extras/vagrant/vcl_test.sh | 20 ++++--- src/uri/vppcom.c | 48 +++++++++------ test/scripts/socket_test.sh | 13 +++- 6 files changed, 272 insertions(+), 144 deletions(-) mode change 100644 => 120000 extras/vagrant/Vagrantfile create mode 100644 extras/vagrant/Vagrantfile.default create mode 100644 extras/vagrant/Vagrantfile.vcl_test (limited to 'test/scripts') diff --git a/extras/vagrant/Vagrantfile b/extras/vagrant/Vagrantfile deleted file mode 100644 index 07db660c..00000000 --- a/extras/vagrant/Vagrantfile +++ /dev/null @@ -1,113 +0,0 @@ -# -*- mode: ruby -*- -# vi: set ft=ruby : - -Vagrant.configure(2) do |config| - - # Pick the right distro and bootstrap, default is ubuntu1604 - distro = ( ENV['VPP_VAGRANT_DISTRO'] || "ubuntu1604") - if distro == 'centos7' - config.vm.box = "puppetlabs/centos-7.2-64-nocm" - config.ssh.insert_key = false - else - config.vm.box = "puppetlabs/ubuntu-16.04-64-nocm" - end - config.vm.box_check_update = false - - config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"update.sh") - config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"build.sh"), :args => "/vpp vagrant" - - post_build = ( ENV['VPP_VAGRANT_POST_BUILD'] ) - if post_build == "test" - config.vm.provision "shell", inline: "echo Testing VPP; cd /vpp; make test" - elsif post_build == "install" - config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"install.sh"), :args => "/vpp" - config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"clearinterfaces.sh") - config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"run.sh") - elsif post_build == "vcl-test" - config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"vcl_test.sh"), :args => "/vpp vagrant" - end - - # Add .gnupg dir in so folks can sign patches - # Note, as gnupg puts socket files in that dir, we have - # to be cautious and make sure we are dealing with a plain file - homedir = File.expand_path("~/") - Dir["#{homedir}/.gnupg/**/*"].each do |fname| - if File.file?(fname) - destname = fname.sub(Regexp.escape("#{homedir}/"),'') - config.vm.provision "file", source: fname, destination: destname - end - end - - # Copy in the .gitconfig if it exists - if File.file?(File.expand_path("~/.gitconfig")) - config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" - end - - # vagrant-cachier caches apt/yum etc to speed subsequent - # vagrant up - # to enable, run - # vagrant plugin install vagrant-cachier - # - if Vagrant.has_plugin?("vagrant-cachier") - config.cache.scope = :box - end - - # Define some physical ports for your VMs to be used by DPDK - nics = (ENV['VPP_VAGRANT_NICS'] || "2").to_i(10) - for i in 1..nics - config.vm.network "private_network", type: "dhcp" - end - - # use http proxy if avaiable - if ENV['http_proxy'] && Vagrant.has_plugin?("vagrant-proxyconf") - config.proxy.http = ENV['http_proxy'] - config.proxy.https = ENV['https_proxy'] - config.proxy.no_proxy = "localhost,127.0.0.1" - end - - vmcpu=(ENV['VPP_VAGRANT_VMCPU'] || 2) - vmram=(ENV['VPP_VAGRANT_VMRAM'] || 4096) - - config.ssh.forward_agent = true - config.ssh.forward_x11 = true - - config.vm.provider "virtualbox" do |vb| - vb.customize ["modifyvm", :id, "--ioapic", "on"] - vb.memory = "#{vmram}" - vb.cpus = "#{vmcpu}" - - # rsync the vpp directory if provision hasn't happened yet - unless File.exist? (".vagrant/machines/default/virtualbox/action_provision") - config.vm.synced_folder "../../", "/vpp", type: "rsync", - rsync__auto: false, - rsync__exclude: [ - "build-root/build*/", - "build-root/install*/", - "build-root/images*/", - "build-root/*.deb", - "build-root/*.rpm", - "build-root/*.changes", - "build-root/python", - "build-root/deb/debian/*.dkms", - "build-root/deb/debian/*.install", - "build-root/deb/debian/changes", - "build-root/tools"] - end - - #support for the SSE4.x instruction is required in some versions of VB. - vb.customize ["setextradata", :id, "VBoxInternal/CPUM/SSE4.1", "1"] - vb.customize ["setextradata", :id, "VBoxInternal/CPUM/SSE4.2", "1"] - end - config.vm.provider "vmware_fusion" do |fusion,override| - fusion.vmx["memsize"] = "#{vmram}" - fusion.vmx["numvcpus"] = "#{vmcpu}" - end - config.vm.provider "libvirt" do |lv| - lv.memory = "#{vmram}" - lv.cpus = "#{vmcpu}" - end - config.vm.provider "vmware_workstation" do |vws,override| - vws.vmx["memsize"] = "#{vmram}" - vws.vmx["numvcpus"] = "#{vmcpu}" - end -end diff --git a/extras/vagrant/Vagrantfile b/extras/vagrant/Vagrantfile new file mode 120000 index 00000000..a0bc2252 --- /dev/null +++ b/extras/vagrant/Vagrantfile @@ -0,0 +1 @@ +Vagrantfile.default \ No newline at end of file diff --git a/extras/vagrant/Vagrantfile.default b/extras/vagrant/Vagrantfile.default new file mode 100644 index 00000000..8d6d02a6 --- /dev/null +++ b/extras/vagrant/Vagrantfile.default @@ -0,0 +1,111 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure(2) do |config| + + # Pick the right distro and bootstrap, default is ubuntu1604 + distro = ( ENV['VPP_VAGRANT_DISTRO'] || "ubuntu1604") + if distro == 'centos7' + config.vm.box = "puppetlabs/centos-7.2-64-nocm" + config.ssh.insert_key = false + else + config.vm.box = "puppetlabs/ubuntu-16.04-64-nocm" + end + config.vm.box_check_update = false + + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"update.sh") + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"build.sh"), :args => "/vpp vagrant" + + post_build = ( ENV['VPP_VAGRANT_POST_BUILD'] ) + if post_build == "test" + config.vm.provision "shell", inline: "echo Testing VPP; cd /vpp; make test" + elsif post_build == "install" + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"install.sh"), :args => "/vpp" + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"clearinterfaces.sh") + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"run.sh") + end + + # Add .gnupg dir in so folks can sign patches + # Note, as gnupg puts socket files in that dir, we have + # to be cautious and make sure we are dealing with a plain file + homedir = File.expand_path("~/") + Dir["#{homedir}/.gnupg/**/*"].each do |fname| + if File.file?(fname) + destname = fname.sub(Regexp.escape("#{homedir}/"),'') + config.vm.provision "file", source: fname, destination: destname + end + end + + # Copy in the .gitconfig if it exists + if File.file?(File.expand_path("~/.gitconfig")) + config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" + end + + # vagrant-cachier caches apt/yum etc to speed subsequent + # vagrant up + # to enable, run + # vagrant plugin install vagrant-cachier + # + if Vagrant.has_plugin?("vagrant-cachier") + config.cache.scope = :box + end + + # Define some physical ports for your VMs to be used by DPDK + nics = (ENV['VPP_VAGRANT_NICS'] || "2").to_i(10) + for i in 1..nics + config.vm.network "private_network", type: "dhcp" + end + + # use http proxy if avaiable + if ENV['http_proxy'] && Vagrant.has_plugin?("vagrant-proxyconf") + config.proxy.http = ENV['http_proxy'] + config.proxy.https = ENV['https_proxy'] + config.proxy.no_proxy = "localhost,127.0.0.1" + end + + vmcpu=(ENV['VPP_VAGRANT_VMCPU'] || 2) + vmram=(ENV['VPP_VAGRANT_VMRAM'] || 4096) + + config.ssh.forward_agent = true + config.ssh.forward_x11 = true + + config.vm.provider "virtualbox" do |vb| + vb.customize ["modifyvm", :id, "--ioapic", "on"] + vb.memory = "#{vmram}" + vb.cpus = "#{vmcpu}" + + # rsync the vpp directory if provision hasn't happened yet + unless File.exist? (".vagrant/machines/default/virtualbox/action_provision") + config.vm.synced_folder "../../", "/vpp", type: "rsync", + rsync__auto: false, + rsync__exclude: [ + "build-root/build*/", + "build-root/install*/", + "build-root/images*/", + "build-root/*.deb", + "build-root/*.rpm", + "build-root/*.changes", + "build-root/python", + "build-root/deb/debian/*.dkms", + "build-root/deb/debian/*.install", + "build-root/deb/debian/changes", + "build-root/tools"] + end + + #support for the SSE4.x instruction is required in some versions of VB. + vb.customize ["setextradata", :id, "VBoxInternal/CPUM/SSE4.1", "1"] + vb.customize ["setextradata", :id, "VBoxInternal/CPUM/SSE4.2", "1"] + end + config.vm.provider "vmware_fusion" do |fusion,override| + fusion.vmx["memsize"] = "#{vmram}" + fusion.vmx["numvcpus"] = "#{vmcpu}" + end + config.vm.provider "libvirt" do |lv| + lv.memory = "#{vmram}" + lv.cpus = "#{vmcpu}" + end + config.vm.provider "vmware_workstation" do |vws,override| + vws.vmx["memsize"] = "#{vmram}" + vws.vmx["numvcpus"] = "#{vmcpu}" + end +end diff --git a/extras/vagrant/Vagrantfile.vcl_test b/extras/vagrant/Vagrantfile.vcl_test new file mode 100644 index 00000000..92d6832a --- /dev/null +++ b/extras/vagrant/Vagrantfile.vcl_test @@ -0,0 +1,110 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +Vagrant.configure(2) do |config| + + # Pick the right distro and bootstrap, default is ubuntu1604 + distro = ( ENV['VPP_VAGRANT_DISTRO'] || "ubuntu1604") + if distro == 'centos7' + config.vm.box = "puppetlabs/centos-7.2-64-nocm" + config.ssh.insert_key = false + else + config.vm.box = "puppetlabs/ubuntu-16.04-64-nocm" + end + config.vm.box_check_update = false + + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"update.sh") + config.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"build.sh"), :args => "/vpp vagrant" + + # Create VCL client and server VM's + config.vm.define "vcl-server" do |server| + server.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"vcl_test.sh"), :args => "/vpp vagrant vcl-server" + end + config.vm.define "vcl-client" do |client| + client.vm.provision :shell, :path => File.join(File.dirname(__FILE__),"vcl_test.sh"), :args => "/vpp vagrant vcl-client" + end + + # Add .gnupg dir in so folks can sign patches + # Note, as gnupg puts socket files in that dir, we have + # to be cautious and make sure we are dealing with a plain file + homedir = File.expand_path("~/") + Dir["#{homedir}/.gnupg/**/*"].each do |fname| + if File.file?(fname) + destname = fname.sub(Regexp.escape("#{homedir}/"),'') + config.vm.provision "file", source: fname, destination: destname + end + end + + # Copy in the .gitconfig if it exists + if File.file?(File.expand_path("~/.gitconfig")) + config.vm.provision "file", source: "~/.gitconfig", destination: ".gitconfig" + end + + # vagrant-cachier caches apt/yum etc to speed subsequent + # vagrant up + # to enable, run + # vagrant plugin install vagrant-cachier + # + if Vagrant.has_plugin?("vagrant-cachier") + config.cache.scope = :box + end + + # Define some physical ports for your VMs to be used by DPDK + nics = (ENV['VPP_VAGRANT_NICS'] || "2").to_i(10) + for i in 1..nics + config.vm.network "private_network", type: "dhcp" + end + + # use http proxy if avaiable + if ENV['http_proxy'] && Vagrant.has_plugin?("vagrant-proxyconf") + config.proxy.http = ENV['http_proxy'] + config.proxy.https = ENV['https_proxy'] + config.proxy.no_proxy = "localhost,127.0.0.1" + end + + vmcpu=(ENV['VPP_VAGRANT_VMCPU'] || 2) + vmram=(ENV['VPP_VAGRANT_VMRAM'] || 4096) + + config.ssh.forward_agent = true + config.ssh.forward_x11 = true + + config.vm.provider "virtualbox" do |vb| + vb.customize ["modifyvm", :id, "--ioapic", "on"] + vb.memory = "#{vmram}" + vb.cpus = "#{vmcpu}" + + # rsync the vpp directory if provision hasn't happened yet + unless File.exist? (".vagrant/machines/client/virtualbox/action_provision") + config.vm.synced_folder "../../", "/vpp", type: "rsync", + rsync__auto: false, + rsync__exclude: [ + "build-root/build*/", + "build-root/install*/", + "build-root/images*/", + "build-root/*.deb", + "build-root/*.rpm", + "build-root/*.changes", + "build-root/python", + "build-root/deb/debian/*.dkms", + "build-root/deb/debian/*.install", + "build-root/deb/debian/changes", + "build-root/tools"] + end + + #support for the SSE4.x instruction is required in some versions of VB. + vb.customize ["setextradata", :id, "VBoxInternal/CPUM/SSE4.1", "1"] + vb.customize ["setextradata", :id, "VBoxInternal/CPUM/SSE4.2", "1"] + end + config.vm.provider "vmware_fusion" do |fusion,override| + fusion.vmx["memsize"] = "#{vmram}" + fusion.vmx["numvcpus"] = "#{vmcpu}" + end + config.vm.provider "libvirt" do |lv| + lv.memory = "#{vmram}" + lv.cpus = "#{vmcpu}" + end + config.vm.provider "vmware_workstation" do |vws,override| + vws.vmx["memsize"] = "#{vmram}" + vws.vmx["numvcpus"] = "#{vmcpu}" + end +end diff --git a/extras/vagrant/vcl_test.sh b/extras/vagrant/vcl_test.sh index 9fb924ae..3be18517 100644 --- a/extras/vagrant/vcl_test.sh +++ b/extras/vagrant/vcl_test.sh @@ -1,23 +1,25 @@ #!/bin/bash -# Get Command Line arguements if present -VPP_DIR=$1 -if [ "x$1" != "x" ]; then +if [ -n "$1" ]; then VPP_DIR=$1 else VPP_DIR=`dirname $0`/../../ fi -if [ "x$2" != "x" ]; then +if [ -n "$2" ]; then SUDOCMD="sudo -H -u $2" fi echo 'Building VCL test apps' cd $VPP_DIR $SUDOCMD perl -pi -e 's/noinst_PROGRAMS/bin_PROGRAMS/g' $VPP_DIR/src/uri.am -$SUDOCMD make build-release -echo "export WS_ROOT=$VPP_DIR" | sudo -H -u vagrant tee /home/vagrant/.bash_aliases -source /home/vagrant/.bash_aliases -sudo cp $VPP_DIR/src/vpp/conf/80-vpp.conf /etc/sysctl.d -sudo sysctl -p/etc/sysctl.d/80-vpp.conf +$SUDOCMD make dpdk-install-dev build-release +sudo sysctl -p$VPP_DIR/src/vpp/conf/80-vpp.conf sudo modprobe uio_pci_generic + +if [ "$2" = "vagrant" ] && [ -d "/home/vagrant" ] ; then + dot_bash_aliases="/home/$2/.bash_aliases" + echo "export WS_ROOT=$VPP_DIR" | $SUDOCMD tee $dot_bash_aliases + source $dot_bash_aliases +fi + diff --git a/src/uri/vppcom.c b/src/uri/vppcom.c index aa307f1d..8a8a806c 100644 --- a/src/uri/vppcom.c +++ b/src/uri/vppcom.c @@ -852,19 +852,15 @@ vl_api_unbind_sock_reply_t_handler (vl_api_unbind_sock_reply_t * mp) clib_spinlock_lock (&vcm->sessions_lockp); rv = vppcom_session_at_index (vcm->bind_session_index, &session); - if (PREDICT_FALSE (rv)) + if (rv == VPPCOM_OK) { - if (VPPCOM_DEBUG > 1) - clib_warning ("[%d] invalid session, sid (%d) has been closed!", - vcm->my_pid, vcm->bind_session_index); - } + if ((VPPCOM_DEBUG > 1) && (mp->retval)) + clib_warning ("[%d] unbind failed: %U", vcm->my_pid, format_api_error, + ntohl (mp->retval)); - if (mp->retval) - clib_warning ("[%d] unbind failed: %U", vcm->my_pid, format_api_error, - ntohl (mp->retval)); - - vcm->bind_session_index = ~0; - session->state = STATE_START; + vcm->bind_session_index = ~0; + session->state = STATE_START; + } clib_spinlock_unlock (&vcm->sessions_lockp); } @@ -993,6 +989,7 @@ vl_api_accept_session_t_handler (vl_api_accept_session_t * mp) unix_shared_memory_queue_t *); session->state = STATE_ACCEPT; session->is_cut_thru = 0; + session->is_server = 1; session->port = ntohs (mp->port); session->is_ip4 = mp->is_ip4; clib_memcpy (session->ip, mp->ip, sizeof (session->ip)); @@ -1277,10 +1274,6 @@ vppcom_session_disconnect (u32 session_index) vcm->my_pid, vppcom_retval_str (rv), rv); return rv; } - - clib_spinlock_lock (&vcm->sessions_lockp); - pool_put_index (vcm->sessions, session_index); - clib_spinlock_unlock (&vcm->sessions_lockp); return VPPCOM_OK; } @@ -1853,14 +1846,31 @@ vppcom_session_close (uint32_t session_index) if (session->is_cut_thru) { if (session->is_server) - rv = vppcom_session_unbind_cut_thru (session); + { + rv = vppcom_session_unbind_cut_thru (session); + if ((VPPCOM_DEBUG > 0) && (rv < 0)) + clib_warning ("[%d] unbind cut-thru (session %d) failed, " + "rv = %s (%d)", + vcm->my_pid, session_index, + vppcom_retval_str (rv), rv); + } + } + else if (session->is_server) + { + rv = vppcom_session_unbind (session_index); + if ((VPPCOM_DEBUG > 0) && (rv < 0)) + clib_warning ("[%d] unbind (session %d) failed, rv = %s (%d)", + vcm->my_pid, session_index, vppcom_retval_str (rv), rv); } else { - rv = (session->is_server) ? - vppcom_session_unbind (session_index) : - vppcom_session_disconnect (session_index); + rv = vppcom_session_disconnect (session_index); + if ((VPPCOM_DEBUG > 0) && (rv < 0)) + clib_warning ("[%d] disconnect (session %d) failed, rv = %s (%d)", + vcm->my_pid, session_index, vppcom_retval_str (rv), rv); } + if (rv < 0) + return rv; clib_spinlock_lock (&vcm->sessions_lockp); pool_put_index (vcm->sessions, session_index); diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh index 8c3f20a2..1573b48a 100755 --- a/test/scripts/socket_test.sh +++ b/test/scripts/socket_test.sh @@ -9,7 +9,7 @@ vpp_shm_dir="/dev/shm/" vpp_run_dir="/run/vpp" lib64_dir="$WS_ROOT/build-root/install-vpp-native/vpp/lib64/" lib64_debug_dir="$WS_ROOT/build-root/install-vpp_debug-native/vpp/lib64/" -dpdk_devbind="$WS_ROOT/build-root/install-vpp-native/dpdk/share/dpdk/usertools/dpdk-devbind.py" +dpdk_devbind="/usr/share/dpdk/usertools/dpdk-devbind.py" docker_vpp_dir="/vpp/" docker_app_dir="/vpp/" docker_lib64_dir="/vpp-lib64/" @@ -302,6 +302,13 @@ if [[ $run_test =~ .*"_vcl" ]] && [ $iperf3 -eq 1 ] ; then env_test_failed="true" fi +if [ -n "$mult_host"] && [ ! -f "$dpdk_devbind" ] ; then + echo "ERROR: Can't find dpdk-devbind.py!" + echo " Run \"cd \$WS_ROOT; make dpdk-install-dev\" to install it." + echo + env_test_failed="true" +fi + if [ -n "$env_test_failed" ] ; then exit 1 fi @@ -458,8 +465,8 @@ write_script_header() { echo "$bash_header" > $1 echo -e "#\n# $1 generated on $(date)\n#" >> $1 if [ $leave_tmp_files -eq 0 ] ; then - if [ -n "$multi_host" ] ; then - echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file; sudo $dpdk_devbind -e $vpp_eth_kernel_driver $vpp_eth_pci_id; sudo ifup $vpp_eth_name\" $trap_signals" >> $1 + if [ -n "$multi_host" ] && [[ "$3" == VPP* ]] ; then + echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file; sudo $dpdk_devbind -b $vpp_eth_kernel_driver $vpp_eth_pci_id; sudo ifup $vpp_eth_name\" $trap_signals" >> $1 else echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file\" $trap_signals" >> $1 fi -- cgit 1.2.3-korg From 8f2a4eafeaa439432107563033728e09665c16d9 Mon Sep 17 00:00:00 2001 From: Klement Sekera Date: Thu, 4 May 2017 06:15:18 +0200 Subject: Add new C API Change-Id: I717ce3cd7c867c155de149ec56623269d26d0ff7 Signed-off-by: Klement Sekera --- .gitignore | 1 + Makefile | 11 +- src/Makefile.am | 2 + src/configure.ac | 2 +- src/vlibmemory/unix_shared_memory_queue.c | 65 ++ src/vlibmemory/unix_shared_memory_queue.h | 11 +- src/vpp-api/vapi/Makefile.am | 63 ++ src/vpp-api/vapi/libvapiclient.map | 41 + src/vpp-api/vapi/vapi.c | 895 ++++++++++++++++++++++ src/vpp-api/vapi/vapi.h | 285 +++++++ src/vpp-api/vapi/vapi_c_gen.py | 809 ++++++++++++++++++++ src/vpp-api/vapi/vapi_dbg.h | 76 ++ src/vpp-api/vapi/vapi_internal.h | 126 ++++ src/vpp-api/vapi/vapi_json_parser.py | 303 ++++++++ test/Makefile | 6 +- test/ext/Makefile | 17 + test/ext/vapi_test.c | 1152 +++++++++++++++++++++++++++++ test/scripts/test-loop.sh | 14 +- test/test_vapi.py | 78 ++ 19 files changed, 3943 insertions(+), 14 deletions(-) create mode 100644 src/vpp-api/vapi/Makefile.am create mode 100644 src/vpp-api/vapi/libvapiclient.map create mode 100644 src/vpp-api/vapi/vapi.c create mode 100644 src/vpp-api/vapi/vapi.h create mode 100755 src/vpp-api/vapi/vapi_c_gen.py create mode 100644 src/vpp-api/vapi/vapi_dbg.h create mode 100644 src/vpp-api/vapi/vapi_internal.h create mode 100644 src/vpp-api/vapi/vapi_json_parser.py create mode 100644 test/ext/Makefile create mode 100644 test/ext/vapi_test.c create mode 100644 test/test_vapi.py (limited to 'test/scripts') diff --git a/.gitignore b/.gitignore index ba4e104a..5a6266d7 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ /build-root/test-doc/ /build-root/test-cov/ /build-root/python/ +/build-root/vapi_test/ /build-config.mk /dpdk/*.tar.gz /dpdk/*.tar.xz diff --git a/Makefile b/Makefile index c08115d4..c46fa6bb 100644 --- a/Makefile +++ b/Makefile @@ -62,7 +62,7 @@ DEB_DEPENDS = curl build-essential autoconf automake bison libssl-dev ccache DEB_DEPENDS += debhelper dkms git libtool libapr1-dev dh-systemd DEB_DEPENDS += libconfuse-dev git-review exuberant-ctags cscope pkg-config DEB_DEPENDS += lcov chrpath autoconf nasm indent libnuma-dev -DEB_DEPENDS += python-all python-dev python-virtualenv python-pip libffi6 +DEB_DEPENDS += python-all python-dev python-virtualenv python-pip libffi6 check ifeq ($(OS_VERSION_ID),14.04) DEB_DEPENDS += openjdk-8-jdk-headless else ifeq ($(OS_ID)-$(OS_VERSION_ID),debian-8) @@ -76,6 +76,7 @@ RPM_DEPENDS = redhat-lsb glibc-static java-1.8.0-openjdk-devel yum-utils RPM_DEPENDS += apr-devel RPM_DEPENDS += openssl-devel RPM_DEPENDS += numactl-devel +RPM_DEPENDS += check ifeq ($(OS_ID)-$(OS_VERSION_ID),fedora-25) RPM_DEPENDS += python-devel RPM_DEPENDS += python2-virtualenv @@ -101,7 +102,13 @@ endif RPM_SUSE_DEPENDS = autoconf automake bison ccache chrpath distribution-release gcc6 glibc-devel-static RPM_SUSE_DEPENDS += java-1_8_0-openjdk-devel libopenssl-devel libtool lsb-release make openssl-devel -RPM_SUSE_DEPENDS += python-devel python-pip python-rpm-macros shadow nasm libnuma-devel +RPM_SUSE_DEPENDS += python-devel python-pip python-rpm-macros shadow nasm libnuma-devel python3 + +ifeq ($(filter rhel centos,$(OS_ID)),$(OS_ID)) + RPM_DEPENDS += python34 +else + RPM_DEPENDS += python3 +endif ifneq ($(wildcard $(STARTUP_DIR)/startup.conf),) STARTUP_CONF ?= $(STARTUP_DIR)/startup.conf diff --git a/src/Makefile.am b/src/Makefile.am index 41076e0e..7b35e50c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -80,6 +80,8 @@ if ENABLE_JAPI SUBDIRS += vpp-api/java endif +SUBDIRS += vpp-api/vapi + ############################################################################### # API ############################################################################### diff --git a/src/configure.ac b/src/configure.ac index 6b6d9636..2efb23ad 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -3,7 +3,7 @@ LT_INIT AC_CONFIG_AUX_DIR([.]) AM_INIT_AUTOMAKE([subdir-objects]) AM_SILENT_RULES([yes]) -AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/Makefile]) +AC_CONFIG_FILES([Makefile plugins/Makefile vpp-api/python/Makefile vpp-api/java/Makefile vpp-api/vapi/Makefile]) AC_CONFIG_MACRO_DIR([m4]) AC_PROG_CC diff --git a/src/vlibmemory/unix_shared_memory_queue.c b/src/vlibmemory/unix_shared_memory_queue.c index e86edec3..4db4851c 100644 --- a/src/vlibmemory/unix_shared_memory_queue.c +++ b/src/vlibmemory/unix_shared_memory_queue.c @@ -234,6 +234,71 @@ unix_shared_memory_queue_add (unix_shared_memory_queue_t * q, return 0; } +/* + * unix_shared_memory_queue_add2 + */ +int +unix_shared_memory_queue_add2 (unix_shared_memory_queue_t * q, u8 * elem, + u8 * elem2, int nowait) +{ + i8 *tailp; + int need_broadcast = 0; + + if (nowait) + { + /* zero on success */ + if (pthread_mutex_trylock (&q->mutex)) + { + return (-1); + } + } + else + pthread_mutex_lock (&q->mutex); + + if (PREDICT_FALSE (q->cursize + 1 == q->maxsize)) + { + if (nowait) + { + pthread_mutex_unlock (&q->mutex); + return (-2); + } + while (q->cursize + 1 == q->maxsize) + { + (void) pthread_cond_wait (&q->condvar, &q->mutex); + } + } + + tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); + clib_memcpy (tailp, elem, q->elsize); + + q->tail++; + q->cursize++; + + if (q->tail == q->maxsize) + q->tail = 0; + + need_broadcast = (q->cursize == 1); + + tailp = (i8 *) (&q->data[0] + q->elsize * q->tail); + clib_memcpy (tailp, elem2, q->elsize); + + q->tail++; + q->cursize++; + + if (q->tail == q->maxsize) + q->tail = 0; + + if (need_broadcast) + { + (void) pthread_cond_broadcast (&q->condvar); + if (q->signal_when_queue_non_empty) + kill (q->consumer_pid, q->signal_when_queue_non_empty); + } + pthread_mutex_unlock (&q->mutex); + + return 0; +} + /* * unix_shared_memory_queue_sub */ diff --git a/src/vlibmemory/unix_shared_memory_queue.h b/src/vlibmemory/unix_shared_memory_queue.h index 13800065..27de3218 100644 --- a/src/vlibmemory/unix_shared_memory_queue.h +++ b/src/vlibmemory/unix_shared_memory_queue.h @@ -21,7 +21,6 @@ #define included_unix_shared_memory_queue_h #include -#include typedef struct _unix_shared_memory_queue { @@ -43,10 +42,12 @@ unix_shared_memory_queue_t *unix_shared_memory_queue_init (int nels, int signal_when_queue_non_empty); void unix_shared_memory_queue_free (unix_shared_memory_queue_t * q); -int unix_shared_memory_queue_add (unix_shared_memory_queue_t * q, - u8 * elem, int nowait); -int unix_shared_memory_queue_sub (unix_shared_memory_queue_t * q, - u8 * elem, int nowait); +int unix_shared_memory_queue_add (unix_shared_memory_queue_t * q, u8 * elem, + int nowait); +int unix_shared_memory_queue_add2 (unix_shared_memory_queue_t * q, u8 * elem, + u8 * elem2, int nowait); +int unix_shared_memory_queue_sub (unix_shared_memory_queue_t * q, u8 * elem, + int nowait); void unix_shared_memory_queue_lock (unix_shared_memory_queue_t * q); void unix_shared_memory_queue_unlock (unix_shared_memory_queue_t * q); int unix_shared_memory_queue_is_full (unix_shared_memory_queue_t * q); diff --git a/src/vpp-api/vapi/Makefile.am b/src/vpp-api/vapi/Makefile.am new file mode 100644 index 00000000..ce681c38 --- /dev/null +++ b/src/vpp-api/vapi/Makefile.am @@ -0,0 +1,63 @@ +# 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. + +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_LDFLAGS = -shared -avoid-version -rpath /none -no-undefined + +bin_PROGRAMS = +noinst_LTLIBRARIES = +CLEANDIRS = + +%.api.vapi.h: %.api.json vapi_c_gen.py + @echo " VAPI C GEN $< " $@ ; \ + mkdir -p `dirname $@` ; \ + $(top_srcdir)/vpp-api/vapi/vapi_c_gen.py $< + +%.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)) + +vapi.c: $(BUILT_SOURCES) + +JSON_FILES = $(wildcard *.api.json) + + +lib_LTLIBRARIES = libvapiclient.la + +libvapiclient_la_SOURCES = vapi.c + +libvapiclient_la_LIBADD = -lpthread -lm -lrt \ + $(top_builddir)/libvppinfra.la \ + $(top_builddir)/libvlibmemoryclient.la \ + $(top_builddir)/libsvm.la + +libvapiclient_la_LDFLAGS = \ + -Wl,-L$(top_builddir)/.libs,--whole-archive,--no-whole-archive \ + -Wl,--version-script=$(srcdir)/libvapiclient.map,-lrt + +libvapiclient_la_CPPFLAGS = -I. -I$(top_builddir)/vpp-api/vapi + +nobase_include_HEADERS = ${top_srcdir}/vpp-api/client/vppapiclient.h \ + vapi.h \ + vapi_dbg.h \ + vapi_internal.h \ + $(patsubst %.api.json,%.api.vapi.h,$(JSON_FILES)) + +# vi:syntax=automake diff --git a/src/vpp-api/vapi/libvapiclient.map b/src/vpp-api/vapi/libvapiclient.map new file mode 100644 index 00000000..53733002 --- /dev/null +++ b/src/vpp-api/vapi/libvapiclient.map @@ -0,0 +1,41 @@ + +VAPICLIENT_17.07 { + global: + vapi_msg_alloc; + vapi_msg_free; + vapi_ctx_alloc; + vapi_ctx_free; + vapi_is_msg_available; + vapi_connect; + vapi_disconnect; + vapi_get_fd; + vapi_send; + vapi_send2; + vapi_recv; + vapi_wait; + vapi_dispatch_one; + vapi_dispatch; + vapi_set_event_cb; + vapi_clear_event_cb; + vapi_set_generic_event_cb; + vapi_clear_generic_event_cb; + vapi_get_client_index; + vapi_register_msg; + vapi_get_client_index; + vapi_is_nonblocking; + vapi_requests_full; + vapi_gen_req_context; + vapi_producer_lock; + vapi_send_with_control_ping; + vapi_store_request; + vapi_is_nonblocking; + vapi_producer_unlock; + vapi_lookup_vl_msg_id; + vapi_lookup_vapi_msg_id_t; + vapi_msg_is_with_context; + vapi_get_context_offset; + vapi_msg_id_control_ping; + vapi_msg_id_control_ping_reply; + + local: *; +}; diff --git a/src/vpp-api/vapi/vapi.c b/src/vpp-api/vapi/vapi.c new file mode 100644 index 00000000..b9c81a13 --- /dev/null +++ b/src/vpp-api/vapi/vapi.c @@ -0,0 +1,895 @@ +/* + *------------------------------------------------------------------ + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +/* we need to use control pings for some stuff and because we're forced to put + * the code in headers, we need a way to be able to grab the ids of these + * messages - so declare them here as extern */ +vapi_msg_id_t vapi_msg_id_control_ping = 0; +vapi_msg_id_t vapi_msg_id_control_ping_reply = 0; + +struct +{ + size_t count; + vapi_message_desc_t **msgs; + size_t max_len_name_with_crc; +} __vapi_metadata; + +typedef struct +{ + u32 context; + vapi_cb_t callback; + void *callback_ctx; + bool is_dump; +} vapi_req_t; + +static const u32 context_counter_mask = (1 << 31); + +typedef struct +{ + vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id, + void *payload); + void *ctx; +} vapi_generic_cb_with_ctx; + +typedef struct +{ + vapi_error_e (*cb) (vapi_ctx_t ctx, void *callback_ctx, void *payload); + void *ctx; +} vapi_event_cb_with_ctx; + +struct vapi_ctx_s +{ + vapi_mode_e mode; + int requests_size; /* size of the requests array (circular queue) */ + int requests_start; /* index of first request */ + int requests_count; /* number of used slots */ + vapi_req_t *requests; + u32 context_counter; + vapi_generic_cb_with_ctx generic_cb; + vapi_event_cb_with_ctx *event_cbs; + u16 *vapi_msg_id_t_to_vl_msg_id; + u16 vl_msg_id_max; + vapi_msg_id_t *vl_msg_id_to_vapi_msg_t; + bool connected; + pthread_mutex_t requests_mutex; +}; + +u32 +vapi_gen_req_context (vapi_ctx_t ctx) +{ + ++ctx->context_counter; + ctx->context_counter %= context_counter_mask; + return ctx->context_counter | context_counter_mask; +} + +size_t +vapi_get_request_count (vapi_ctx_t ctx) +{ + return ctx->requests_count; +} + +bool +vapi_requests_full (vapi_ctx_t ctx) +{ + return (ctx->requests_count == ctx->requests_size); +} + +static bool +vapi_requests_empty (vapi_ctx_t ctx) +{ + return (0 == ctx->requests_count); +} + +static int +vapi_requests_end (vapi_ctx_t ctx) +{ + return (ctx->requests_start + ctx->requests_count) % ctx->requests_size; +} + +void +vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, + vapi_cb_t callback, void *callback_ctx) +{ + assert (!vapi_requests_full (ctx)); + /* if the mutex is not held, bad things will happen */ + assert (0 != pthread_mutex_trylock (&ctx->requests_mutex)); + const int requests_end = vapi_requests_end (ctx); + vapi_req_t *slot = &ctx->requests[requests_end]; + slot->is_dump = is_dump; + slot->context = context; + slot->callback = callback; + slot->callback_ctx = callback_ctx; + VAPI_DBG ("stored@%d: context:%x (start is @%d)", requests_end, context, + ctx->requests_start); + ++ctx->requests_count; + assert (!vapi_requests_empty (ctx)); +} + +#if VAPI_DEBUG_ALLOC +struct to_be_freed_s; +struct to_be_freed_s +{ + void *v; + struct to_be_freed_s *next; +}; + +static struct to_be_freed_s *to_be_freed = NULL; + +void +vapi_add_to_be_freed (void *v) +{ + struct to_be_freed_s *prev = NULL; + struct to_be_freed_s *tmp; + tmp = to_be_freed; + while (tmp && tmp->v) + { + prev = tmp; + tmp = tmp->next; + } + if (!tmp) + { + if (!prev) + { + tmp = to_be_freed = calloc (1, sizeof (*to_be_freed)); + } + else + { + tmp = prev->next = calloc (1, sizeof (*to_be_freed)); + } + } + VAPI_DBG ("To be freed %p", v); + tmp->v = v; +} + +void +vapi_trace_free (void *v) +{ + struct to_be_freed_s *tmp = to_be_freed; + while (tmp && tmp->v != v) + { + tmp = tmp->next; + } + if (tmp && tmp->v == v) + { + VAPI_DBG ("Freed %p", v); + tmp->v = NULL; + } + else + { + VAPI_ERR ("Trying to free untracked pointer %p", v); + abort (); + } +} + +void +vapi_to_be_freed_validate () +{ + struct to_be_freed_s *tmp = to_be_freed; + while (tmp) + { + if (tmp->v) + { + VAPI_ERR ("Unfreed msg %p!", tmp->v); + } + tmp = tmp->next; + } +} + +#endif + +void * +vapi_msg_alloc (vapi_ctx_t ctx, size_t size) +{ + if (!ctx->connected) + { + return NULL; + } + void *rv = vl_msg_api_alloc_or_null (size); + return rv; +} + +void +vapi_msg_free (vapi_ctx_t ctx, void *msg) +{ + if (!ctx->connected) + { + return; + } +#if VAPI_DEBUG_ALLOC + vapi_trace_free (msg); +#endif + vl_msg_api_free (msg); +} + +vapi_error_e +vapi_ctx_alloc (vapi_ctx_t * result) +{ + vapi_ctx_t ctx = calloc (1, sizeof (struct vapi_ctx_s)); + if (!ctx) + { + return VAPI_ENOMEM; + } + ctx->context_counter = 0; + ctx->vapi_msg_id_t_to_vl_msg_id = + malloc (__vapi_metadata.count * + sizeof (*ctx->vapi_msg_id_t_to_vl_msg_id)); + if (!ctx->vapi_msg_id_t_to_vl_msg_id) + { + goto fail; + } + ctx->event_cbs = calloc (__vapi_metadata.count, sizeof (*ctx->event_cbs)); + if (!ctx->event_cbs) + { + goto fail; + } + pthread_mutex_init (&ctx->requests_mutex, NULL); + *result = ctx; + return VAPI_OK; +fail: + vapi_ctx_free (ctx); + return VAPI_ENOMEM; +} + +void +vapi_ctx_free (vapi_ctx_t ctx) +{ + assert (!ctx->connected); + free (ctx->requests); + free (ctx->vapi_msg_id_t_to_vl_msg_id); + free (ctx->event_cbs); + free (ctx->vl_msg_id_to_vapi_msg_t); + pthread_mutex_destroy (&ctx->requests_mutex); + free (ctx); +} + +bool +vapi_is_msg_available (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + return vapi_lookup_vl_msg_id (ctx, id) != UINT16_MAX; +} + +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) +{ + if (response_queue_size <= 0 || max_outstanding_requests <= 0) + { + return VAPI_EINVAL; + } + ctx->requests_size = max_outstanding_requests; + const size_t size = ctx->requests_size * sizeof (*ctx->requests); + void *tmp = realloc (ctx->requests, size); + if (!tmp) + { + return VAPI_ENOMEM; + } + ctx->requests = tmp; + memset (ctx->requests, 0, size); + ctx->requests_start = ctx->requests_count = 0; + if (chroot_prefix) + { + VAPI_DBG ("set memory root path `%s'", chroot_prefix); + vl_set_memory_root_path ((char *) chroot_prefix); + } + static char api_map[] = "/vpe-api"; + VAPI_DBG ("client api map `%s'", api_map); + if ((vl_client_api_map (api_map)) < 0) + { + return VAPI_EMAP_FAIL; + } + VAPI_DBG ("connect client `%s'", name); + if (vl_client_connect ((char *) name, 0, response_queue_size) < 0) + { + vl_client_api_unmap (); + return VAPI_ECON_FAIL; + } +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("start probing messages"); +#endif + int rv; + int i; + for (i = 0; i < __vapi_metadata.count; ++i) + { + vapi_message_desc_t *m = __vapi_metadata.msgs[i]; + u8 scratch[m->name_with_crc_len + 1]; + memcpy (scratch, m->name_with_crc, m->name_with_crc_len + 1); + u32 id = vl_api_get_msg_index (scratch); + if (~0 != id) + { + if (id > UINT16_MAX) + { + VAPI_ERR ("Returned vl_msg_id `%u' > UINT16MAX `%u'!", id, + UINT16_MAX); + rv = VAPI_EINVAL; + goto fail; + } + if (id > ctx->vl_msg_id_max) + { + vapi_msg_id_t *tmp = realloc (ctx->vl_msg_id_to_vapi_msg_t, + sizeof + (*ctx->vl_msg_id_to_vapi_msg_t) * + (id + 1)); + if (!tmp) + { + rv = VAPI_ENOMEM; + goto fail; + } + ctx->vl_msg_id_to_vapi_msg_t = tmp; + ctx->vl_msg_id_max = id; + } + ctx->vl_msg_id_to_vapi_msg_t[id] = m->id; + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = id; +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("Message `%s' has vl_msg_id `%u'", m->name_with_crc, + (unsigned) id); +#endif + } + else + { + ctx->vapi_msg_id_t_to_vl_msg_id[m->id] = UINT16_MAX; + VAPI_DBG ("Message `%s' not available", m->name_with_crc); + } + } +#if VAPI_DEBUG_CONNECT + VAPI_DBG ("finished probing messages"); +#endif + if (!vapi_is_msg_available (ctx, vapi_msg_id_control_ping) || + !vapi_is_msg_available (ctx, vapi_msg_id_control_ping_reply)) + { + VAPI_ERR + ("control ping or control ping reply not available, cannot connect"); + rv = VAPI_EINCOMPATIBLE; + goto fail; + } + ctx->mode = mode; + ctx->connected = true; + return VAPI_OK; +fail: + vl_client_disconnect (); + vl_client_api_unmap (); + return rv; +} + +vapi_error_e +vapi_disconnect (vapi_ctx_t ctx) +{ + if (!ctx->connected) + { + return VAPI_EINVAL; + } + vl_client_disconnect (); + vl_client_api_unmap (); +#if VAPI_DEBUG_ALLOC + vapi_to_be_freed_validate (); +#endif + ctx->connected = false; + return VAPI_OK; +} + +vapi_error_e +vapi_get_fd (vapi_ctx_t ctx, int *fd) +{ + return VAPI_ENOTSUP; +} + +vapi_error_e +vapi_send (vapi_ctx_t ctx, void *msg) +{ + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg || !ctx->connected) + { + rv = VAPI_EINVAL; + goto out; + } + int tmp; + unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue; +#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 ("send msg %u[%s]", msgid, __vapi_metadata.msgs[id]->name); + } + else + { + VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + } + } + else + { + VAPI_DBG ("send msg %u[UNKNOWN]", msgid); + } +#endif + tmp = unix_shared_memory_queue_add (q, (u8 *) & msg, + VAPI_MODE_BLOCKING == + ctx->mode ? 0 : 1); + if (tmp < 0) + { + rv = VAPI_EAGAIN; + } +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +vapi_error_e +vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2) +{ + vapi_error_e rv = VAPI_OK; + if (!ctx || !msg1 || !msg2 || !ctx->connected) + { + rv = VAPI_EINVAL; + goto out; + } + unix_shared_memory_queue_t *q = api_main.shmem_hdr->vl_input_queue; +#if VAPI_DEBUG + unsigned msgid1 = be16toh (*(u16 *) msg1); + unsigned msgid2 = be16toh (*(u16 *) msg2); + const char *name1 = "UNKNOWN"; + const char *name2 = "UNKNOWN"; + if (msgid1 <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid1]; + if (id < __vapi_metadata.count) + { + name1 = __vapi_metadata.msgs[id]->name; + } + } + if (msgid2 <= ctx->vl_msg_id_max) + { + vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[msgid2]; + if (id < __vapi_metadata.count) + { + name2 = __vapi_metadata.msgs[id]->name; + } + } + VAPI_DBG ("send two: %u[%s], %u[%s]", msgid1, name1, msgid2, name2); +#endif + int tmp = unix_shared_memory_queue_add2 (q, (u8 *) & msg1, (u8 *) & msg2, + VAPI_MODE_BLOCKING == + ctx->mode ? 0 : 1); + if (tmp < 0) + { + rv = VAPI_EAGAIN; + } +out: + VAPI_DBG ("vapi_send() rv = %d", rv); + return rv; +} + +vapi_error_e +vapi_recv (vapi_ctx_t ctx, void **msg, size_t * msg_size) +{ + if (!ctx || !ctx->connected || !msg || !msg_size) + { + return VAPI_EINVAL; + } + vapi_error_e rv = VAPI_OK; + api_main_t *am = &api_main; + uword data; + + if (am->our_pid == 0) + { + return VAPI_EINVAL; + } + + unix_shared_memory_queue_t *q = am->vl_input_queue; + VAPI_DBG ("doing shm queue sub"); + int tmp = unix_shared_memory_queue_sub (q, (u8 *) & data, 0); + if (tmp == 0) + { +#if VAPI_DEBUG_ALLOC + vapi_add_to_be_freed ((void *) data); +#endif + msgbuf_t *msgbuf = + (msgbuf_t *) ((u8 *) data - offsetof (msgbuf_t, data)); + if (!msgbuf->data_len) + { + vapi_msg_free (ctx, (u8 *) data); + return VAPI_EAGAIN; + } + *msg = (u8 *) data; + *msg_size = ntohl (msgbuf->data_len); + VAPI_DBG ("recv msg %p", *msg); + } + else + { + rv = VAPI_EAGAIN; + } + return rv; +} + +vapi_error_e +vapi_wait (vapi_ctx_t ctx, vapi_wait_mode_e mode) +{ + /* FIXME */ + return VAPI_ENOTSUP; +} + +static vapi_error_e +vapi_dispatch_response (vapi_ctx_t ctx, vapi_msg_id_t id, + u32 context, void *msg) +{ + int mrv; + if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv)); + return VAPI_MUTEX_FAILURE; + } + int tmp = ctx->requests_start; + const int requests_end = vapi_requests_end (ctx); + while (ctx->requests[tmp].context != context && tmp != requests_end) + { + ++tmp; + if (tmp == ctx->requests_size) + { + tmp = 0; + } + } + VAPI_DBG ("dispatch, search from %d, %s at %d", ctx->requests_start, + ctx->requests[tmp].context == context ? "matched" : "stopped", + tmp); + vapi_error_e rv = VAPI_OK; + if (ctx->requests[tmp].context == context) + { + while (ctx->requests_start != tmp) + { + VAPI_ERR ("No response to req with context=%u", + (unsigned) ctx->requests[tmp].context); + ctx->requests[ctx->requests_start].callback (ctx, + ctx->requests + [ctx-> + requests_start].callback_ctx, + VAPI_ENORESP, true, + NULL); + memset (&ctx->requests[ctx->requests_start], 0, + sizeof (ctx->requests[ctx->requests_start])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + } + // now ctx->requests_start == tmp + int payload_offset = vapi_get_payload_offset (id); + void *payload = ((u8 *) msg) + payload_offset; + bool is_last = true; + if (ctx->requests[tmp].is_dump) + { + if (vapi_msg_id_control_ping_reply == id) + { + payload = NULL; + } + else + { + is_last = false; + } + } + if (payload_offset != -1) + { + rv = + ctx->requests[tmp].callback (ctx, ctx->requests[tmp].callback_ctx, + VAPI_OK, is_last, payload); + } + else + { + /* this is a message without payload, so bend the callback a little + */ + rv = + ((vapi_error_e (*)(vapi_ctx_t, void *, vapi_error_e, bool)) + ctx->requests[tmp].callback) (ctx, + ctx->requests[tmp].callback_ctx, + VAPI_OK, is_last); + } + if (is_last) + { + memset (&ctx->requests[ctx->requests_start], 0, + sizeof (ctx->requests[ctx->requests_start])); + ++ctx->requests_start; + --ctx->requests_count; + if (ctx->requests_start == ctx->requests_size) + { + ctx->requests_start = 0; + } + } + VAPI_DBG ("after dispatch, req start = %d, end = %d, count = %d", + ctx->requests_start, requests_end, ctx->requests_count); + } + if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv, + strerror (mrv)); + abort (); /* this really shouldn't happen */ + } + return rv; +} + +static vapi_error_e +vapi_dispatch_event (vapi_ctx_t ctx, vapi_msg_id_t id, void *msg) +{ + if (ctx->event_cbs[id].cb) + { + return ctx->event_cbs[id].cb (ctx, ctx->event_cbs[id].ctx, msg); + } + else if (ctx->generic_cb.cb) + { + return ctx->generic_cb.cb (ctx, ctx->generic_cb.ctx, id, msg); + } + else + { + VAPI_DBG + ("No handler/generic handler for msg id %u[%s], message ignored", + (unsigned) id, __vapi_metadata.msgs[id]->name); + } + return VAPI_OK; +} + +static bool +vapi_msg_is_with_context (vapi_msg_id_t id) +{ + assert (id <= __vapi_metadata.count); + return __vapi_metadata.msgs[id]->has_context; +} + +vapi_error_e +vapi_dispatch_one (vapi_ctx_t ctx) +{ + VAPI_DBG ("vapi_dispatch_one()"); + void *msg; + size_t size; + vapi_error_e rv = vapi_recv (ctx, &msg, &size); + if (VAPI_OK != rv) + { + VAPI_DBG ("vapi_recv failed with rv=%d", rv); + return rv; + } + u16 vpp_id = be16toh (*(u16 *) msg); + if (vpp_id > ctx->vl_msg_id_max) + { + VAPI_ERR ("Unknown msg ID received, id `%u', out of range <0,%u>", + (unsigned) vpp_id, (unsigned) ctx->vl_msg_id_max); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + if (~0 == (unsigned) ctx->vl_msg_id_to_vapi_msg_t[vpp_id]) + { + VAPI_ERR ("Unknown msg ID received, id `%u' marked as not supported", + (unsigned) vpp_id); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + const vapi_msg_id_t id = ctx->vl_msg_id_to_vapi_msg_t[vpp_id]; + const size_t expect_size = vapi_get_message_size (id); + if (size < expect_size) + { + VAPI_ERR + ("Invalid msg received, unexpected size `%zu' < expected min `%zu'", + size, expect_size); + vapi_msg_free (ctx, msg); + return VAPI_EINVAL; + } + u32 context; + vapi_get_swap_to_host_func (id) (msg); + if (vapi_msg_is_with_context (id)) + { + context = *(u32 *) (((u8 *) msg) + vapi_get_context_offset (id)); + /* is this a message originating from VAPI? */ + VAPI_DBG ("dispatch, context is %x", context); + if (context & context_counter_mask) + { + rv = vapi_dispatch_response (ctx, id, context, msg); + goto done; + } + } + rv = vapi_dispatch_event (ctx, id, msg); + +done: + vapi_msg_free (ctx, msg); + return rv; +} + +vapi_error_e +vapi_dispatch (vapi_ctx_t ctx) +{ + vapi_error_e rv = VAPI_OK; + while (!vapi_requests_empty (ctx)) + { + rv = vapi_dispatch_one (ctx); + if (VAPI_OK != rv) + { + return rv; + } + } + return rv; +} + +void +vapi_set_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id, + vapi_event_cb callback, void *callback_ctx) +{ + vapi_event_cb_with_ctx *c = &ctx->event_cbs[id]; + c->cb = callback; + c->ctx = callback_ctx; +} + +void +vapi_clear_event_cb (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + vapi_set_event_cb (ctx, id, NULL, NULL); +} + +void +vapi_set_generic_event_cb (vapi_ctx_t ctx, vapi_generic_event_cb callback, + void *callback_ctx) +{ + ctx->generic_cb.cb = callback; + ctx->generic_cb.ctx = callback_ctx; +} + +void +vapi_clear_generic_event_cb (vapi_ctx_t ctx) +{ + ctx->generic_cb.cb = NULL; + ctx->generic_cb.ctx = NULL; +} + +u16 +vapi_lookup_vl_msg_id (vapi_ctx_t ctx, vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return ctx->vapi_msg_id_t_to_vl_msg_id[id]; +} + +int +vapi_get_client_index (vapi_ctx_t ctx) +{ + return api_main.my_client_index; +} + +bool +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) +{ + return ctx->requests_size - 1; +} + +int +vapi_get_payload_offset (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->payload_offset; +} + +void (*vapi_get_swap_to_host_func (vapi_msg_id_t id)) (void *msg) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->swap_to_host; +} + +void (*vapi_get_swap_to_be_func (vapi_msg_id_t id)) (void *msg) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->swap_to_be; +} + +size_t +vapi_get_message_size (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->size; +} + +size_t +vapi_get_context_offset (vapi_msg_id_t id) +{ + assert (id < __vapi_metadata.count); + return __vapi_metadata.msgs[id]->context_offset; +} + +vapi_msg_id_t +vapi_register_msg (vapi_message_desc_t * msg) +{ + int i = 0; + for (i = 0; i < __vapi_metadata.count; ++i) + { + if (!strcmp + (msg->name_with_crc, __vapi_metadata.msgs[i]->name_with_crc)) + { + /* this happens if somebody is linking together several objects while + * using the static inline headers, just fill in the already + * assigned id here so that all the objects are in sync */ + msg->id = __vapi_metadata.msgs[i]->id; + return msg->id; + } + } + vapi_msg_id_t id = __vapi_metadata.count; + ++__vapi_metadata.count; + __vapi_metadata.msgs = + realloc (__vapi_metadata.msgs, + sizeof (*__vapi_metadata.msgs) * __vapi_metadata.count); + __vapi_metadata.msgs[id] = msg; + size_t s = strlen (msg->name_with_crc); + if (s > __vapi_metadata.max_len_name_with_crc) + { + __vapi_metadata.max_len_name_with_crc = s; + } + msg->id = id; + return id; +} + +vapi_error_e +vapi_producer_lock (vapi_ctx_t ctx) +{ + int mrv; + if (0 != (mrv = pthread_mutex_lock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_lock() failed, rv=%d:%s", mrv, strerror (mrv)); + (void) mrv; /* avoid warning if the above debug is not enabled */ + return VAPI_MUTEX_FAILURE; + } + return VAPI_OK; +} + +vapi_error_e +vapi_producer_unlock (vapi_ctx_t ctx) +{ + int mrv; + if (0 != (mrv = pthread_mutex_unlock (&ctx->requests_mutex))) + { + VAPI_DBG ("pthread_mutex_unlock() failed, rv=%d:%s", mrv, + strerror (mrv)); + (void) mrv; /* avoid warning if the above debug is not enabled */ + return VAPI_MUTEX_FAILURE; + } + return VAPI_OK; +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi.h b/src/vpp-api/vapi/vapi.h new file mode 100644 index 00000000..1e1d567a --- /dev/null +++ b/src/vpp-api/vapi/vapi.h @@ -0,0 +1,285 @@ +/* + *------------------------------------------------------------------ + * 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 vpp_api_h_included +#define vpp_api_h_included + +#include +#include +#include + +/** + * @file vapi.h + * + * common vpp api C declarations + * + * This file declares the common C API functions. These include connect, + * disconnect and utility functions as well as the low-level vapi_send and + * vapi_recv API. This is only the transport layer. + * + * Message formats and higher-level APIs are generated by running the + * vapi_c_gen.py script (which is run for in-tree APIs as part of the build + * 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; + +/** + * @brief allocate vapi message of given size + * + * @note message must be freed by vapi_msg_free if not consumed by vapi_send + * call + * + * @param ctx opaque vapi context + * + * @return pointer to message or NULL if out of memory + */ +void *vapi_msg_alloc (vapi_ctx_t ctx, size_t size); + +/** + * @brief free a vapi message + * + * @note messages received by vapi_recv must be freed when no longer needed + * + * @param ctx opaque vapi context + * @param msg message to be freed + */ +void vapi_msg_free (vapi_ctx_t ctx, void *msg); + +/** + * @brief allocate vapi context + * + * @param[out] pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_ctx_alloc (vapi_ctx_t * result); + +/** + * @brief free vapi context + */ +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); + +/** + * @brief connect to vpp + * + * @param ctx opaque vapi context, must be allocated using vapi_ctx_alloc first + * @param name application name + * @param chroot_prefix shared memory prefix + * @param max_outstanding_requests max number of outstanding requests queued + * @param response_queue_size size of the response queue + * @param mode mode of operation - blocking or nonblocking + * + * @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); + +/** + * @brief disconnect from vpp + * + * @param ctx opaque vapi context + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_disconnect (vapi_ctx_t ctx); + +/** + * @brief get event file descriptor + * + * @note this file descriptor becomes readable when messages (from vpp) + * are waiting in queue + * + * @param ctx opaque vapi context + * @param[out] fd pointer to result variable + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_get_fd (vapi_ctx_t ctx, int *fd); + +/** + * @brief low-level api for sending messages to vpp + * + * @note it is not recommended to use this api directly, use generated api + * instead + * + * @param ctx opaque vapi context + * @param msg message to send + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_send (vapi_ctx_t ctx, void *msg); + +/** + * @brief low-level api for atomically sending two messages to vpp - either + * both messages are sent or neither one is + * + * @note it is not recommended to use this api directly, use generated api + * instead + * + * @param ctx opaque vapi context + * @param msg1 first message to send + * @param msg2 second message to send + * + * @return VAPI_OK on success, other error code on error + */ +vapi_error_e vapi_send2 (vapi_ctx_t ctx, void *msg1, void *msg2); + +/** + * @brief low-level api for reading messages from vpp + * + * @note it is not recommended to use this api directly, use generated api + * instead + * + * @param ctx opaque vapi context + * @param[out] msg pointer to result variable containing message + * @param[out] msg_size pointer to result variable containing message size + * + * @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); + +/** + * @brief wait for connection to become readable or writable + * + * @param ctx opaque vapi context + * @param mode type of property to wait for - readability, writability or both + * + * @return VAPI_OK on success, other error code on error + */ +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); + +/** + * @brief loop vapi_dispatch_one until responses to all currently outstanding + * requests have been received and their callbacks called + * + * @note the dispatch loop is interrupted if any error is encountered or + * returned from the callback, in which case this error is returned as the + * result of vapi_dispatch. In this case it might be necessary to call dispatch + * again to process the remaining messages. Returning VAPI_EUSER from + * a callback allows the user to break the dispatch loop (and distinguish + * this case in the calling code from other failures). VAPI never returns + * VAPI_EUSER on its own. + * + * @return VAPI_OK on success, other error code on error + */ +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); + +/** + * @brief set event callback to call when message with given id is dispatched + * + * @param ctx opaque vapi context + * @param id message id + * @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); + +/** + * @brief clear event callback for given message id + * + * @param ctx opaque vapi context + * @param id message 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); +/** + * @brief set generic event callback + * + * @note this callback is called by dispatch if no message-type specific + * callback is set (so it's a fallback callback) + * + * @param ctx opaque vapi context + * @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); + +/** + * @brief clear generic event callback + * + * @param ctx opaque vapi context + */ +void vapi_clear_generic_event_cb (vapi_ctx_t ctx); + +#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 new file mode 100755 index 00000000..2bc1eef8 --- /dev/null +++ b/src/vpp-api/vapi/vapi_c_gen.py @@ -0,0 +1,809 @@ +#!/usr/bin/env python3 + +import argparse +import os +import sys +import logging +from vapi_json_parser import Field, Struct, Message, JsonParser,\ + SimpleType, StructType + + +class CField(Field): + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + super().__init__(field_name, field_type, array_len, nelem_field) + + def get_c_def(self): + if self.len is not None: + return "%s %s[%d]" % (self.type.get_c_name(), self.name, self.len) + else: + return "%s %s" % (self.type.get_c_name(), self.name) + + 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 } }"\ + " while(0);" % ( + self.len, + self.type.get_swap_to_be_code(struct, "%s[i]" % var)) + else: + if self.nelem_field.needs_byte_swap(): + nelem_field = "%s(%s%s)" % ( + self.nelem_field.type.get_swap_to_host_func_name(), + struct, self.nelem_field.name) + else: + nelem_field = "%s%s" % (struct, self.nelem_field.name) + return ( + "do { int i; for (i = 0; i < %s; ++i) { %s } }" + " while(0);" % + (nelem_field, self.type.get_swap_to_be_code( + struct, "%s[i]" % var))) + return self.type.get_swap_to_be_code(struct, "%s" % var) + + 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 } }"\ + " 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 } }" + " while(0);" % + (struct, self.nelem_field.name, + self.type.get_swap_to_host_code( + struct, "%s[i]" % var))) + return self.type.get_swap_to_host_code(struct, "%s" % var) + + def needs_byte_swap(self): + return self.type.needs_byte_swap() + + +class CStruct(Struct): + def __init__(self, name, fields): + super().__init__(name, fields) + + def get_c_def(self): + return "\n".join([ + "typedef struct __attribute__((__packed__)) {", + "%s;" % ";\n".join([" %s" % x.get_c_def() + for x in self.fields]), + "} %s;" % self.get_c_name()]) + + +class CSimpleType (SimpleType): + + swap_to_be_dict = { + 'i16': 'htobe16', 'u16': 'htobe16', + 'i32': 'htobe32', 'u32': 'htobe32', + 'i64': 'htobe64', 'u64': 'htobe64', + } + + swap_to_host_dict = { + 'i16': 'be16toh', 'u16': 'be16toh', + 'i32': 'be32toh', 'u32': 'be32toh', + 'i64': 'be64toh', 'u64': 'be64toh', + } + + def __init__(self, name): + super().__init__(name) + + def get_c_name(self): + return self.name + + def get_swap_to_be_func_name(self): + return self.swap_to_be_dict[self.name] + + def get_swap_to_host_func_name(self): + return self.swap_to_host_dict[self.name] + + def get_swap_to_be_code(self, struct, var): + x = "%s%s" % (struct, var) + return "%s = %s(%s);" % (x, self.get_swap_to_be_func_name(), x) + + def get_swap_to_host_code(self, struct, var): + x = "%s%s" % (struct, var) + return "%s = %s(%s);" % (x, self.get_swap_to_host_func_name(), x) + + def needs_byte_swap(self): + try: + self.get_swap_to_host_func_name() + return True + except: + pass + return False + + +class CStructType (StructType, CStruct): + def __init__(self, definition, typedict, field_class): + super().__init__(definition, typedict, field_class) + + def get_c_name(self): + return "vapi_type_%s" % self.name + + def get_swap_to_be_func_name(self): + return "%s_hton" % self.get_c_name() + + def get_swap_to_host_func_name(self): + return "%s_ntoh" % self.get_c_name() + + def get_swap_to_be_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_be_func_name(), self.get_c_name()) + + def get_swap_to_be_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_to_be_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_be_code("msg->", "%s" % p.name) + for p in self.fields if p.needs_byte_swap()]), + ) + + def get_swap_to_host_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_host_func_name(), self.get_c_name()) + + def get_swap_to_host_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_to_host_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_host_code("msg->", "%s" % p.name) + for p in self.fields if p.needs_byte_swap()]), + ) + + def get_swap_to_be_code(self, struct, var): + return "%s(&%s%s);" % (self.get_swap_to_be_func_name(), struct, var) + + def get_swap_to_host_code(self, struct, var): + return "%s(&%s%s);" % (self.get_swap_to_host_func_name(), struct, var) + + def needs_byte_swap(self): + for f in self.fields: + if f.needs_byte_swap(): + return True + return False + + +class CMessage (Message): + 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) + self.payload_members = [ + " %s" % p.get_c_def() + for p in self.fields + if p.type != self.header + ] + + def has_payload(self): + return len(self.payload_members) > 0 + + def get_msg_id_name(self): + return "vapi_msg_id_%s" % self.name + + def get_c_name(self): + return "vapi_msg_%s" % self.name + + def get_payload_struct_name(self): + return "vapi_payload_%s" % self.name + + def get_alloc_func_vla_field_length_name(self, field): + return "%s_array_size" % field.name + + def get_alloc_func_name(self): + return "vapi_alloc_%s" % self.name + + 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])) + + def get_alloc_func_def(self): + extra = [] + if self.header.has_field('client_index'): + extra.append( + " msg->header.client_index = vapi_get_client_index(ctx);") + if self.header.has_field('context'): + extra.append(" msg->header.context = 0;") + return "\n".join([ + "%s" % self.get_alloc_func_decl(), + "{", + " %s *msg = NULL;" % self.get_c_name(), + " const size_t size = sizeof(%s)%s;" % ( + self.get_c_name(), + "".join([ + " + sizeof(msg->payload.%s[0]) * %s" % ( + f.name, + self.get_alloc_func_vla_field_length_name(f)) + for f in self.fields + if f.nelem_field is not None + ])), + " msg = vapi_msg_alloc(ctx, size);", + " if (!msg) {", + " return NULL;", + " }", + ] + extra + [ + " msg->header._vl_msg_id = vapi_lookup_vl_msg_id(ctx, %s);" % + self.get_msg_id_name(), + "\n".join([" msg->payload.%s = %s;" % ( + f.nelem_field.name, + self.get_alloc_func_vla_field_length_name(f)) + for f in self.fields + if f.nelem_field is not None]), + " return msg;", + "}"]) + + def get_calc_msg_size_func_name(self): + return "vapi_calc_%s_msg_size" % self.name + + def get_calc_msg_size_func_decl(self): + return "uword %s(%s *msg)" % ( + self.get_calc_msg_size_func_name(), + self.get_c_name()) + + def get_calc_msg_size_func_def(self): + return "\n".join([ + "%s" % self.get_calc_msg_size_func_decl(), + "{", + " return sizeof(*msg)%s;" % + "".join(["+ msg->payload.%s * sizeof(msg->payload.%s[0])" % ( + f.nelem_field.name, + f.name) + for f in self.fields + if f.nelem_field is not None + ]), + "}", + ]) + + def get_c_def(self): + if self.has_payload(): + return "\n".join([ + "typedef struct __attribute__ ((__packed__)) {", + "%s; " % + ";\n".join(self.payload_members), + "} %s;" % self.get_payload_struct_name(), + "", + "typedef struct __attribute__ ((__packed__)) {", + (" %s %s;" % (self.header.get_c_name(), + self.fields[0].name) + if self.header is not None else ""), + " %s payload;" % self.get_payload_struct_name(), + "} %s;" % self.get_c_name(), ]) + else: + return "\n".join([ + "typedef struct __attribute__ ((__packed__)) {", + (" %s %s;" % (self.header.get_c_name(), + self.fields[0].name) + if self.header is not None else ""), + "} %s;" % self.get_c_name(), ]) + + def get_swap_payload_to_host_func_name(self): + return "%s_payload_ntoh" % self.get_c_name() + + def get_swap_payload_to_be_func_name(self): + return "%s_payload_hton" % self.get_c_name() + + def get_swap_payload_to_host_func_decl(self): + return "void %s(%s *payload)" % ( + self.get_swap_payload_to_host_func_name(), + self.get_payload_struct_name()) + + def get_swap_payload_to_be_func_decl(self): + return "void %s(%s *payload)" % ( + self.get_swap_payload_to_be_func_name(), + self.get_payload_struct_name()) + + def get_swap_payload_to_be_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_payload_to_be_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_be_code("payload->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() and p.type != self.header]), + ) + + def get_swap_payload_to_host_func_def(self): + return "%s\n{\n%s\n}" % ( + self.get_swap_payload_to_host_func_decl(), + "\n".join([ + " %s" % p.get_swap_to_host_code("payload->", "%s" % p.name) + for p in self.fields + if p.needs_byte_swap() and p.type != self.header]), + ) + + def get_swap_to_host_func_name(self): + return "%s_ntoh" % self.get_c_name() + + def get_swap_to_be_func_name(self): + return "%s_hton" % self.get_c_name() + + def get_swap_to_host_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_host_func_name(), self.get_c_name()) + + def get_swap_to_be_func_decl(self): + return "void %s(%s *msg)" % ( + self.get_swap_to_be_func_name(), self.get_c_name()) + + def get_swap_to_be_func_def(self): + return "\n".join([ + "%s" % self.get_swap_to_be_func_decl(), + "{", + (" VAPI_DBG(\"Swapping `%s'@%%p to big endian\", msg);" % + self.get_c_name()), + " %s(&msg->header);" % self.header.get_swap_to_be_func_name() + if self.header is not None else "", + " %s(&msg->payload);" % self.get_swap_payload_to_be_func_name() + if self.has_payload() else "", + "}", + ]) + + def get_swap_to_host_func_def(self): + return "\n".join([ + "%s" % self.get_swap_to_host_func_decl(), + "{", + (" VAPI_DBG(\"Swapping `%s'@%%p to host byte order\", msg);" % + self.get_c_name()), + " %s(&msg->header);" % self.header.get_swap_to_host_func_name() + if self.header is not None else "", + " %s(&msg->payload);" % self.get_swap_payload_to_host_func_name() + if self.has_payload() else "", + "}", + ]) + + def get_op_func_name(self): + return "vapi_%s" % self.name + + def get_op_func_decl(self): + if self.reply.has_payload(): + return "vapi_error_e %s(%s)" % ( + self.get_op_func_name(), + ",\n ".join([ + 'struct vapi_ctx_s *ctx', + '%s *msg' % self.get_c_name(), + 'vapi_error_e (*callback)(struct vapi_ctx_s *ctx', + ' void *callback_ctx', + ' vapi_error_e rv', + ' bool is_last', + ' %s *reply)' % + self.reply.get_payload_struct_name(), + 'void *callback_ctx', + ]) + ) + else: + return "vapi_error_e %s(%s)" % ( + self.get_op_func_name(), + ",\n ".join([ + 'struct vapi_ctx_s *ctx', + '%s *msg' % self.get_c_name(), + 'vapi_error_e (*callback)(struct vapi_ctx_s *ctx', + ' void *callback_ctx', + ' vapi_error_e rv', + ' bool is_last)', + 'void *callback_ctx', + ]) + ) + + def get_op_func_def(self): + return "\n".join([ + "%s" % self.get_op_func_decl(), + "{", + " if (!msg || !callback) {", + " return VAPI_EINVAL;", + " }", + " if (vapi_is_nonblocking(ctx) && vapi_requests_full(ctx)) {", + " return VAPI_EAGAIN;", + " }", + " vapi_error_e rv;", + " if (VAPI_OK != (rv = vapi_producer_lock (ctx))) {", + " return rv;", + " }", + " u32 req_context = vapi_gen_req_context(ctx);", + " msg->header.context = req_context;", + " %s(msg);" % self.get_swap_to_be_func_name(), + (" if (VAPI_OK == (rv = vapi_send_with_control_ping " + "(ctx, msg, req_context))) {" + if self.is_dump() else + " if (VAPI_OK == (rv = vapi_send (ctx, msg))) {" + ), + (" vapi_store_request(ctx, req_context, %s, " + "(vapi_cb_t)callback, callback_ctx);" % + ("true" if self.is_dump() else "false")), + " if (VAPI_OK != vapi_producer_unlock (ctx)) {", + " abort (); /* this really shouldn't happen */", + " }", + " if (vapi_is_nonblocking(ctx)) {", + " rv = VAPI_OK;", + " } else {", + " rv = vapi_dispatch(ctx);", + " }", + " } else {", + " %s(msg);" % self.get_swap_to_host_func_name(), + " if (VAPI_OK != vapi_producer_unlock (ctx)) {", + " abort (); /* this really shouldn't happen */", + " }", + " }", + " return rv;", + "}", + "", + ]) + + def get_event_cb_func_decl(self): + if not self.is_reply(): + raise Exception( + "Cannot register event callback for non-reply function") + if self.has_payload(): + return "\n".join([ + "void vapi_set_%s_event_cb (" % + self.get_c_name(), + " struct vapi_ctx_s *ctx, ", + (" vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " + "void *callback_ctx, %s *payload)," % + self.get_payload_struct_name()), + " void *callback_ctx)", + ]) + else: + return "\n".join([ + "void vapi_set_%s_event_cb (" % + self.get_c_name(), + " struct vapi_ctx_s *ctx, ", + " vapi_error_e (*callback)(struct vapi_ctx_s *ctx, " + "void *callback_ctx),", + " void *callback_ctx)", + ]) + + def get_event_cb_func_def(self): + if not self.is_reply(): + raise Exception( + "Cannot register event callback for non-reply function") + return "\n".join([ + "%s" % self.get_event_cb_func_decl(), + "{", + (" vapi_set_event_cb(ctx, %s, (vapi_event_cb)callback, " + "callback_ctx);" % + self.get_msg_id_name()), + "}"]) + + def get_c_metadata_struct_name(self): + return "__vapi_metadata_%s" % self.name + + def get_c_constructor(self): + has_context = False + if self.header is not None: + has_context = self.header.has_field('context') + return '\n'.join([ + 'static void __attribute__((constructor)) __vapi_constructor_%s()' + % self.name, + '{', + ' static const char name[] = "%s";' % self.name, + ' static const char name_with_crc[] = "%s_%s";' + % (self.name, self.crc[2:]), + ' static vapi_message_desc_t %s = {' % + self.get_c_metadata_struct_name(), + ' name,', + ' sizeof(name) - 1,', + ' name_with_crc,', + ' sizeof(name_with_crc) - 1,', + ' true,' if has_context else ' false,', + ' 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,', + ' 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(), + ' ~0,', + ' };', + '', + ' %s = vapi_register_msg(&%s);' % + (self.get_msg_id_name(), self.get_c_metadata_struct_name()), + ' VAPI_DBG("Assigned msg id %%d to %s", %s);' % + (self.name, self.get_msg_id_name()), + '}', + ]) + + +vapi_send_with_control_ping = """ +static inline vapi_error_e +vapi_send_with_control_ping (vapi_ctx_t ctx, void *msg, u32 context) +{ + vapi_msg_control_ping *ping = vapi_alloc_control_ping (ctx); + if (!ping) + { + return VAPI_ENOMEM; + } + ping->header.context = context; + vapi_msg_control_ping_hton (ping); + return vapi_send2 (ctx, msg, ping); +} +""" + + +def gen_json_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 ") + 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 ") + print("#include ") + print("#include ") + print("#include ") + print("#include ") + 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("") + 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 ") + print("#include ") + print("#include ") + print("#include ") + print("#include ") + print("#include ") + if io.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 ") + print("") + for m in parser.messages_by_json[j].values(): + print("extern vapi_msg_id_t %s;" % m.get_msg_id_name()) + print("") + print("#define DEFINE_VAPI_MSG_IDS_%s\\" % + j.replace(".", "_").replace("/", "_").replace("-", "_").upper()) + print("\\\n".join([ + " vapi_msg_id_t %s;" % m.get_msg_id_name() + for m in parser.messages_by_json[j].values() + ])) + print("") + print("") + for t in parser.types_by_json[j].values(): + try: + print("%s" % t.get_c_def()) + print("") + except: + pass + for m in parser.messages_by_json[j].values(): + print("%s" % m.get_c_def()) + print("") + + print("") + function_attrs = "static inline " + for t in parser.types_by_json[j].values(): + print("%s%s" % (function_attrs, t.get_swap_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, t.get_swap_to_host_func_def())) + print("") + for m in parser.messages_by_json[j].values(): + if m.has_payload(): + print("%s%s" % (function_attrs, + m.get_swap_payload_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, + m.get_swap_payload_to_host_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_calc_msg_size_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_swap_to_be_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_swap_to_host_func_def())) + print("") + for m in parser.messages_by_json[j].values(): + if m.is_reply(): + continue + print("%s%s" % (function_attrs, m.get_alloc_func_def())) + print("") + print("%s%s" % (function_attrs, m.get_op_func_def())) + print("") + 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%s;" % (function_attrs, m.get_event_cb_func_def())) + print("") + print("") + + if io.name == "vpe.api.vapi.h": + print("%s" % vapi_send_with_control_ping) + print("") + + print("#endif") + sys.stdout = orig_stdout + + +def json_to_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) + + +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 C GEN") + logger.setLevel(log_level) + + argparser = argparse.ArgumentParser(description="VPP JSON API parser") + 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') + args = argparser.parse_args() + + jsonparser = JsonParser(logger, args.files, + simple_type_class=CSimpleType, + struct_type_class=CStructType, + field_class=CField, + message_class=CMessage) + + # not using the model of having separate generated header and code files + # with generated symbols present in shared library (per discussion with + # Damjan), to avoid symbol version issues in .so + # gen_c_headers_and_code(jsonparser, logger, args.prefix) + + gen_c_unified_headers(jsonparser, logger, args.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 new file mode 100644 index 00000000..95a80089 --- /dev/null +++ b/src/vpp-api/vapi/vapi_dbg.h @@ -0,0 +1,76 @@ +/* + *------------------------------------------------------------------ + * 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 __included_vapi_debug_h__ +#define __included_vapi_debug_h__ + +/* controls debug prints */ +#define VAPI_DEBUG (0) +#define VAPI_DEBUG_CONNECT (0) +#define VAPI_DEBUG_ALLOC (0) + +#if VAPI_DEBUG +#include +#define VAPI_DEBUG_FILE_DEF \ + static const char *__file = NULL; \ + { \ + __file = strrchr (__FILE__, '/'); \ + if (__file) \ + { \ + ++__file; \ + } \ + else \ + { \ + __file = __FILE__; \ + } \ + } + +#define VAPI_DBG(fmt, ...) \ + do \ + { \ + VAPI_DEBUG_FILE_DEF \ + printf ("DBG:%s:%d:%s():" fmt, __file, __LINE__, __func__, \ + ##__VA_ARGS__); \ + printf ("\n"); \ + fflush (stdout); \ + } \ + while (0); + +#define VAPI_ERR(fmt, ...) \ + do \ + { \ + VAPI_DEBUG_FILE_DEF \ + printf ("ERR:%s:%d:%s():" fmt, __file, __LINE__, __func__, \ + ##__VA_ARGS__); \ + printf ("\n"); \ + fflush (stdout); \ + } \ + while (0); +#else +#define VAPI_DBG(...) +#define VAPI_ERR(...) +#endif + +#endif /* __included_vapi_debug_h__ */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vpp-api/vapi/vapi_internal.h b/src/vpp-api/vapi/vapi_internal.h new file mode 100644 index 00000000..5b85788d --- /dev/null +++ b/src/vpp-api/vapi/vapi_internal.h @@ -0,0 +1,126 @@ +/* + *------------------------------------------------------------------ + * 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_INTERNAL_H +#define VAPI_INTERNAL_H + +#include +#include + +/** + * @file vapi_internal.h + * + * internal vpp api C declarations + * + * This file contains internal vpp api C declarations. It's not intended to be + * used by the client programmer and the API defined here might change at any + * time.. + */ + +struct vapi_ctx_s; + +typedef struct __attribute__ ((__packed__)) +{ + u16 _vl_msg_id; + u32 context; +} vapi_type_msg_header1_t; + +typedef struct __attribute__ ((__packed__)) +{ + u16 _vl_msg_id; + u32 client_index; + u32 context; +} vapi_type_msg_header2_t; + +static inline void +vapi_type_msg_header1_t_hton (vapi_type_msg_header1_t * h) +{ + h->_vl_msg_id = htobe16 (h->_vl_msg_id); +} + +static inline void +vapi_type_msg_header1_t_ntoh (vapi_type_msg_header1_t * h) +{ + h->_vl_msg_id = be16toh (h->_vl_msg_id); +} + +static inline void +vapi_type_msg_header2_t_hton (vapi_type_msg_header2_t * h) +{ + h->_vl_msg_id = htobe16 (h->_vl_msg_id); +} + +static inline void +vapi_type_msg_header2_t_ntoh (vapi_type_msg_header2_t * h) +{ + h->_vl_msg_id = be16toh (h->_vl_msg_id); +} + + +#include + +typedef vapi_error_e (*vapi_cb_t) (struct vapi_ctx_s *, void *, vapi_error_e, + bool, void *); + +typedef void (*generic_swap_fn_t) (void *payload); + +typedef struct +{ + const char *name; + size_t name_len; + const char *name_with_crc; + size_t name_with_crc_len; + bool has_context; + size_t context_offset; + size_t payload_offset; + size_t size; + generic_swap_fn_t swap_to_be; + generic_swap_fn_t swap_to_host; + vapi_msg_id_t id; /* assigned at run-time */ +} vapi_message_desc_t; + +typedef struct +{ + const char *name; + int payload_offset; + size_t size; + void (*swap_to_be) (void *payload); + 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); +int vapi_get_client_index (vapi_ctx_t ctx); +bool vapi_is_nonblocking (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); +u32 vapi_gen_req_context (vapi_ctx_t ctx); +void vapi_store_request (vapi_ctx_t ctx, u32 context, bool is_dump, + vapi_cb_t callback, void *callback_ctx); +int vapi_get_payload_offset (vapi_msg_id_t id); +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); + +vapi_error_e vapi_producer_lock (vapi_ctx_t ctx); +vapi_error_e vapi_producer_unlock (vapi_ctx_t ctx); + +#endif diff --git a/src/vpp-api/vapi/vapi_json_parser.py b/src/vpp-api/vapi/vapi_json_parser.py new file mode 100644 index 00000000..57a22383 --- /dev/null +++ b/src/vpp-api/vapi/vapi_json_parser.py @@ -0,0 +1,303 @@ +#!/usr/bin/env python3 + +import json + + +def msg_is_reply(name): + return name.endswith('_reply') or name.endswith('_details') \ + or name.endswith('_event') or name.endswith('_counters') + + +class ParseError (Exception): + pass + + +magic_prefix = "vl_api_" +magic_suffix = "_t" + + +def remove_magic(what): + if what.startswith(magic_prefix) and what.endswith(magic_suffix): + return what[len(magic_prefix): - len(magic_suffix)] + return what + + +class Field: + + def __init__( + self, + field_name, + field_type, + array_len=None, + nelem_field=None): + self.name = field_name + self.type = field_type + self.len = array_len + self.nelem_field = nelem_field + + def __str__(self): + if self.len is None: + return "name: %s, type: %s" % (self.name, self.type) + elif self.len > 0: + return "name: %s, type: %s, length: %s" % (self.name, self.type, + self.len) + else: + return ("name: %s, type: %s, variable length stored in: %s" % + (self.name, self.type, self.nelem_field)) + + +class Type: + def __init__(self, name): + self.name = name + + +class SimpleType (Type): + + def __init__(self, name): + super().__init__(name) + + def __str__(self): + return self.name + + +def get_msg_header_defs(struct_type_class, field_class, typedict): + return [ + struct_type_class(['msg_header1_t', + ['u16', '_vl_msg_id'], + ['u32', 'context'], + ], + typedict, field_class + ), + struct_type_class(['msg_header2_t', + ['u16', '_vl_msg_id'], + ['u32', 'client_index'], + ['u32', 'context'], + ], + typedict, field_class + ), + ] + + +class Struct: + + def __init__(self, name, fields): + self.name = name + self.fields = fields + self.field_names = [n.name for n in self.fields] + + +class Message: + + def __init__(self, logger, definition, typedict, + struct_type_class, simple_type_class, field_class): + self.logger = logger + m = definition + logger.debug("Parsing message definition `%s'" % m) + name = m[0] + self.name = name + logger.debug("Message name is `%s'" % name) + ignore = True + self.header = None + fields = [] + for header in get_msg_header_defs(struct_type_class, field_class, + typedict): + logger.debug("Probing header `%s'" % header.name) + if header.is_part_of_def(m[1:]): + self.header = header + logger.debug("Found header `%s'" % header.name) + fields.append(field_class(field_name='header', + field_type=self.header)) + ignore = False + break + if ignore and not msg_is_reply(name): + raise ParseError("While parsing message `%s': could not find all " + "common header fields" % name) + for field in m[1:]: + if len(field) == 1 and 'crc' in field: + self.crc = field['crc'] + logger.debug("Found CRC `%s'" % self.crc) + continue + else: + field_type = field[0] + if field_type in typedict: + field_type = typedict[field_type] + else: + field_type = typedict[remove_magic(field_type)] + if len(field) == 2: + if self.header is not None and\ + self.header.has_field(field[1]): + continue + p = field_class(field_name=field[1], + field_type=field_type) + elif len(field) == 3: + if field[2] == 0: + raise ParseError( + "While parsing message `%s': variable length " + "array `%s' doesn't have reference to member " + "containing the actual length" % ( + name, field[1])) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2]) + elif len(field) == 4: + nelem_field = None + for f in fields: + if f.name == field[3]: + nelem_field = f + if nelem_field is None: + raise ParseError( + "While parsing message `%s': couldn't find " + "variable length array `%s' member containing " + "the actual length `%s'" % ( + name, field[1], field[3])) + p = field_class( + field_name=field[1], + field_type=field_type, + array_len=field[2], + nelem_field=nelem_field) + else: + raise Exception("Don't know how to parse message " + "definition for message `%s': `%s'" % + (m, m[1:])) + logger.debug("Parsed field `%s'" % p) + fields.append(p) + self.fields = fields + + def is_dump(self): + return self.name.endswith('_dump') + + def is_reply(self): + return msg_is_reply(self.name) + + +class StructType (Type, Struct): + + def __init__(self, definition, typedict, field_class): + t = definition + name = t[0] + fields = [] + for field in t[1:]: + if len(field) == 1 and 'crc' in field: + self.crc = field['crc'] + continue + elif len(field) == 2: + p = field_class(field_name=field[1], + field_type=typedict[field[0]]) + elif len(field) == 3: + if field[2] == 0: + raise ParseError("While parsing type `%s': array `%s' has " + "variable length" % (name, field[1])) + p = field_class(field_name=field[1], + field_type=typedict[field[0]], + array_len=field[2]) + else: + raise ParseError( + "Don't know how to parse type definition for " + "type `%s': `%s'" % (t, t[1:])) + fields.append(p) + Type.__init__(self, name) + Struct.__init__(self, name, fields) + + def has_field(self, name): + return name in self.field_names + + def is_part_of_def(self, definition): + for idx in range(len(self.fields)): + field = definition[idx] + p = self.fields[idx] + if field[1] != p.name: + return False + if field[0] != p.type.name: + raise ParseError( + "Unexpected field type `%s' (should be `%s'), " + "while parsing msg/def/field `%s/%s/%s'" % + (field[0], p.type, p.name, definition, field)) + return True + + +class JsonParser: + def __init__(self, logger, files, simple_type_class=SimpleType, + struct_type_class=StructType, field_class=Field, + message_class=Message): + self.messages = {} + self.types = { + x: simple_type_class(x) for x in [ + 'i8', 'i16', 'i32', 'i64', + 'u8', 'u16', 'u32', 'u64', + 'f64' + ] + } + + self.simple_type_class = simple_type_class + self.struct_type_class = struct_type_class + self.field_class = field_class + self.message_class = message_class + + self.exceptions = [] + self.json_files = [] + self.types_by_json = {} + self.messages_by_json = {} + self.logger = logger + for f in files: + self.parse_json_file(f) + self.finalize_parsing() + + def parse_json_file(self, path): + self.logger.info("Parsing json api file: `%s'" % path) + self.json_files.append(path) + self.types_by_json[path] = {} + self.messages_by_json[path] = {} + with open(path) as f: + j = json.load(f) + for t in j['types']: + try: + type_ = self.struct_type_class(t, self.types, + self.field_class) + if type_.name in self.types: + raise ParseError("Duplicate type `%s'" % type_.name) + except ParseError as e: + self.exceptions.append(e) + continue + self.types[type_.name] = type_ + self.types_by_json[path][type_.name] = type_ + for m in j['messages']: + try: + msg = self.message_class(self.logger, m, self.types, + self.struct_type_class, + self.simple_type_class, + self.field_class) + if msg.name in self.messages: + raise ParseError("Duplicate message `%s'" % msg.name) + except ParseError as e: + self.exceptions.append(e) + continue + self.messages[msg.name] = msg + self.messages_by_json[path][msg.name] = msg + + def get_reply(self, message): + if self.messages[message].is_dump(): + return self.messages["%s_details" % message[:-len("_dump")]] + return self.messages["%s_reply" % message] + + def finalize_parsing(self): + if len(self.messages) == 0: + for e in self.exceptions: + self.logger.error(e) + raise Exception("No messages parsed.") + for jn, j in self.messages_by_json.items(): + remove = [] + for n, m in j.items(): + try: + if not m.is_reply(): + try: + m.reply = self.get_reply(n) + except: + raise ParseError( + "Cannot find reply to message `%s'" % n) + except ParseError as e: + self.exceptions.append(e) + remove.append(n) + + self.messages_by_json[jn] = { + k: v for k, v in j.items() if k not in remove} diff --git a/test/Makefile b/test/Makefile index 72b4dac7..132ebee6 100644 --- a/test/Makefile +++ b/test/Makefile @@ -107,7 +107,11 @@ sanity: verify-no-running-vpp echo \"*******************************************************************\" &&\ false)" -test: verify-python-path $(PAPI_INSTALL_DONE) sanity reset +.PHONY: ext +ext: + make -C ext + +test: verify-python-path $(PAPI_INSTALL_DONE) ext sanity reset $(call retest-func) retest: verify-python-path sanity reset diff --git a/test/ext/Makefile b/test/ext/Makefile new file mode 100644 index 00000000..4a45fef6 --- /dev/null +++ b/test/ext/Makefile @@ -0,0 +1,17 @@ +BINDIR = $(BR)/vapi_test/ +BIN = $(addprefix $(BINDIR), vapi_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/ + +all: $(BIN) + +$(BINDIR): + mkdir -p $(BINDIR) + +SRC = vapi_test.c + +$(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) + +clean: + rm -rf $(BINDIR) diff --git a/test/ext/vapi_test.c b/test/ext/vapi_test.c new file mode 100644 index 00000000..eca6be7d --- /dev/null +++ b/test/ext/vapi_test.c @@ -0,0 +1,1152 @@ +/* + *------------------------------------------------------------------ + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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; + +static char *app_name = NULL; +static char *api_prefix = NULL; +static const int max_outstanding_requests = 64; +static const int response_queue_size = 32; + +START_TEST (test_invalid_values) +{ + vapi_ctx_t ctx; + vapi_error_e rv = vapi_ctx_alloc (&ctx); + ck_assert_int_eq (VAPI_OK, rv); + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_eq (NULL, sv); + rv = vapi_send (ctx, sv); + ck_assert_int_eq (VAPI_EINVAL, rv); + rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_BLOCKING); + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_send (ctx, NULL); + ck_assert_int_eq (VAPI_EINVAL, rv); + rv = vapi_send (NULL, NULL); + ck_assert_int_eq (VAPI_EINVAL, rv); + rv = vapi_recv (NULL, NULL, NULL); + ck_assert_int_eq (VAPI_EINVAL, rv); + rv = vapi_recv (ctx, NULL, NULL); + ck_assert_int_eq (VAPI_EINVAL, rv); + vapi_msg_show_version_reply *reply; + rv = vapi_recv (ctx, (void **) &reply, NULL); + ck_assert_int_eq (VAPI_EINVAL, rv); + rv = vapi_disconnect (ctx); + ck_assert_int_eq (VAPI_OK, rv); + vapi_ctx_free (ctx); +} + +END_TEST; + +START_TEST (test_hton_1) +{ + const u16 _vl_msg_id = 1; + vapi_type_msg_header1_t h; + h._vl_msg_id = _vl_msg_id; + vapi_type_msg_header1_t_hton (&h); + ck_assert_int_eq (be16toh (h._vl_msg_id), _vl_msg_id); +} + +END_TEST; + +START_TEST (test_hton_2) +{ + const u16 _vl_msg_id = 1; + const u32 client_index = 3; + vapi_type_msg_header2_t h; + h._vl_msg_id = _vl_msg_id; + h.client_index = client_index; + vapi_type_msg_header2_t_hton (&h); + ck_assert_int_eq (be16toh (h._vl_msg_id), _vl_msg_id); + ck_assert_int_eq (h.client_index, client_index); +} + +END_TEST; + +START_TEST (test_hton_3) +{ + const size_t data_size = 10; + vapi_msg_vnet_interface_combined_counters *m = + malloc (sizeof (vapi_msg_vnet_interface_combined_counters) + + data_size * sizeof (vapi_type_vlib_counter)); + ck_assert_ptr_ne (NULL, m); + vapi_payload_vnet_interface_combined_counters *p = &m->payload; + const u16 _vl_msg_id = 1; + p->_vl_msg_id = _vl_msg_id; + const u32 first_sw_if_index = 2; + p->first_sw_if_index = first_sw_if_index; + p->count = data_size; + const u64 packets = 1234; + const u64 bytes = 2345; + int i; + for (i = 0; i < data_size; ++i) + { + p->data[i].packets = packets; + p->data[i].bytes = bytes; + } + vapi_msg_vnet_interface_combined_counters_hton (m); + ck_assert_int_eq (_vl_msg_id, be16toh (p->_vl_msg_id)); + ck_assert_int_eq (first_sw_if_index, be32toh (p->first_sw_if_index)); + ck_assert_int_eq (data_size, be32toh (p->count)); + for (i = 0; i < data_size; ++i) + { + ck_assert_int_eq (packets, be64toh (p->data[i].packets)); + ck_assert_int_eq (bytes, be64toh (p->data[i].bytes)); + } + free (p); +} + +END_TEST; + +#define verify_hton_swap(expr, value) \ + if (4 == sizeof (expr)) \ + { \ + ck_assert_int_eq (expr, htobe32 (value)); \ + } \ + else if (2 == sizeof (expr)) \ + { \ + ck_assert_int_eq (expr, htobe16 (value)); \ + } \ + else \ + { \ + ck_assert_int_eq (expr, value); \ + } + +START_TEST (test_hton_4) +{ + const int vla_count = 3; + char x[sizeof (vapi_msg_bridge_domain_details) + + vla_count * sizeof (vapi_type_bridge_domain_sw_if)]; + vapi_msg_bridge_domain_details *d = (void *) x; + int cnt = 1; + d->header._vl_msg_id = cnt++; + d->header.context = cnt++; + d->payload.bd_id = cnt++; + d->payload.flood = cnt++; + d->payload.uu_flood = cnt++; + d->payload.forward = cnt++; + d->payload.learn = cnt++; + d->payload.arp_term = cnt++; + d->payload.mac_age = cnt++; + d->payload.bvi_sw_if_index = cnt++; + d->payload.n_sw_ifs = vla_count; + int i; + for (i = 0; i < vla_count; ++i) + { + vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i]; + det->context = cnt++; + det->sw_if_index = cnt++; + det->shg = cnt++; + } + ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d)); + vapi_msg_bridge_domain_details_hton (d); + int tmp = 1; + verify_hton_swap (d->header._vl_msg_id, tmp); + ++tmp; + ck_assert_int_eq (d->header.context, tmp); + ++tmp; + verify_hton_swap (d->payload.bd_id, tmp); + ++tmp; + verify_hton_swap (d->payload.flood, tmp); + ++tmp; + verify_hton_swap (d->payload.uu_flood, tmp); + ++tmp; + verify_hton_swap (d->payload.forward, tmp); + ++tmp; + verify_hton_swap (d->payload.learn, tmp); + ++tmp; + verify_hton_swap (d->payload.arp_term, tmp); + ++tmp; + verify_hton_swap (d->payload.mac_age, tmp); + ++tmp; + verify_hton_swap (d->payload.bvi_sw_if_index, tmp); + ++tmp; + ck_assert_int_eq (d->payload.n_sw_ifs, htobe32 (vla_count)); + for (i = 0; i < vla_count; ++i) + { + vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i]; + verify_hton_swap (det->context, tmp); + ++tmp; + verify_hton_swap (det->sw_if_index, tmp); + ++tmp; + verify_hton_swap (det->shg, tmp); + ++tmp; + } + vapi_msg_bridge_domain_details_ntoh (d); + tmp = 1; + ck_assert_int_eq (d->header._vl_msg_id, tmp); + ++tmp; + ck_assert_int_eq (d->header.context, tmp); + ++tmp; + ck_assert_int_eq (d->payload.bd_id, tmp); + ++tmp; + ck_assert_int_eq (d->payload.flood, tmp); + ++tmp; + ck_assert_int_eq (d->payload.uu_flood, tmp); + ++tmp; + ck_assert_int_eq (d->payload.forward, tmp); + ++tmp; + ck_assert_int_eq (d->payload.learn, tmp); + ++tmp; + ck_assert_int_eq (d->payload.arp_term, tmp); + ++tmp; + ck_assert_int_eq (d->payload.mac_age, tmp); + ++tmp; + ck_assert_int_eq (d->payload.bvi_sw_if_index, tmp); + ++tmp; + ck_assert_int_eq (d->payload.n_sw_ifs, vla_count); + for (i = 0; i < vla_count; ++i) + { + vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i]; + ck_assert_int_eq (det->context, tmp); + ++tmp; + ck_assert_int_eq (det->sw_if_index, tmp); + ++tmp; + ck_assert_int_eq (det->shg, tmp); + ++tmp; + } + ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d)); +} + +END_TEST; + +START_TEST (test_ntoh_1) +{ + const u16 _vl_msg_id = 1; + vapi_type_msg_header1_t h; + h._vl_msg_id = _vl_msg_id; + vapi_type_msg_header1_t_ntoh (&h); + ck_assert_int_eq (htobe16 (h._vl_msg_id), _vl_msg_id); +} + +END_TEST; + +START_TEST (test_ntoh_2) +{ + const u16 _vl_msg_id = 1; + const u32 client_index = 3; + vapi_type_msg_header2_t h; + h._vl_msg_id = _vl_msg_id; + h.client_index = client_index; + vapi_type_msg_header2_t_ntoh (&h); + ck_assert_int_eq (htobe16 (h._vl_msg_id), _vl_msg_id); + ck_assert_int_eq (h.client_index, client_index); +} + +END_TEST; + +START_TEST (test_ntoh_3) +{ + const size_t data_size = 10; + vapi_msg_vnet_interface_combined_counters *m = + malloc (sizeof (vapi_msg_vnet_interface_combined_counters) + + data_size * sizeof (vapi_type_vlib_counter)); + ck_assert_ptr_ne (NULL, m); + vapi_payload_vnet_interface_combined_counters *p = &m->payload; + const u16 _vl_msg_id = 1; + p->_vl_msg_id = _vl_msg_id; + const u32 first_sw_if_index = 2; + p->first_sw_if_index = first_sw_if_index; + const size_t be_data_size = htobe32 (data_size); + p->count = be_data_size; + const u64 packets = 1234; + const u64 bytes = 2345; + int i; + for (i = 0; i < data_size; ++i) + { + p->data[i].packets = packets; + p->data[i].bytes = bytes; + } + vapi_msg_vnet_interface_combined_counters_ntoh (m); + ck_assert_int_eq (_vl_msg_id, be16toh (p->_vl_msg_id)); + ck_assert_int_eq (first_sw_if_index, be32toh (p->first_sw_if_index)); + ck_assert_int_eq (be_data_size, be32toh (p->count)); + for (i = 0; i < data_size; ++i) + { + ck_assert_int_eq (packets, htobe64 (p->data[i].packets)); + ck_assert_int_eq (bytes, htobe64 (p->data[i].bytes)); + } + free (p); +} + +END_TEST; + +#define verify_ntoh_swap(expr, value) \ + if (4 == sizeof (expr)) \ + { \ + ck_assert_int_eq (expr, be32toh (value)); \ + } \ + else if (2 == sizeof (expr)) \ + { \ + ck_assert_int_eq (expr, be16toh (value)); \ + } \ + else \ + { \ + ck_assert_int_eq (expr, value); \ + } + +START_TEST (test_ntoh_4) +{ + const int vla_count = 3; + char x[sizeof (vapi_msg_bridge_domain_details) + + vla_count * sizeof (vapi_type_bridge_domain_sw_if)]; + vapi_msg_bridge_domain_details *d = (void *) x; + int cnt = 1; + d->header._vl_msg_id = cnt++; + d->header.context = cnt++; + d->payload.bd_id = cnt++; + d->payload.flood = cnt++; + d->payload.uu_flood = cnt++; + d->payload.forward = cnt++; + d->payload.learn = cnt++; + d->payload.arp_term = cnt++; + d->payload.mac_age = cnt++; + d->payload.bvi_sw_if_index = cnt++; + d->payload.n_sw_ifs = htobe32 (vla_count); + int i; + for (i = 0; i < vla_count; ++i) + { + vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i]; + det->context = cnt++; + det->sw_if_index = cnt++; + det->shg = cnt++; + } + vapi_msg_bridge_domain_details_ntoh (d); + ck_assert_int_eq (sizeof (x), vapi_calc_bridge_domain_details_msg_size (d)); + int tmp = 1; + verify_ntoh_swap (d->header._vl_msg_id, tmp); + ++tmp; + ck_assert_int_eq (d->header.context, tmp); + ++tmp; + verify_ntoh_swap (d->payload.bd_id, tmp); + ++tmp; + verify_ntoh_swap (d->payload.flood, tmp); + ++tmp; + verify_ntoh_swap (d->payload.uu_flood, tmp); + ++tmp; + verify_ntoh_swap (d->payload.forward, tmp); + ++tmp; + verify_ntoh_swap (d->payload.learn, tmp); + ++tmp; + verify_ntoh_swap (d->payload.arp_term, tmp); + ++tmp; + verify_ntoh_swap (d->payload.mac_age, tmp); + ++tmp; + verify_ntoh_swap (d->payload.bvi_sw_if_index, tmp); + ++tmp; + ck_assert_int_eq (d->payload.n_sw_ifs, vla_count); + for (i = 0; i < vla_count; ++i) + { + vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i]; + verify_ntoh_swap (det->context, tmp); + ++tmp; + verify_ntoh_swap (det->sw_if_index, tmp); + ++tmp; + verify_ntoh_swap (det->shg, tmp); + ++tmp; + } + vapi_msg_bridge_domain_details_hton (d); + tmp = 1; + ck_assert_int_eq (d->header._vl_msg_id, tmp); + ++tmp; + ck_assert_int_eq (d->header.context, tmp); + ++tmp; + ck_assert_int_eq (d->payload.bd_id, tmp); + ++tmp; + ck_assert_int_eq (d->payload.flood, tmp); + ++tmp; + ck_assert_int_eq (d->payload.uu_flood, tmp); + ++tmp; + ck_assert_int_eq (d->payload.forward, tmp); + ++tmp; + ck_assert_int_eq (d->payload.learn, tmp); + ++tmp; + ck_assert_int_eq (d->payload.arp_term, tmp); + ++tmp; + ck_assert_int_eq (d->payload.mac_age, tmp); + ++tmp; + ck_assert_int_eq (d->payload.bvi_sw_if_index, tmp); + ++tmp; + ck_assert_int_eq (d->payload.n_sw_ifs, htobe32 (vla_count)); + for (i = 0; i < vla_count; ++i) + { + vapi_type_bridge_domain_sw_if *det = &d->payload.sw_if_details[i]; + ck_assert_int_eq (det->context, tmp); + ++tmp; + ck_assert_int_eq (det->sw_if_index, tmp); + ++tmp; + ck_assert_int_eq (det->shg, tmp); + ++tmp; + } +} + +END_TEST; + +vapi_error_e +show_version_cb (vapi_ctx_t ctx, void *caller_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_show_version_reply * p) +{ + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (true, is_last); + ck_assert_str_eq ("vpe", (char *) p->program); + 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); + ++*(int *) caller_ctx; + return VAPI_OK; +} + +typedef struct +{ + int called; + int expected_retval; + u32 *sw_if_index_storage; +} test_create_loopback_ctx_t; + +vapi_error_e +loopback_create_cb (vapi_ctx_t ctx, void *caller_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_create_loopback_reply * p) +{ + test_create_loopback_ctx_t *clc = caller_ctx; + ck_assert_int_eq (clc->expected_retval, p->retval); + *clc->sw_if_index_storage = p->sw_if_index; + ++clc->called; + return VAPI_OK; +} + +typedef struct +{ + int called; + int expected_retval; + u32 *sw_if_index_storage; +} test_delete_loopback_ctx_t; + +vapi_error_e +loopback_delete_cb (vapi_ctx_t ctx, void *caller_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_delete_loopback_reply * p) +{ + test_delete_loopback_ctx_t *dlc = caller_ctx; + ck_assert_int_eq (dlc->expected_retval, p->retval); + ++dlc->called; + return VAPI_OK; +} + +START_TEST (test_connect) +{ + vapi_ctx_t ctx; + vapi_error_e rv = vapi_ctx_alloc (&ctx); + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_BLOCKING); + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_disconnect (ctx); + ck_assert_int_eq (VAPI_OK, rv); + vapi_ctx_free (ctx); +} + +END_TEST; + +vapi_ctx_t ctx; + +void +setup_blocking (void) +{ + vapi_error_e rv = vapi_ctx_alloc (&ctx); + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_BLOCKING); + ck_assert_int_eq (VAPI_OK, rv); +} + +void +setup_nonblocking (void) +{ + vapi_error_e rv = vapi_ctx_alloc (&ctx); + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_connect (ctx, app_name, api_prefix, max_outstanding_requests, + response_queue_size, VAPI_MODE_NONBLOCKING); + ck_assert_int_eq (VAPI_OK, rv); +} + +void +teardown (void) +{ + vapi_disconnect (ctx); + vapi_ctx_free (ctx); +} + +START_TEST (test_show_version_1) +{ + printf ("--- Basic show version message - reply test ---\n"); + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + vapi_msg_show_version_hton (sv); + vapi_error_e rv = vapi_send (ctx, sv); + ck_assert_int_eq (VAPI_OK, rv); + vapi_msg_show_version_reply *resp; + 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); + vapi_msg_free (ctx, resp); +} + +END_TEST; + +START_TEST (test_show_version_2) +{ + int called = 0; + printf ("--- Show version via blocking callback API ---\n"); + const int attempts = response_queue_size * 4; + int i = 0; + for (i = 0; i < attempts; ++i) + { + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + vapi_error_e rv = vapi_show_version (ctx, sv, show_version_cb, &called); + ck_assert_int_eq (VAPI_OK, rv); + } + ck_assert_int_eq (attempts, called); +} + +END_TEST; + +typedef struct +{ + bool last_called; + size_t num_ifs; + u32 *sw_if_indexes; + bool *seen; + int called; +} sw_interface_dump_ctx; + +vapi_error_e +sw_interface_dump_cb (struct vapi_ctx_s *ctx, void *callback_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_sw_interface_details * reply) +{ + sw_interface_dump_ctx *dctx = callback_ctx; + ck_assert_int_eq (false, dctx->last_called); + if (is_last) + { + ck_assert (NULL == reply); + dctx->last_called = true; + } + else + { + ck_assert (reply); + printf ("Interface dump entry: [%u]: %s\n", reply->sw_if_index, + reply->interface_name); + size_t i = 0; + for (i = 0; i < dctx->num_ifs; ++i) + { + if (dctx->sw_if_indexes[i] == reply->sw_if_index) + { + ck_assert_int_eq (false, dctx->seen[i]); + dctx->seen[i] = true; + } + } + } + ++dctx->called; + return VAPI_OK; +} + +START_TEST (test_loopbacks_1) +{ + printf ("--- Create/delete loopbacks using blocking API ---\n"); + const size_t 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)); + test_create_loopback_ctx_t clcs[num_ifs]; + memset (&clcs, 0, sizeof (clcs)); + test_delete_loopback_ctx_t dlcs[num_ifs]; + memset (&dlcs, 0, sizeof (dlcs)); + int i; + for (i = 0; i < num_ifs; ++i) + { + memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6); + mac_addresses[i][5] = i; + clcs[i].sw_if_index_storage = &sw_if_indexes[i]; + } + for (i = 0; i < num_ifs; ++i) + { + vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx); + memcpy (cl->payload.mac_address, mac_addresses[i], + sizeof (cl->payload.mac_address)); + vapi_error_e rv = + vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i]); + ck_assert_int_eq (VAPI_OK, rv); + } + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, clcs[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], + sw_if_indexes[i]); + } + bool seen[num_ifs]; + sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 }; + vapi_msg_sw_interface_dump *dump; + vapi_error_e rv; + const int attempts = response_queue_size * 4; + for (i = 0; i < attempts; ++i) + { + dctx.last_called = false; + memset (&seen, 0, sizeof (seen)); + dump = vapi_alloc_sw_interface_dump (ctx); + dump->payload.name_filter_valid = 0; + memset (dump->payload.name_filter, 0, + sizeof (dump->payload.name_filter)); + while (VAPI_EAGAIN == + (rv = + vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, + &dctx))) + ; + ck_assert_int_eq (true, dctx.last_called); + int j = 0; + for (j = 0; j < num_ifs; ++j) + { + ck_assert_int_eq (true, seen[j]); + } + } + memset (&seen, 0, sizeof (seen)); + for (i = 0; i < num_ifs; ++i) + { + vapi_msg_delete_loopback *dl = vapi_alloc_delete_loopback (ctx); + dl->payload.sw_if_index = sw_if_indexes[i]; + vapi_error_e rv = + vapi_delete_loopback (ctx, dl, loopback_delete_cb, &dlcs[i]); + ck_assert_int_eq (VAPI_OK, rv); + } + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, dlcs[i].called); + printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]); + } + dctx.last_called = false; + memset (&seen, 0, sizeof (seen)); + dump = vapi_alloc_sw_interface_dump (ctx); + dump->payload.name_filter_valid = 0; + memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter)); + while (VAPI_EAGAIN == + (rv = + vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx))) + ; + ck_assert_int_eq (true, dctx.last_called); + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (false, seen[i]); + } +} + +END_TEST; + +START_TEST (test_show_version_3) +{ + printf ("--- Show version via async callback ---\n"); + int called = 0; + vapi_error_e rv; + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + while (VAPI_EAGAIN == + (rv = vapi_show_version (ctx, sv, show_version_cb, &called))) + ; + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (0, called); + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (1, called); + called = 0; + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (0, called); +} + +END_TEST; + +START_TEST (test_show_version_4) +{ + printf ("--- Show version via async callback - multiple messages ---\n"); + vapi_error_e rv; + const size_t num_req = 5; + int contexts[num_req]; + memset (contexts, 0, sizeof (contexts)); + int i; + for (i = 0; i < num_req; ++i) + { + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + while (VAPI_EAGAIN == + (rv = + vapi_show_version (ctx, sv, show_version_cb, &contexts[i]))) + ; + ck_assert_int_eq (VAPI_OK, rv); + int j; + for (j = 0; j < num_req; ++j) + { + ck_assert_int_eq (0, contexts[j]); + } + } + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + for (i = 0; i < num_req; ++i) + { + ck_assert_int_eq (1, contexts[i]); + } + memset (contexts, 0, sizeof (contexts)); + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + for (i = 0; i < num_req; ++i) + { + ck_assert_int_eq (0, contexts[i]); + } +} + +END_TEST; + +START_TEST (test_loopbacks_2) +{ + printf ("--- Create/delete loopbacks using non-blocking API ---\n"); + vapi_error_e rv; + const size_t 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)); + test_create_loopback_ctx_t clcs[num_ifs]; + memset (&clcs, 0, sizeof (clcs)); + test_delete_loopback_ctx_t dlcs[num_ifs]; + memset (&dlcs, 0, sizeof (dlcs)); + int i; + for (i = 0; i < num_ifs; ++i) + { + memcpy (&mac_addresses[i], "\1\2\3\4\5\6", 6); + mac_addresses[i][5] = i; + clcs[i].sw_if_index_storage = &sw_if_indexes[i]; + } + for (i = 0; i < num_ifs; ++i) + { + vapi_msg_create_loopback *cl = vapi_alloc_create_loopback (ctx); + memcpy (cl->payload.mac_address, mac_addresses[i], + sizeof (cl->payload.mac_address)); + while (VAPI_EAGAIN == + (rv = + vapi_create_loopback (ctx, cl, loopback_create_cb, &clcs[i]))) + ; + ck_assert_int_eq (VAPI_OK, rv); + } + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, clcs[i].called); + printf ("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]); + } + bool seen[num_ifs]; + memset (&seen, 0, sizeof (seen)); + sw_interface_dump_ctx dctx = { false, num_ifs, sw_if_indexes, seen, 0 }; + vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx); + dump->payload.name_filter_valid = 0; + memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter)); + while (VAPI_EAGAIN == + (rv = + vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx))) + ; + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (false, seen[i]); + } + memset (&seen, 0, sizeof (seen)); + ck_assert_int_eq (false, dctx.last_called); + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (true, seen[i]); + } + memset (&seen, 0, sizeof (seen)); + ck_assert_int_eq (true, dctx.last_called); + for (i = 0; i < num_ifs; ++i) + { + vapi_msg_delete_loopback *dl = vapi_alloc_delete_loopback (ctx); + dl->payload.sw_if_index = sw_if_indexes[i]; + while (VAPI_EAGAIN == + (rv = + vapi_delete_loopback (ctx, dl, loopback_delete_cb, &dlcs[i]))) + ; + ck_assert_int_eq (VAPI_OK, rv); + } + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (1, dlcs[i].called); + printf ("Deleted loopback with sw_if_index %u\n", sw_if_indexes[i]); + } + memset (&seen, 0, sizeof (seen)); + dctx.last_called = false; + dump = vapi_alloc_sw_interface_dump (ctx); + dump->payload.name_filter_valid = 0; + memset (dump->payload.name_filter, 0, sizeof (dump->payload.name_filter)); + while (VAPI_EAGAIN == + (rv = + vapi_sw_interface_dump (ctx, dump, sw_interface_dump_cb, &dctx))) + ; + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + for (i = 0; i < num_ifs; ++i) + { + ck_assert_int_eq (false, seen[i]); + } + memset (&seen, 0, sizeof (seen)); + ck_assert_int_eq (true, dctx.last_called); +} + +END_TEST; + +vapi_error_e +interface_simple_stats_cb (vapi_ctx_t ctx, void *callback_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_want_interface_simple_stats_reply * + payload) +{ + return VAPI_OK; +} + +vapi_error_e +simple_counters_cb (vapi_ctx_t ctx, void *callback_ctx, + vapi_payload_vnet_interface_simple_counters * payload) +{ + int *called = callback_ctx; + ++*called; + printf ("simple counters: first_sw_if_index=%u\n", + payload->first_sw_if_index); + return VAPI_OK; +} + +START_TEST (test_stats_1) +{ + printf ("--- Receive stats using generic blocking API ---\n"); + vapi_msg_want_interface_simple_stats *ws = + vapi_alloc_want_interface_simple_stats (ctx); + ws->payload.enable_disable = 1; + ws->payload.pid = getpid (); + vapi_error_e rv; + rv = vapi_want_interface_simple_stats (ctx, ws, interface_simple_stats_cb, + NULL); + ck_assert_int_eq (VAPI_OK, rv); + int called = 0; + vapi_set_event_cb (ctx, vapi_msg_id_vnet_interface_simple_counters, + (vapi_event_cb) simple_counters_cb, &called); + rv = vapi_dispatch_one (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (1, called); +} + +END_TEST; + +START_TEST (test_stats_2) +{ + printf ("--- Receive stats using stat-specific blocking API ---\n"); + vapi_msg_want_interface_simple_stats *ws = + vapi_alloc_want_interface_simple_stats (ctx); + ws->payload.enable_disable = 1; + ws->payload.pid = getpid (); + vapi_error_e rv; + rv = vapi_want_interface_simple_stats (ctx, ws, interface_simple_stats_cb, + NULL); + ck_assert_int_eq (VAPI_OK, rv); + int called = 0; + vapi_set_vapi_msg_vnet_interface_simple_counters_event_cb (ctx, + simple_counters_cb, + &called); + rv = vapi_dispatch_one (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (1, called); +} + +END_TEST; + +vapi_error_e +generic_cb (vapi_ctx_t ctx, void *callback_ctx, vapi_msg_id_t id, void *msg) +{ + int *called = callback_ctx; + ck_assert_int_eq (0, *called); + ++*called; + ck_assert_int_eq (id, vapi_msg_id_show_version_reply); + ck_assert_ptr_ne (NULL, msg); + vapi_msg_show_version_reply *reply = msg; + ck_assert_str_eq ("vpe", (char *) reply->payload.program); + return VAPI_OK; +} + +START_TEST (test_show_version_5) +{ + printf ("--- Receive show version using generic callback - nonblocking " + "API ---\n"); + vapi_error_e rv; + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + vapi_msg_show_version_hton (sv); + while (VAPI_EAGAIN == (rv = vapi_send (ctx, sv))) + ; + ck_assert_int_eq (VAPI_OK, rv); + int called = 0; + vapi_set_generic_event_cb (ctx, generic_cb, &called); + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_dispatch_one (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (1, called); + sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + vapi_msg_show_version_hton (sv); + while (VAPI_EAGAIN == (rv = vapi_send (ctx, sv))) + ; + ck_assert_int_eq (VAPI_OK, rv); + vapi_clear_generic_event_cb (ctx); + rv = vapi_dispatch_one (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (1, called); /* needs to remain unchanged */ +} + +END_TEST; + +vapi_error_e +combined_counters_cb (struct vapi_ctx_s *ctx, void *callback_ctx, + vapi_payload_vnet_interface_combined_counters * payload) +{ + int *called = callback_ctx; + ++*called; + printf ("combined counters: first_sw_if_index=%u\n", + payload->first_sw_if_index); + return VAPI_OK; +} + +vapi_error_e +stats_cb (vapi_ctx_t ctx, void *callback_ctx, vapi_error_e rv, + bool is_last, vapi_payload_want_stats_reply * payload) +{ + return VAPI_OK; +} + +START_TEST (test_stats_3) +{ + printf ("--- Receive multiple stats using stat-specific non-blocking API " + "---\n"); + vapi_msg_want_stats *ws = vapi_alloc_want_stats (ctx); + ws->payload.enable_disable = 1; + ws->payload.pid = getpid (); + vapi_error_e rv; + rv = vapi_want_stats (ctx, ws, stats_cb, NULL); + ck_assert_int_eq (VAPI_OK, rv); + int called = 0; + int called2 = 0; + vapi_set_vapi_msg_vnet_interface_simple_counters_event_cb (ctx, + simple_counters_cb, + &called); + vapi_set_vapi_msg_vnet_interface_combined_counters_event_cb (ctx, + combined_counters_cb, + &called2); + while (!called || !called2) + { + if (VAPI_EAGAIN != (rv = vapi_dispatch_one (ctx))) + { + ck_assert_int_eq (VAPI_OK, rv); + } + } +} + +END_TEST; + +vapi_error_e +show_version_no_cb (vapi_ctx_t ctx, void *caller_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_show_version_reply * p) +{ + ck_assert_int_eq (VAPI_ENORESP, rv); + ck_assert_int_eq (true, is_last); + ck_assert_ptr_eq (NULL, p); + ++*(int *) caller_ctx; + return VAPI_OK; +} + +START_TEST (test_no_response_1) +{ + printf ("--- Simulate no response to regular message ---\n"); + vapi_error_e rv; + vapi_msg_show_version *sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + sv->header._vl_msg_id = ~0; /* malformed ID causes vpp to drop the msg */ + int called = 0; + while (VAPI_EAGAIN == + (rv = vapi_show_version (ctx, sv, show_version_no_cb, &called))) + ; + ck_assert_int_eq (VAPI_OK, rv); + sv = vapi_alloc_show_version (ctx); + ck_assert_ptr_ne (NULL, sv); + while (VAPI_EAGAIN == + (rv = vapi_show_version (ctx, sv, show_version_cb, &called))) + ; + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (2, called); +} + +END_TEST; + +vapi_error_e +no_msg_cb (struct vapi_ctx_s *ctx, void *callback_ctx, + vapi_error_e rv, bool is_last, + vapi_payload_sw_interface_details * reply) +{ + int *called = callback_ctx; + ++*called; + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (true, is_last); + ck_assert_ptr_eq (NULL, reply); + return VAPI_OK; +} + +START_TEST (test_no_response_2) +{ + printf ("--- Simulate no response to dump message ---\n"); + vapi_error_e rv; + vapi_msg_sw_interface_dump *dump = vapi_alloc_sw_interface_dump (ctx); + dump->header._vl_msg_id = ~0; /* malformed ID causes vpp to drop the msg */ + int no_called = 0; + while (VAPI_EAGAIN == + (rv = vapi_sw_interface_dump (ctx, dump, no_msg_cb, &no_called))) + ; + ck_assert_int_eq (VAPI_OK, rv); + rv = vapi_dispatch (ctx); + ck_assert_int_eq (VAPI_OK, rv); + ck_assert_int_eq (1, no_called); +} + +END_TEST; +Suite * +test_suite (void) +{ + Suite *s = suite_create ("VAPI test"); + + TCase *tc_negative = tcase_create ("Negative tests"); + tcase_add_test (tc_negative, test_invalid_values); + suite_add_tcase (s, tc_negative); + + TCase *tc_swap = tcase_create ("Byteswap tests"); + tcase_add_test (tc_swap, test_hton_1); + tcase_add_test (tc_swap, test_hton_2); + tcase_add_test (tc_swap, test_hton_3); + tcase_add_test (tc_swap, test_hton_4); + tcase_add_test (tc_swap, test_ntoh_1); + tcase_add_test (tc_swap, test_ntoh_2); + tcase_add_test (tc_swap, test_ntoh_3); + tcase_add_test (tc_swap, test_ntoh_4); + suite_add_tcase (s, tc_swap); + + TCase *tc_connect = tcase_create ("Connect"); + tcase_add_test (tc_connect, test_connect); + suite_add_tcase (s, tc_connect); + + TCase *tc_block = tcase_create ("Blocking API"); + tcase_set_timeout (tc_block, 25); + tcase_add_checked_fixture (tc_block, setup_blocking, teardown); + tcase_add_test (tc_block, test_show_version_1); + tcase_add_test (tc_block, test_show_version_2); + tcase_add_test (tc_block, test_loopbacks_1); + tcase_add_test (tc_block, test_stats_1); + tcase_add_test (tc_block, test_stats_2); + suite_add_tcase (s, tc_block); + + TCase *tc_nonblock = tcase_create ("Nonblocking API"); + tcase_set_timeout (tc_nonblock, 25); + tcase_add_checked_fixture (tc_nonblock, setup_nonblocking, teardown); + tcase_add_test (tc_nonblock, test_show_version_3); + tcase_add_test (tc_nonblock, test_show_version_4); + tcase_add_test (tc_nonblock, test_show_version_5); + tcase_add_test (tc_nonblock, test_loopbacks_2); + tcase_add_test (tc_nonblock, test_stats_3); + tcase_add_test (tc_nonblock, test_no_response_1); + tcase_add_test (tc_nonblock, test_no_response_2); + suite_add_tcase (s, tc_nonblock); + + 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/scripts/test-loop.sh b/test/scripts/test-loop.sh index 17dc7c39..51f5d5ce 100755 --- a/test/scripts/test-loop.sh +++ b/test/scripts/test-loop.sh @@ -3,14 +3,15 @@ function usage() { echo "$0" 1>&2 echo "" 1>&2 - echo "Usage: $0 [-p ] [-m ] -- " 1>&2 + echo "Usage: $0 [-p ] [-m ] -- " 1>&2 echo "" 1>&2 echo "Parameters:" 1>&2 echo " -p - run a command before each test loop (e.g. 'git pull')" 1>&2 echo " -m - if set, email is sent to this address on failure" 1>&2 echo "" 1>&2 - echo "Example:" 1>&2 - echo " $0 -m -- test-debug TEST=l2bd" + echo "Examples:" 1>&2 + echo " $0 -m -- test-debug TEST=l2bd" 1>&2 + echo " $0 -m -- verify" 1>&2 exit 1; } @@ -44,8 +45,11 @@ shift $((OPTIND-1)) if ! echo $* | grep test >/dev/null then - echo "Error: command line doesn't look right - should contain \`test' token..." >&2 - usage + if ! echo $* | grep verify >/dev/null + then + echo "Error: command line doesn't look right - should contain \`test' or \`verify' token..." >&2 + usage + fi fi function finish { diff --git a/test/test_vapi.py b/test/test_vapi.py new file mode 100644 index 00000000..86c1ee06 --- /dev/null +++ b/test/test_vapi.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +""" VAPI test """ + +from __future__ import division +import unittest +import os +import signal +import subprocess +from threading import Thread +from log import single_line_delim +from framework import VppTestCase, running_extended_tests, VppTestRunner + + +class Worker(Thread): + def __init__(self, args, logger): + self.logger = logger + self.args = args + self.result = None + super(Worker, self).__init__() + + def run(self): + executable = self.args[0] + self.logger.debug("Running executable w/args `%s'" % self.args) + env = os.environ.copy() + env["CK_LOG_FILE_NAME"] = "-" + self.process = subprocess.Popen( + self.args, shell=False, env=env, preexec_fn=os.setpgrp, + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = self.process.communicate() + self.logger.debug("Finished running `%s'" % executable) + self.logger.info("Return code is `%s'" % self.process.returncode) + self.logger.info(single_line_delim) + self.logger.info("Executable `%s' wrote to stdout:" % executable) + self.logger.info(single_line_delim) + self.logger.info(out) + self.logger.info(single_line_delim) + self.logger.info("Executable `%s' wrote to stderr:" % executable) + self.logger.info(single_line_delim) + self.logger.error(err) + self.logger.info(single_line_delim) + self.result = self.process.returncode + + +@unittest.skipUnless(running_extended_tests(), "part of extended tests") +class VAPITestCase(VppTestCase): + """ VAPI test """ + + def test_vapi(self): + """ run 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 + worker = Worker( + [executable, "vapi client", self.shm_prefix], self.logger) + worker.start() + timeout = 45 + 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") + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) -- cgit 1.2.3-korg From 3d67449de523521a9bdbcd2e3a092ffc0e2281f7 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Mon, 25 Sep 2017 15:37:56 -0400 Subject: Refactor multi-host socket_test.sh for bare-metal. Change-Id: I4fcde6652e0c66315a453250c6e02cd32176833d Signed-off-by: Dave Wallace --- extras/vagrant/vcl_test.sh | 0 test/scripts/socket_test.sh | 69 +++++++++++++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 18 deletions(-) mode change 100644 => 100755 extras/vagrant/vcl_test.sh (limited to 'test/scripts') diff --git a/extras/vagrant/vcl_test.sh b/extras/vagrant/vcl_test.sh old mode 100644 new mode 100755 diff --git a/test/scripts/socket_test.sh b/test/scripts/socket_test.sh index 1573b48a..39454557 100755 --- a/test/scripts/socket_test.sh +++ b/test/scripts/socket_test.sh @@ -33,6 +33,7 @@ tmp_cmdfile_prefix="/tmp/socket_test_cmd" cmd1_file="${tmp_cmdfile_prefix}1.$$" cmd2_file="${tmp_cmdfile_prefix}2.$$" cmd3_file="${tmp_cmdfile_prefix}3.$$" +vpp_eth_name="enp0s8" tmp_vpp_exec_file="/tmp/vpp_config.$$" tmp_gdb_cmdfile_prefix="/tmp/gdb_cmdfile" def_gdb_cmdfile_prefix="$WS_ROOT/extras/gdb/gdb_cmdfile" @@ -68,6 +69,8 @@ OPTIONS: -d Run the vpp_debug version of all apps. -c Set VPPCOM_CONF to use the vppcom_test.conf file. -i Run iperf3 for client/server app in native tests. + -n Name of ethernet for VPP to use in multi-host cfg. + -6 Use ipv6 addressing. -m c[lient] Run client in multi-host cfg (server on remote host) s[erver] Run server in multi-host cfg (client on remote host) -e a[ll] Run all in emacs+gdb. @@ -114,8 +117,9 @@ declare -i perf_server=0 declare -i leave_tmp_files=0 declare -i bash_after_exit=0 declare -i iperf3=0 +declare -i use_ipv6=0 -while getopts ":hitlbcdm:e:g:p:E:I:N:P:R:S:T:UBVX" opt; do +while getopts ":hitlbcd6n:m:e:g:p:E:I:N:P:R:S:T:UBVX" opt; do case $opt in h) usage ;; l) leave_tmp_files=1 @@ -124,12 +128,15 @@ while getopts ":hitlbcdm:e:g:p:E:I:N:P:R:S:T:UBVX" opt; do ;; i) iperf3=1 ;; + 6) use_ipv6=1 + ;; t) xterm_geom="180x40" use_tabs="true" ;; c) VPPCOM_CONF="${vppcom_conf_dir}vppcom_test.conf" ;; d) title_dbg="-DEBUG" + _debug="_debug" vpp_dir=$vpp_debug_dir lib64_dir=$lib64_debug_dir ;; @@ -151,6 +158,8 @@ while getopts ":hitlbcdm:e:g:p:E:I:N:P:R:S:T:UBVX" opt; do vpp_dir=$vpp_debug_dir lib64_dir=$lib64_debug_dir ;; + n) vpp_eth_name="$OPTARG" + ;; m) if [ $OPTARG = "c" ] || [ $OPTARG = "client" ] ; then multi_host="client" elif [ $OPTARG = "s" ] || [ $OPTARG = "server" ] ; then @@ -245,6 +254,11 @@ if [ -z "$WS_ROOT" ] ; then exit 1 fi +if [[ "$(grep bin_PROGRAMS $WS_ROOT/src/uri.am)" = "" ]] ; then + $WS_ROOT/extras/vagrant/vcl_test.sh $WS_ROOT $USER + (cd $WS_ROOT; make build) +fi + if [ ! -d $vpp_dir ] ; then echo "ERROR: Missing VPP$DEBUG bin directory!" >&2 echo " $vpp_dir" >&2 @@ -325,6 +339,7 @@ if [ -z "$api_segment" ] ; then api_segment=" api-segment { gid $user_gid }" fi if [ -n "$multi_host" ] ; then + sudo modprobe uio_pci_generic vpp_args="unix { interactive exec $tmp_vpp_exec_file}${api_segment}" else vpp_args="unix { interactive }${api_segment}" @@ -347,16 +362,21 @@ else fi verify_no_vpp() { - local running_vpp="ps -eaf|grep -v grep|grep \"bin/vpp\"" - if [ "$(eval $running_vpp)" != "" ] ; then - echo "ERROR: Please kill all running vpp instances:" + local grep_for_vpp="ps -eaf|grep -v grep|grep \"bin/vpp\"" + + if [ -n "$api_prefix" ] ; then + grep_for_vpp="$grep_for_vpp|grep \"prefix $api_prefix\"" + fi + local running_vpp="$(eval $grep_for_vpp)" + if [ -n "$running_vpp" ] ; then + echo "ERROR: Please kill the following vpp instance(s):" echo - eval $running_vpp + echo $running_vpp echo exit 1 fi clean_devshm="$vpp_shm_dir*db $vpp_shm_dir*global_vm $vpp_shm_dir*vpe-api $vpp_shm_dir[0-9]*-[0-9]* $vpp_shm_dir*:segment[0-9]*" - rm -f $clean_devshm + sudo rm -f $clean_devshm devshm_files="$(ls -l $clean_devshm 2>/dev/null | grep $(whoami))" if [ "$devshm_files" != "" ] ; then echo "ERROR: Please remove the following $vpp_shm_dir files:" @@ -370,7 +390,6 @@ verify_no_vpp() { sudo chown root:$USER $vpp_run_dir fi if [ -n "$multi_host" ] ; then - vpp_eth_name="enp0s8" vpp_eth_pci_id="$(ls -ld /sys/class/net/$vpp_eth_name/device | awk '{print $11}' | cut -d/ -f4)" if [ -z "$vpp_eth_pci_id" ] ; then echo "ERROR: Missing ethernet interface $vpp_eth_name!" @@ -390,6 +409,8 @@ verify_no_vpp() { vpp_eth_ifname="GigabitEthernet$bus/$slot/$func" ;; ixgbe) vpp_eth_ifname="TenGigabitEthernet$bus/$slot/$func" ;; + i40e) + vpp_eth_ifname="FortyGigabitEthernet$bus/$slot/$func" ;; *) echo "ERROR: Unknown ethernet kernel driver $vpp_eth_kernel_driver!" usage ;; @@ -397,17 +418,19 @@ verify_no_vpp() { vpp_eth_ip4_addr="$(ip -4 -br addr show $vpp_eth_name | awk '{print $3}')" if [ -z "$vpp_eth_ip4_addr" ] ; then - echo "ERROR: No inet address configured for $vpp_eth_name!" - usage + if [ "$multi_host" = "server" ] ; then + vpp_eth_ip4_addr="10.10.10.10/24" + else + vpp_eth_ip4_addr="10.10.10.11/24" + fi fi - vpp_eth_ip6_addr="$(ip -6 -br addr show $vpp_eth_name | awk '{print $3}')" - if [ -z "$vpp_eth_ip6_addr" ] ; then + if [ $use_ipv6 -eq 1 ] && [ -z "$vpp_eth_ip6_addr" ] ; then echo "ERROR: No inet6 address configured for $vpp_eth_name!" usage fi - vpp_args="$vpp_args plugins { path $lib64_dir/vpp_plugins } dpdk { dev $vpp_eth_pci_id }" + vpp_args="$vpp_args plugins { path ${lib64_dir}vpp_plugins } dpdk { dev $vpp_eth_pci_id }" - sudo ifdown $vpp_eth_name 2> /dev/null + sudo ifconfig $vpp_eth_name down 2> /dev/null echo "Configuring VPP to use $vpp_eth_name ($vpp_eth_pci_id), inet addr $vpp_eth_ip4_addr" cat <> $tmp_vpp_exec_file @@ -465,15 +488,15 @@ write_script_header() { echo "$bash_header" > $1 echo -e "#\n# $1 generated on $(date)\n#" >> $1 if [ $leave_tmp_files -eq 0 ] ; then - if [ -n "$multi_host" ] && [[ "$3" == VPP* ]] ; then - echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file; sudo $dpdk_devbind -b $vpp_eth_kernel_driver $vpp_eth_pci_id; sudo ifup $vpp_eth_name\" $trap_signals" >> $1 + if [ -n "$multi_host" ] ; then + echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file; sudo $dpdk_devbind -b $vpp_eth_kernel_driver $vpp_eth_pci_id; sudo ifconfig $vpp_eth_name up\" $trap_signals" >> $1 else echo "trap \"rm -f $1 $2 $tmp_vpp_exec_file\" $trap_signals" >> $1 fi fi echo "export VPPCOM_CONF=${vppcom_conf_dir}${vppcom_conf}" >> $1 if [ "$pre_cmd" = "$gdb_in_emacs " ] ; then - if [ -n "$multi_host" ] ; then + if [ -n "$multi_host" ] && [[ $3 =~ "VPP".* ]] ; then cat <> $1 $gdb_in_emacs() { sudo emacs --eval "(gdb \"gdb -x $2 -i=mi --args \$*\")" --eval "(setq frame-title-format \"$3\")" @@ -618,7 +641,12 @@ native_vcl() { tmp_gdb_cmdfile=$tmp_gdb_cmdfile_server gdb_cmdfile=$VPPCOM_SERVER_GDB_CMDFILE set_pre_cmd $emacs_server $gdb_server - write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "sleep 2" + if [ "$multi_host" = "server" ] ; then + delay="sleep 10" + else + delay="sleep 3" + fi + write_script_header $cmd2_file $tmp_gdb_cmdfile "$title2" "$delay" echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd2_file echo "${pre_cmd}${app_dir}${srvr_app}" >> $cmd2_file write_script_footer $cmd2_file $perf_server @@ -630,7 +658,12 @@ native_vcl() { tmp_gdb_cmdfile=$tmp_gdb_cmdfile_client gdb_cmdfile=$VPPCOM_CLIENT_GDB_CMDFILE set_pre_cmd $emacs_client $gdb_client - write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "sleep 3" + if [ "$multi_host" = "client" ] ; then + delay="sleep 10" + else + delay="sleep 3" + fi + write_script_header $cmd3_file $tmp_gdb_cmdfile "$title3" "$delay" echo "export LD_LIBRARY_PATH=\"$lib64_dir:$LD_LIBRARY_PATH\"" >> $cmd3_file echo "srvr_addr=\"$sock_srvr_addr\"" >> $cmd3_file echo "${pre_cmd}${app_dir}${clnt_app}" >> $cmd3_file -- cgit 1.2.3-korg From e691345d7a888ad57848c86b86211192e07d5de7 Mon Sep 17 00:00:00 2001 From: Dave Wallace Date: Sat, 30 Sep 2017 01:53:26 -0400 Subject: make test: archive failed test data with build logs. (VPP-1011) - Fix invocation of compress_failed.sh - Fix compress_failed to copy compressed results files to $WORKSPACE/archives and return failure exit code. Failed test case data will be copied to logs.fd.io and found in the archives/-FAILED directory in the build log link in the vpp-verify-master-ubuntu1604 jenkins job page. For example: https://logs.fd.io/production/vex-yul-rot-jenkins-1/vpp-verify-master-ubuntu1604/7353/archives/ Change-Id: Ife9a0737115e69c0a8441e3bb0133af1528d909b Signed-off-by: Dave Wallace (cherry picked from commit 25dc16715ee3fc0a600e2f58841173249bfae501) --- test/Makefile | 3 +-- test/scripts/compress_failed.sh | 46 +++++++++++++++++++++++++++-------------- 2 files changed, 32 insertions(+), 17 deletions(-) (limited to 'test/scripts') diff --git a/test/Makefile b/test/Makefile index 721ec963..da77accc 100644 --- a/test/Makefile +++ b/test/Makefile @@ -86,8 +86,7 @@ $(PAPI_INSTALL_DONE): $(PIP_PATCH_DONE) @touch $@ define retest-func - @env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) scripts/setsid_wrapper.sh $(FORCE_FOREGROUND) $(PYTHON_VENV_PATH)/bin/activate python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS) - @env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) scripts/compress_failed.sh + @env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) scripts/setsid_wrapper.sh $(FORCE_FOREGROUND) $(PYTHON_VENV_PATH)/bin/activate python run_tests.py -d $(TEST_DIR) $(UNITTEST_EXTRA_OPTS) || env VPP_TEST_FAILED_DIR=$(VPP_TEST_FAILED_DIR) COMPRESS_FAILED_TEST_LOGS=$(COMPRESS_FAILED_TEST_LOGS) scripts/compress_failed.sh endef .PHONY: sanity diff --git a/test/scripts/compress_failed.sh b/test/scripts/compress_failed.sh index 6076b3b3..9559e2ac 100755 --- a/test/scripts/compress_failed.sh +++ b/test/scripts/compress_failed.sh @@ -2,20 +2,36 @@ if [ "$(ls -A ${VPP_TEST_FAILED_DIR})" ] then - if [ "${COMPRESS_FAILED_TEST_LOGS}" == "yes" ] - then - echo -n "Compressing files in temporary directories from failed test runs..." - cd ${VPP_TEST_FAILED_DIR} - for d in * - do - cd ${d} - find . ! -path . -print0 | xargs -0 -n1 gzip - cd ${VPP_TEST_FAILED_DIR} - done - echo "done." - else - echo "Not compressing files in temporary directories from failed test runs." - fi + if [ "${COMPRESS_FAILED_TEST_LOGS}" == "yes" ] + then + echo -n "Compressing files in temporary directories from failed test runs... " + cd ${VPP_TEST_FAILED_DIR} + for d in * + do + cd ${d} + find . ! -path . -print0 | xargs -0 -n1 gzip + cd ${VPP_TEST_FAILED_DIR} + done + echo "done." + if [ -n "$WORKSPACE" ] + then + echo "Copying failed test logs into build log archive directory ($WORKSPACE/archives)... " + for failed_test in $(ls $VPP_TEST_FAILED_DIR) + do + mkdir -p $WORKSPACE/archives/$failed_test + cp -a $VPP_TEST_FAILED_DIR/$failed_test/* $WORKSPACE/archives/$failed_test + done + echo "done." + fi + + else + echo "Not compressing files in temporary directories from failed test runs." + fi else - echo "No symlinks to failed tests' temporary directories found in ${VPP_TEST_FAILED_DIR}." + echo "No symlinks to failed tests' temporary directories found in ${VPP_TEST_FAILED_DIR}." fi + +# This script gets run only if there was a 'make test' failure, +# so return failure error status so that the build results are +# recorded correctly. +exit 1 -- cgit 1.2.3-korg