diff options
author | Michal Cmarada <mcmarada@cisco.com> | 2019-01-23 09:05:07 +0100 |
---|---|---|
committer | Michal Cmarada <mcmarada@cisco.com> | 2019-01-23 09:05:07 +0100 |
commit | 21f3798c7938d070725816214368394e6a16bd04 (patch) | |
tree | 27c67704c37f612bd6ef938051c6d3cc982b3127 | |
parent | 7ec8cd1b967fdbd07482acd2d49c57fb64b26c3b (diff) |
initial commit19.04-rc0
- migrates source code from VPP code base
- modifies cmake configuration
- adds cpack configuration for deb and rpm packaging
- adds documentation
Change-Id: Ib6a39030f6c47c8ae654fcb886c618e159fb63b4
Signed-off-by: Michal Cmarada <mcmarada@cisco.com>
125 files changed, 12703 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6533a7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.idea +/build-root/lib +/build-root/packages +/build-root/.deps.ok +/target +**/core +/Makefile + +cmake-build*/ +_CPack_Packages/ +CMakeFiles/ +CMakeCache.txt +install_manifest.txt +*.cmake +*.cbp +*.pyc diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..422d3ba --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=gerrit.fd.io +port=29418 +project=jvpp diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..427f31b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,111 @@ +# Copyright (c) 2018 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. + +cmake_minimum_required(VERSION 3.5 FATAL_ERROR) +add_custom_target(install-dep COMMAND make install-dep + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/Requirements + ) + +project(jvpp) + +include(CheckCCompilerFlag) + +if(CMAKE_SYSTEM_PROCESSOR MATCHES "amd64.*|x86_64.*|AMD64.*") + set(CMAKE_C_FLAGS "-march=corei7 -mtune=corei7-avx ${CMAKE_C_FLAGS}") +elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "^(aarch64.*|AARCH64.*)") + set(CMAKE_C_FLAGS "-march=armv8-a+crc ${CMAKE_C_FLAGS}") +endif() + +check_c_compiler_flag("-Wno-address-of-packed-member" compiler_flag_no_address_of_packed_member) +if (compiler_flag_no_address_of_packed_member) + add_definitions(-Wno-address-of-packed-member) +endif() + +message("\nEnvironment Variables:") + +# JVPP RELATED VARIABLES +unset(JVPP_VERSION) +unset(JAPI_LIB_VERSION) +execute_process( + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/ + COMMAND ./version + OUTPUT_VARIABLE JVPP_VERSION + OUTPUT_STRIP_TRAILING_WHITESPACE +) +string(REPLACE "-" ";" JAPI_LIB_VERSION ${JVPP_VERSION}) +list(GET JAPI_LIB_VERSION 0 JAPI_LIB_VERSION) + +message(" JVPP Main Version: ${JVPP_VERSION}") +message(" JVPP Version: ${JAPI_LIB_VERSION}") + +# OS RELATED VARIABLES +unset(RELEASE_ID) +unset(RELEASE_CODENAME) +find_program(LSB_RELEASE_EXEC lsb_release) +execute_process(COMMAND ${LSB_RELEASE_EXEC} -is + OUTPUT_VARIABLE RELEASE_ID + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + +execute_process(COMMAND ${LSB_RELEASE_EXEC} -cs + OUTPUT_VARIABLE RELEASE_CODENAME + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + +message(" OS Release Id: ${RELEASE_ID} ") +message(" OS Release Codename: ${RELEASE_CODENAME} ") + + +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build-root/lib) +set(CMAKE_INSTALL_RPATH ${CMAKE_INSTALL_PREFIX}/lib) +set(CMAKE_INSTALL_MESSAGE NEVER) + +find_package(Threads REQUIRED) + +unset(dirlist) + +macro(subdirlist dirlist dirpath) + file(GLOB dirs RELATIVE ${dirpath} ${dirpath}/*) + foreach(dir ${dirs}) + if(IS_DIRECTORY ${dirpath}/${dir}) + list(APPEND dirlist ${dirpath}/${dir}) + endif() + endforeach() +endmacro() + +#list(APPEND dirlist $ENV{JAVA_HOME}) +#subdirlist(dirlist /usr/lib/jvm) +#subdirlist(dirlist /usr/lib64/jvm) +# +#unset(JAVA_HOME_SET) +#find_path(JAVA_HOME_SET NAMES include/jni.h PATHS ${dirlist}) +#if (NOT JAVA_HOME_SET) +# message("JAVA_HOME is not found") +#else() +# message("JAVA HOME: ${JAVA_HOME}") +# set(ENV{JAVA_HOME} "${JAVA_HOME_SET}") +#endif() + +message("\nJAVA:") +message(" JAVA_HOME: $ENV{JAVA_HOME}") + +find_package(Java 1.8 REQUIRED COMPONENTS Development) +message("JAVA: ${Java}") +get_filename_component(jvm_path ${Java_JAVAC_EXECUTABLE} DIRECTORY) +set (Java_INCLUDE_DIRS ${jvm_path}/../include ${jvm_path}/../include/linux) + +message(" Java headers: ${Java_INCLUDE_DIRS}") +message(" Java compiler: ${Java_JAVAC_EXECUTABLE}") +add_subdirectory(java) + +#find_package(vpp REQUIRED) diff --git a/Requierements/Makefile b/Requierements/Makefile new file mode 100644 index 0000000..6be83fd --- /dev/null +++ b/Requierements/Makefile @@ -0,0 +1,268 @@ +# Copyright (c) 2018 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. + +export WS_ROOT=$(CURDIR) +export BR=$(WS_ROOT)/build-root +CCACHE_DIR?=$(BR)/.ccache +GDB?=gdb +PLATFORM?=vpp +SAMPLE_PLUGIN?=no +STARTUP_DIR?=$(PWD) +MACHINE=$(shell uname -m) +SUDO?=sudo + +,:=, +define disable_plugins +$(if $(1), \ + "plugins {" \ + $(patsubst %,"plugin %_plugin.so { disable }",$(subst $(,), ,$(1))) \ + " }" \ + ,) +endef + +MINIMAL_STARTUP_CONF=" \ +unix { \ + interactive \ + cli-listen /run/vpp/cli.sock \ + gid $(shell id -g) \ + $(if $(wildcard startup.vpp),"exec startup.vpp",) \ +} \ +$(if $(DPDK_CONFIG), "dpdk { $(DPDK_CONFIG) }",) \ +$(call disable_plugins,$(DISABLED_PLUGINS)) \ +" + +GDB_ARGS= -ex "handle SIGUSR1 noprint nostop" + +# +# OS Detection +# +# We allow Darwin (MacOS) for docs generation; VPP build will still fail. +ifneq ($(shell uname),Darwin) +OS_ID = $(shell grep '^ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') +OS_VERSION_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g') +endif + +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) +PKG=deb +else ifeq ($(filter rhel centos fedora opensuse opensuse-leap opensuse-tumbleweed,$(OS_ID)),$(OS_ID)) +PKG=rpm +endif + +# +libganglia1-dev if building the gmond plugin + +DEB_DEPENDS = curl build-essential autoconf automake 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 indent clang-format libnuma-dev +DEB_DEPENDS += python-all python-dev python-virtualenv python-pip libffi6 check +DEB_DEPENDS += libboost-all-dev libffi-dev python-ply libmbedtls-dev +DEB_DEPENDS += cmake ninja-build + +# ADD JDK for JVPP +ifeq ($(OS_VERSION_ID),14.04) + DEB_DEPENDS += openjdk-8-jdk-headless + DEB_DEPENDS += libssl-dev +else ifeq ($(OS_ID)-$(OS_VERSION_ID),debian-8) + DEB_DEPENDS += openjdk-8-jdk-headless + DEB_DEPENDS += libssl-dev + APT_ARGS = -t jessie-backports +else ifeq ($(OS_ID)-$(OS_VERSION_ID),debian-9) + DEB_DEPENDS += default-jdk-headless + DEB_DEPENDS += libssl1.0-dev +else + DEB_DEPENDS += default-jdk-headless + DEB_DEPENDS += libssl-dev +endif + +# ADD JDK for JVPP +RPM_DEPENDS = redhat-lsb glibc-static java-1.8.0-openjdk-devel yum-utils + +RPM_DEPENDS += apr-devel +RPM_DEPENDS += numactl-devel +RPM_DEPENDS += check check-devel +RPM_DEPENDS += boost boost-devel +RPM_DEPENDS += selinux-policy selinux-policy-devel +RPM_DEPENDS += cmake3 ninja-build + +ifeq ($(OS_ID)-$(OS_VERSION_ID),fedora-25) + RPM_DEPENDS += subunit subunit-devel + RPM_DEPENDS += openssl-devel + RPM_DEPENDS += python-devel python2-ply + RPM_DEPENDS += python2-virtualenv + RPM_DEPENDS += mbedtls-devel + RPM_DEPENDS_GROUPS = 'C Development Tools and Libraries' +else ifeq ($(shell if [ "$(OS_ID)" = "fedora" ]; then test $(OS_VERSION_ID) -gt 25; echo $$?; fi),0) + RPM_DEPENDS += subunit subunit-devel + RPM_DEPENDS += compat-openssl10-devel + RPM_DEPENDS += python2-devel python2-ply + RPM_DEPENDS += python2-virtualenv + RPM_DEPENDS += mbedtls-devel + RPM_DEPENDS_GROUPS = 'C Development Tools and Libraries' +else + RPM_DEPENDS += openssl-devel + RPM_DEPENDS += python-devel python-ply + RPM_DEPENDS += python-virtualenv + RPM_DEPENDS += devtoolset-7 + RPM_DEPENDS_GROUPS = 'Development Tools' +endif + +# +ganglia-devel if building the ganglia plugin + +RPM_DEPENDS += chrpath libffi-devel rpm-build + +SUSE_NAME= $(shell grep '^NAME=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g' | cut -d' ' -f2) +SUSE_ID= $(shell grep '^VERSION_ID=' /etc/os-release | cut -f2- -d= | sed -e 's/\"//g' | cut -d' ' -f2) +RPM_SUSE_BUILDTOOLS_DEPS = autoconf automake ccache check-devel chrpath +RPM_SUSE_BUILDTOOLS_DEPS += clang cmake indent libtool make ninja python-ply + +# ADD JDK for JVPP +RPM_SUSE_DEVEL_DEPS = glibc-devel-static java-1_8_0-openjdk-devel libnuma-devel + +RPM_SUSE_DEVEL_DEPS += libopenssl-devel openssl-devel mbedtls-devel + +RPM_SUSE_PYTHON_DEPS = python-devel python3-devel python-pip python3-pip +RPM_SUSE_PYTHON_DEPS += python-rpm-macros python3-rpm-macros + +RPM_SUSE_PLATFORM_DEPS = distribution-release shadow rpm-build + +ifeq ($(OS_ID),opensuse) +ifeq ($(SUSE_NAME),Tumbleweed) + RPM_SUSE_DEVEL_DEPS = libboost_headers-devel libboost_thread-devel gcc + RPM_SUSE_PYTHON_DEPS += python2-ply python2-virtualenv +endif +ifeq ($(SUSE_ID),15.0) + RPM_SUSE_DEVEL_DEPS = libboost_headers-devel libboost_thread-devel gcc6 + RPM_SUSE_PYTHON_DEPS += python2-ply python2-virtualenv +else + RPM_SUSE_DEVEL_DEPS += boost_1_61-devel gcc6 + RPM_SUSE_PYTHON_DEPS += python-virtualenv +endif +endif + +ifeq ($(OS_ID),opensuse-leap) +ifeq ($(SUSE_ID),15.0) + RPM_SUSE_DEVEL_DEPS = libboost_headers-devel libboost_thread-devel gcc6 + RPM_SUSE_PYTHON_DEPS += python2-ply python2-virtualenv +endif +endif + +RPM_SUSE_DEPENDS += $(RPM_SUSE_BUILDTOOLS_DEPS) $(RPM_SUSE_DEVEL_DEPS) $(RPM_SUSE_PYTHON_DEPS) $(RPM_SUSE_PLATFORM_DEPS) + +ifneq ($(wildcard $(STARTUP_DIR)/startup.conf),) + STARTUP_CONF ?= $(STARTUP_DIR)/startup.conf +endif + +ifeq ($(findstring y,$(UNATTENDED)),y) +CONFIRM=-y +FORCE=--force-yes +endif + +TARGETS = vpp + +ifneq ($(SAMPLE_PLUGIN),no) +TARGETS += sample-plugin +endif + +.PHONY: help wipe wipe-release build build-release rebuild rebuild-release +.PHONY: run run-release debug debug-release build-vat run-vat pkg-deb pkg-rpm +.PHONY: ctags cscope +.PHONY: test test-debug retest retest-debug test-doc test-wipe-doc test-help test-wipe +.PHONY: test-cov test-wipe-cov + +define banner + @echo "========================================================================" + @echo " $(1)" + @echo "========================================================================" + @echo " " +endef + +help: + @echo "Make Targets:" + @echo " install-dep - install software dependencies" + @echo "" + +$(BR)/.deps.ok: +ifeq ($(findstring y,$(UNATTENDED)),y) + make install-dep +endif +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) + @MISSING=$$(apt-get install -y -qq -s $(DEB_DEPENDS) | grep "^Inst ") ; \ + if [ -n "$$MISSING" ] ; then \ + echo "\nPlease install missing packages: \n$$MISSING\n" ; \ + echo "by executing \"make install-dep\"\n" ; \ + exit 1 ; \ + fi ; \ + exit 0 +else ifneq ("$(wildcard /etc/redhat-release)","") + @for i in $(RPM_DEPENDS) ; do \ + RPM=$$(basename -s .rpm "$${i##*/}" | cut -d- -f1,2,3) ; \ + MISSING+=$$(rpm -q $$RPM | grep "^package") ; \ + done ; \ + if [ -n "$$MISSING" ] ; then \ + echo "Please install missing RPMs: \n$$MISSING\n" ; \ + echo "by executing \"make install-dep\"\n" ; \ + exit 1 ; \ + fi ; \ + exit 0 +endif + @touch $@ + +install-dep: +ifeq ($(filter ubuntu debian,$(OS_ID)),$(OS_ID)) +ifeq ($(OS_VERSION_ID),14.04) + @sudo -E apt-get $(CONFIRM) $(FORCE) install software-properties-common + @sudo -E add-apt-repository ppa:openjdk-r/ppa $(CONFIRM) +endif +ifeq ($(OS_ID)-$(OS_VERSION_ID),debian-8) + @grep -q jessie-backports /etc/apt/sources.list /etc/apt/sources.list.d/* 2> /dev/null \ + || ( echo "Please install jessie-backports" ; exit 1 ) +endif + @sudo -E apt-get update + @sudo -E apt-get $(APT_ARGS) $(CONFIRM) $(FORCE) install $(DEB_DEPENDS) +else ifneq ("$(wildcard /etc/redhat-release)","") +ifeq ($(OS_ID),rhel) + @sudo -E yum-config-manager --enable rhel-server-rhscl-7-rpms +else ifeq ($(OS_ID),centos) + @sudo -E yum install $(CONFIRM) centos-release-scl-rh +endif + @sudo -E yum groupinstall $(CONFIRM) $(RPM_DEPENDS_GROUPS) + @sudo -E yum install $(CONFIRM) $(RPM_DEPENDS) + @sudo -E debuginfo-install $(CONFIRM) glibc openssl-libs mbedtls-devel zlib +else ifeq ($(filter opensuse-tumbleweed,$(OS_ID)),$(OS_ID)) + @sudo -E zypper refresh + @sudo -E zypper install -y $(RPM_SUSE_DEPENDS) +else ifeq ($(filter opensuse-leap,$(OS_ID)),$(OS_ID)) + @sudo -E zypper refresh + @sudo -E zypper install -y $(RPM_SUSE_DEPENDS) +else ifeq ($(filter opensuse,$(OS_ID)),$(OS_ID)) + @sudo -E zypper refresh + @sudo -E zypper install -y $(RPM_SUSE_DEPENDS) +else + $(error "This option currently works only on Ubuntu, Debian, RHEL, CentOS or openSUSE systems") +endif + +define make + @make -C $(BR) PLATFORM=$(PLATFORM) TAG=$(1) $(2) +endef + +$(BR)/scripts/.version: +ifneq ("$(wildcard /etc/redhat-release)","") + $(shell $(BR)/scripts/version rpm-string > $(BR)/scripts/.version) +else + $(shell $(BR)/scripts/version > $(BR)/scripts/.version) +endif + +DIST_FILE = $(BR)/vpp-$(shell src/scripts/version).tar +DIST_SUBDIR = vpp-$(shell src/scripts/version|cut -f1 -d-) + + diff --git a/Test/framework.py b/Test/framework.py new file mode 100644 index 0000000..54b7a2d --- /dev/null +++ b/Test/framework.py @@ -0,0 +1,1324 @@ +#!/usr/bin/env python + +from __future__ import print_function +import gc +import sys +import os +import select +import unittest +import tempfile +import time +import faulthandler +import random +import copy +import psutil +from collections import deque +from threading import Thread, Event +from inspect import getdoc, isclass +from traceback import format_exception +from logging import FileHandler, DEBUG, Formatter +from scapy.packet import Raw +from hook import StepHook, PollHook, VppDiedError +from vpp_pg_interface import VppPGInterface +from vpp_sub_interface import VppSubInterface +from vpp_lo_interface import VppLoInterface +from vpp_papi_provider import VppPapiProvider +from vpp_papi.vpp_stats import VPPStats +from log import RED, GREEN, YELLOW, double_line_delim, single_line_delim, \ + get_logger, colorize +from vpp_object import VppObjectRegistry +from util import ppp, is_core_present +from scapy.layers.inet import IPerror, TCPerror, UDPerror, ICMPerror +from scapy.layers.inet6 import ICMPv6DestUnreach, ICMPv6EchoRequest +from scapy.layers.inet6 import ICMPv6EchoReply +if os.name == 'posix' and sys.version_info[0] < 3: + # using subprocess32 is recommended by python official documentation + # @ https://docs.python.org/2/library/subprocess.html + import subprocess32 as subprocess +else: + import subprocess + + +PASS = 0 +FAIL = 1 +ERROR = 2 +SKIP = 3 +TEST_RUN = 4 + + +debug_framework = False +if os.getenv('TEST_DEBUG', "0") == "1": + debug_framework = True + import debug_internal + + +""" + Test framework module. + + The module provides a set of tools for constructing and running tests and + representing the results. +""" + + +class _PacketInfo(object): + """Private class to create packet info object. + + Help process information about the next packet. + Set variables to default values. + """ + #: Store the index of the packet. + index = -1 + #: Store the index of the source packet generator interface of the packet. + src = -1 + #: Store the index of the destination packet generator interface + #: of the packet. + dst = -1 + #: Store expected ip version + ip = -1 + #: Store expected upper protocol + proto = -1 + #: Store the copy of the former packet. + data = None + + def __eq__(self, other): + index = self.index == other.index + src = self.src == other.src + dst = self.dst == other.dst + data = self.data == other.data + return index and src and dst and data + + +def pump_output(testclass): + """ pump output from vpp stdout/stderr to proper queues """ + stdout_fragment = "" + stderr_fragment = "" + while not testclass.pump_thread_stop_flag.is_set(): + readable = select.select([testclass.vpp.stdout.fileno(), + testclass.vpp.stderr.fileno(), + testclass.pump_thread_wakeup_pipe[0]], + [], [])[0] + if testclass.vpp.stdout.fileno() in readable: + read = os.read(testclass.vpp.stdout.fileno(), 102400) + if len(read) > 0: + split = read.splitlines(True) + if len(stdout_fragment) > 0: + split[0] = "%s%s" % (stdout_fragment, split[0]) + if len(split) > 0 and split[-1].endswith("\n"): + limit = None + else: + limit = -1 + stdout_fragment = split[-1] + testclass.vpp_stdout_deque.extend(split[:limit]) + if not testclass.cache_vpp_output: + for line in split[:limit]: + testclass.logger.debug( + "VPP STDOUT: %s" % line.rstrip("\n")) + if testclass.vpp.stderr.fileno() in readable: + read = os.read(testclass.vpp.stderr.fileno(), 102400) + if len(read) > 0: + split = read.splitlines(True) + if len(stderr_fragment) > 0: + split[0] = "%s%s" % (stderr_fragment, split[0]) + if len(split) > 0 and split[-1].endswith("\n"): + limit = None + else: + limit = -1 + stderr_fragment = split[-1] + testclass.vpp_stderr_deque.extend(split[:limit]) + if not testclass.cache_vpp_output: + for line in split[:limit]: + testclass.logger.debug( + "VPP STDERR: %s" % line.rstrip("\n")) + # ignoring the dummy pipe here intentionally - the flag will take care + # of properly terminating the loop + + +def running_extended_tests(): + s = os.getenv("EXTENDED_TESTS", "n") + return True if s.lower() in ("y", "yes", "1") else False + + +def running_on_centos(): + os_id = os.getenv("OS_ID", "") + return True if "centos" in os_id.lower() else False + + +class KeepAliveReporter(object): + """ + Singleton object which reports test start to parent process + """ + _shared_state = {} + + def __init__(self): + self.__dict__ = self._shared_state + self._pipe = None + + @property + def pipe(self): + return self._pipe + + @pipe.setter + def pipe(self, pipe): + if self._pipe is not None: + raise Exception("Internal error - pipe should only be set once.") + self._pipe = pipe + + def send_keep_alive(self, test, desc=None): + """ + Write current test tmpdir & desc to keep-alive pipe to signal liveness + """ + if self.pipe is None: + # if not running forked.. + return + + if isclass(test): + desc = '%s (%s)' % (desc, unittest.util.strclass(test)) + else: + desc = test.id() + + self.pipe.send((desc, test.vpp_bin, test.tempdir, test.vpp.pid)) + + +class VppTestCase(unittest.TestCase): + """This subclass is a base class for VPP test cases that are implemented as + classes. It provides methods to create and run test case. + """ + + @property + def packet_infos(self): + """List of packet infos""" + return self._packet_infos + + @classmethod + def get_packet_count_for_if_idx(cls, dst_if_index): + """Get the number of packet info for specified destination if index""" + if dst_if_index in cls._packet_count_for_dst_if_idx: + return cls._packet_count_for_dst_if_idx[dst_if_index] + else: + return 0 + + @classmethod + def instance(cls): + """Return the instance of this testcase""" + return cls.test_instance + + @classmethod + def set_debug_flags(cls, d): + cls.debug_core = False + cls.debug_gdb = False + cls.debug_gdbserver = False + if d is None: + return + dl = d.lower() + if dl == "core": + cls.debug_core = True + elif dl == "gdb": + cls.debug_gdb = True + elif dl == "gdbserver": + cls.debug_gdbserver = True + else: + raise Exception("Unrecognized DEBUG option: '%s'" % d) + + @staticmethod + def get_least_used_cpu(): + cpu_usage_list = [set(range(psutil.cpu_count()))] + vpp_processes = [p for p in psutil.process_iter(attrs=['pid', 'name']) + if 'vpp_main' == p.info['name']] + for vpp_process in vpp_processes: + for cpu_usage_set in cpu_usage_list: + try: + cpu_num = vpp_process.cpu_num() + if cpu_num in cpu_usage_set: + cpu_usage_set_index = cpu_usage_list.index( + cpu_usage_set) + if cpu_usage_set_index == len(cpu_usage_list) - 1: + cpu_usage_list.append({cpu_num}) + else: + cpu_usage_list[cpu_usage_set_index + 1].add( + cpu_num) + cpu_usage_set.remove(cpu_num) + break + except psutil.NoSuchProcess: + pass + + for cpu_usage_set in cpu_usage_list: + if len(cpu_usage_set) > 0: + min_usage_set = cpu_usage_set + break + + return random.choice(tuple(min_usage_set)) + + @classmethod + def print_header(cls): + if not hasattr(cls, '_header_printed'): + print(double_line_delim) + print(colorize(getdoc(cls).splitlines()[0], GREEN)) + print(double_line_delim) + cls._header_printed = True + + @classmethod + def setUpConstants(cls): + """ Set-up the test case class based on environment variables """ + s = os.getenv("STEP", "n") + cls.step = True if s.lower() in ("y", "yes", "1") else False + d = os.getenv("DEBUG", None) + c = os.getenv("CACHE_OUTPUT", "1") + cls.cache_vpp_output = False if c.lower() in ("n", "no", "0") else True + cls.set_debug_flags(d) + cls.vpp_bin = os.getenv('VPP_TEST_BIN', "vpp") + cls.plugin_path = os.getenv('VPP_TEST_PLUGIN_PATH') + cls.extern_plugin_path = os.getenv('EXTERN_PLUGINS') + plugin_path = None + if cls.plugin_path is not None: + if cls.extern_plugin_path is not None: + plugin_path = "%s:%s" % ( + cls.plugin_path, cls.extern_plugin_path) + else: + plugin_path = cls.plugin_path + elif cls.extern_plugin_path is not None: + plugin_path = cls.extern_plugin_path + debug_cli = "" + if cls.step or cls.debug_gdb or cls.debug_gdbserver: + debug_cli = "cli-listen localhost:5002" + coredump_size = None + size = os.getenv("COREDUMP_SIZE") + if size is not None: + coredump_size = "coredump-size %s" % size + if coredump_size is None: + coredump_size = "coredump-size unlimited" + + cpu_core_number = cls.get_least_used_cpu() + + cls.vpp_cmdline = [cls.vpp_bin, "unix", + "{", "nodaemon", debug_cli, "full-coredump", + coredump_size, "runtime-dir", cls.tempdir, "}", + "api-trace", "{", "on", "}", "api-segment", "{", + "prefix", cls.shm_prefix, "}", "cpu", "{", + "main-core", str(cpu_core_number), "}", "statseg", + "{", "socket-name", cls.stats_sock, "}", "plugins", + "{", "plugin", "dpdk_plugin.so", "{", "disable", + "}", "plugin", "unittest_plugin.so", "{", "enable", + "}", "}", ] + if plugin_path is not None: + cls.vpp_cmdline.extend(["plugin_path", plugin_path]) + cls.logger.info("vpp_cmdline args: %s" % cls.vpp_cmdline) + cls.logger.info("vpp_cmdline: %s" % " ".join(cls.vpp_cmdline)) + + @classmethod + def wait_for_enter(cls): + if cls.debug_gdbserver: + print(double_line_delim) + print("Spawned GDB server with PID: %d" % cls.vpp.pid) + elif cls.debug_gdb: + print(double_line_delim) + print("Spawned VPP with PID: %d" % cls.vpp.pid) + else: + cls.logger.debug("Spawned VPP with PID: %d" % cls.vpp.pid) + return + print(single_line_delim) + print("You can debug the VPP using e.g.:") + if cls.debug_gdbserver: + print("gdb " + cls.vpp_bin + " -ex 'target remote localhost:7777'") + print("Now is the time to attach a gdb by running the above " + "command, set up breakpoints etc. and then resume VPP from " + "within gdb by issuing the 'continue' command") + elif cls.debug_gdb: + print("gdb " + cls.vpp_bin + " -ex 'attach %s'" % cls.vpp.pid) + print("Now is the time to attach a gdb by running the above " + "command and set up breakpoints etc.") + print(single_line_delim) + raw_input("Press ENTER to continue running the testcase...") + + @classmethod + def run_vpp(cls): + cmdline = cls.vpp_cmdline + + if cls.debug_gdbserver: + gdbserver = '/usr/bin/gdbserver' + if not os.path.isfile(gdbserver) or \ + not os.access(gdbserver, os.X_OK): + raise Exception("gdbserver binary '%s' does not exist or is " + "not executable" % gdbserver) + + cmdline = [gdbserver, 'localhost:7777'] + cls.vpp_cmdline + cls.logger.info("Gdbserver cmdline is %s", " ".join(cmdline)) + + try: + cls.vpp = subprocess.Popen(cmdline, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=1) + except subprocess.CalledProcessError as e: + cls.logger.critical("Couldn't start vpp: %s" % e) + raise + + cls.wait_for_enter() + + @classmethod + def wait_for_stats_socket(cls): + deadline = time.time() + 3 + ok = False + while time.time() < deadline or \ + cls.debug_gdb or cls.debug_gdbserver: + if os.path.exists(cls.stats_sock): + ok = True + break + time.sleep(0.8) + if not ok: + cls.logger.critical("Couldn't stat : {}".format(cls.stats_sock)) + + @classmethod + def setUpClass(cls): + """ + Perform class setup before running the testcase + Remove shared memory files, start vpp and connect the vpp-api + """ + gc.collect() # run garbage collection first + random.seed() + cls.print_header() + cls.logger = get_logger(cls.__name__) + if hasattr(cls, 'parallel_handler'): + cls.logger.addHandler(cls.parallel_handler) + cls.logger.propagate = False + cls.tempdir = tempfile.mkdtemp( + prefix='vpp-unittest-%s-' % cls.__name__) + cls.stats_sock = "%s/stats.sock" % cls.tempdir + cls.file_handler = FileHandler("%s/log.txt" % cls.tempdir) + cls.file_handler.setFormatter( + Formatter(fmt='%(asctime)s,%(msecs)03d %(message)s', + datefmt="%H:%M:%S")) + cls.file_handler.setLevel(DEBUG) + cls.logger.addHandler(cls.file_handler) + cls.shm_prefix = os.path.basename(cls.tempdir) + os.chdir(cls.tempdir) + cls.logger.info("Temporary dir is %s, shm prefix is %s", + cls.tempdir, cls.shm_prefix) + cls.setUpConstants() + cls.reset_packet_infos() + cls._captures = [] + cls._zombie_captures = [] + cls.verbose = 0 + cls.vpp_dead = False + cls.registry = VppObjectRegistry() + cls.vpp_startup_failed = False + cls.reporter = KeepAliveReporter() + # need to catch exceptions here because if we raise, then the cleanup + # doesn't get called and we might end with a zombie vpp + try: + cls.run_vpp() + cls.reporter.send_keep_alive(cls, 'setUpClass') + VppTestResult.current_test_case_info = TestCaseInfo( + cls.logger, cls.tempdir, cls.vpp.pid, cls.vpp_bin) + cls.vpp_stdout_deque = deque() + cls.vpp_stderr_deque = deque() + cls.pump_thread_stop_flag = Event() + cls.pump_thread_wakeup_pipe = os.pipe() + cls.pump_thread = Thread(target=pump_output, args=(cls,)) + cls.pump_thread.daemon = True + cls.pump_thread.start() + if cls.debug_gdb or cls.debug_gdbserver: + read_timeout = 0 + else: + read_timeout = 5 + cls.vapi = VppPapiProvider(cls.shm_prefix, cls.shm_prefix, cls, + read_timeout) + if cls.step: + hook = StepHook(cls) + else: + hook = PollHook(cls) + cls.vapi.register_hook(hook) + cls.wait_for_stats_socket() + cls.statistics = VPPStats(socketname=cls.stats_sock) + try: + hook.poll_vpp() + except VppDiedError: + cls.vpp_startup_failed = True + cls.logger.critical( + "VPP died shortly after startup, check the" + " output to standard error for possible cause") + raise + try: + cls.vapi.connect() + except Exception: + try: + cls.vapi.disconnect() + except Exception: + pass + if cls.debug_gdbserver: + print(colorize("You're running VPP inside gdbserver but " + "VPP-API connection failed, did you forget " + "to 'continue' VPP from within gdb?", RED)) + raise + except Exception: + try: + cls.quit() + except Exception: + pass + raise + + @classmethod + def quit(cls): + """ + Disconnect vpp-api, kill vpp and cleanup shared memory files + """ + if (cls.debug_gdbserver or cls.debug_gdb) and hasattr(cls, 'vpp'): + cls.vpp.poll() + if cls.vpp.returncode is None: + print(double_line_delim) + print("VPP or GDB server is still running") + print(single_line_delim) + raw_input("When done debugging, press ENTER to kill the " + "process and finish running the testcase...") + + # first signal that we want to stop the pump thread, then wake it up + if hasattr(cls, 'pump_thread_stop_flag'): + cls.pump_thread_stop_flag.set() + if hasattr(cls, 'pump_thread_wakeup_pipe'): + os.write(cls.pump_thread_wakeup_pipe[1], 'ding dong wake up') + if hasattr(cls, 'pump_thread'): + cls.logger.debug("Waiting for pump thread to stop") + cls.pump_thread.join() + if hasattr(cls, 'vpp_stderr_reader_thread'): + cls.logger.debug("Waiting for stdderr pump to stop") + cls.vpp_stderr_reader_thread.join() + + if hasattr(cls, 'vpp'): + if hasattr(cls, 'vapi'): + cls.vapi.disconnect() + del cls.vapi + cls.vpp.poll() + if cls.vpp.returncode is None: + cls.logger.debug("Sending TERM to vpp") + cls.vpp.kill() + cls.logger.debug("Waiting for vpp to die") + cls.vpp.communicate() + del cls.vpp + + if cls.vpp_startup_failed: + stdout_log = cls.logger.info + stderr_log = cls.logger.critical + else: + stdout_log = cls.logger.info + stderr_log = cls.logger.info + + if hasattr(cls, 'vpp_stdout_deque'): + stdout_log(single_line_delim) + stdout_log('VPP output to stdout while running %s:', cls.__name__) + stdout_log(single_line_delim) + vpp_output = "".join(cls.vpp_stdout_deque) + with open(cls.tempdir + '/vpp_stdout.txt', 'w') as f: + f.write(vpp_output) + stdout_log('\n%s', vpp_output) + stdout_log(single_line_delim) + + if hasattr(cls, 'vpp_stderr_deque'): + stderr_log(single_line_delim) + stderr_log('VPP output to stderr while running %s:', cls.__name__) + stderr_log(single_line_delim) + vpp_output = "".join(cls.vpp_stderr_deque) + with open(cls.tempdir + '/vpp_stderr.txt', 'w') as f: + f.write(vpp_output) + stderr_log('\n%s', vpp_output) + stderr_log(single_line_delim) + + @classmethod + def tearDownClass(cls): + """ Perform final cleanup after running all tests in this test-case """ + cls.reporter.send_keep_alive(cls, 'tearDownClass') + cls.quit() + cls.file_handler.close() + cls.reset_packet_infos() + if debug_framework: + debug_internal.on_tear_down_class(cls) + + def tearDown(self): + """ Show various debug prints after each test """ + self.logger.debug("--- tearDown() for %s.%s(%s) called ---" % + (self.__class__.__name__, self._testMethodName, + self._testMethodDoc)) + if not self.vpp_dead: + self.logger.debug(self.vapi.cli("show trace")) + self.logger.info(self.vapi.ppcli("show interface")) + self.logger.info(self.vapi.ppcli("show hardware")) + self.logger.info(self.statistics.set_errors_str()) + self.logger.info(self.vapi.ppcli("show run")) + self.logger.info(self.vapi.ppcli("show log")) + self.registry.remove_vpp_config(self.logger) + # Save/Dump VPP api trace log + api_trace = "vpp_api_trace.%s.log" % self._testMethodName + tmp_api_trace = "/tmp/%s" % api_trace + vpp_api_trace_log = "%s/%s" % (self.tempdir, api_trace) + self.logger.info(self.vapi.ppcli("api trace save %s" % api_trace)) + self.logger.info("Moving %s to %s\n" % (tmp_api_trace, + vpp_api_trace_log)) + os.rename(tmp_api_trace, vpp_api_trace_log) + self.logger.info(self.vapi.ppcli("api trace custom-dump %s" % + vpp_api_trace_log)) + else: + self.registry.unregister_all(self.logger) + + def setUp(self): + """ Clear trace before running each test""" + self.reporter.send_keep_alive(self) + self.logger.debug("--- setUp() for %s.%s(%s) called ---" % + (self.__class__.__name__, self._testMethodName, + self._testMethodDoc)) + if self.vpp_dead: + raise Exception("VPP is dead when setting up the test") + self.sleep(.1, "during setUp") + self.vpp_stdout_deque.append( + "--- test setUp() for %s.%s(%s) starts here ---\n" % + (self.__class__.__name__, self._testMethodName, + self._testMethodDoc)) + self.vpp_stderr_deque.append( + "--- test setUp() for %s.%s(%s) starts here ---\n" % + (self.__class__.__name__, self._testMethodName, + self._testMethodDoc)) + self.vapi.cli("clear trace") + # store the test instance inside the test class - so that objects + # holding the class can access instance methods (like assertEqual) + type(self).test_instance = self + + @classmethod + def pg_enable_capture(cls, interfaces=None): + """ + Enable capture on packet-generator interfaces + + :param interfaces: iterable interface indexes (if None, + use self.pg_interfaces) + + """ + if interfaces is None: + interfaces = cls.pg_interfaces + for i in interfaces: + i.enable_capture() + + @classmethod + def register_capture(cls, cap_name): + """ Register a capture in the testclass """ + # add to the list of captures with current timestamp + cls._captures.append((time.time(), cap_name)) + # filter out from zombies + cls._zombie_captures = [(stamp, name) + for (stamp, name) in cls._zombie_captures + if name != cap_name] + + @classmethod + def pg_start(cls): + """ Remove any zombie captures and enable the packet generator """ + # how long before capture is allowed to be deleted - otherwise vpp + # crashes - 100ms seems enough (this shouldn't be needed at all) + capture_ttl = 0.1 + now = time.time() + for stamp, cap_name in cls._zombie_captures: + wait = stamp + capture_ttl - now + if wait > 0: + cls.sleep(wait, "before deleting capture %s" % cap_name) + now = time.time() + cls.logger.debug("Removing zombie capture %s" % cap_name) + cls.vapi.cli('packet-generator delete %s' % cap_name) + + cls.vapi.cli("trace add pg-input 50") # 50 is maximum + cls.vapi.cli('packet-generator enable') + cls._zombie_captures = cls._captures + cls._captures = [] + + @classmethod + def create_pg_interfaces(cls, interfaces): + """ + Create packet-generator interfaces. + + :param interfaces: iterable indexes of the interfaces. + :returns: List of created interfaces. + + """ + result = [] + for i in interfaces: + intf = VppPGInterface(cls, i) + setattr(cls, intf.name, intf) + result.append(intf) + cls.pg_interfaces = result + return result + + @classmethod + def create_loopback_interfaces(cls, count): + """ + Create loopback interfaces. + + :param count: number of interfaces created. + :returns: List of created interfaces. + """ + result = [VppLoInterface(cls) for i in range(count)] + for intf in result: + setattr(cls, intf.name, intf) + cls.lo_interfaces = result + return result + + @staticmethod + def extend_packet(packet, size, padding=' '): + """ + Extend packet to given size by padding with spaces or custom padding + NOTE: Currently works only when Raw layer is present. + + :param packet: packet + :param size: target size + :param padding: padding used to extend the payload + + """ + packet_len = len(packet) + 4 + extend = size - packet_len + if extend > 0: + num = (extend / len(padding)) + 1 + packet[Raw].load += (padding * num)[:extend] + + @classmethod + def reset_packet_infos(cls): + """ Reset the list of packet info objects and packet counts to zero """ + cls._packet_infos = {} + cls._packet_count_for_dst_if_idx = {} + + @classmethod + def create_packet_info(cls, src_if, dst_if): + """ + Create packet info object containing the source and destination indexes + and add it to the testcase's packet info list + + :param VppInterface src_if: source interface + :param VppInterface dst_if: destination interface + + :returns: _PacketInfo object + + """ + info = _PacketInfo() + info.index = len(cls._packet_infos) + info.src = src_if.sw_if_index + info.dst = dst_if.sw_if_index + if isinstance(dst_if, VppSubInterface): + dst_idx = dst_if.parent.sw_if_index + else: + dst_idx = dst_if.sw_if_index + if dst_idx in cls._packet_count_for_dst_if_idx: + cls._packet_count_for_dst_if_idx[dst_idx] += 1 + else: + cls._packet_count_for_dst_if_idx[dst_idx] = 1 + cls._packet_infos[info.index] = info + return info + + @staticmethod + def info_to_payload(info): + """ + Convert _PacketInfo object to packet payload + + :param info: _PacketInfo object + + :returns: string containing serialized data from packet info + """ + return "%d %d %d %d %d" % (info.index, info.src, info.dst, + info.ip, info.proto) + + @staticmethod + def payload_to_info(payload): + """ + Convert packet payload to _PacketInfo object + + :param payload: packet payload + + :returns: _PacketInfo object containing de-serialized data from payload + + """ + numbers = payload.split() + info = _PacketInfo() + info.index = int(numbers[0]) + info.src = int(numbers[1]) + info.dst = int(numbers[2]) + info.ip = int(numbers[3]) + info.proto = int(numbers[4]) + return info + + def get_next_packet_info(self, info): + """ + Iterate over the packet info list stored in the testcase + Start iteration with first element if info is None + Continue based on index in info if info is specified + + :param info: info or None + :returns: next info in list or None if no more infos + """ + if info is None: + next_index = 0 + else: + next_index = info.index + 1 + if next_index == len(self._packet_infos): + return None + else: + return self._packet_infos[next_index] + + def get_next_packet_info_for_interface(self, src_index, info): + """ + Search the packet info list for the next packet info with same source + interface index + + :param src_index: source interface index to search for + :param info: packet info - where to start the search + :returns: packet info or None + + """ + while True: + info = self.get_next_packet_info(info) + if info is None: + return None + if info.src == src_index: + return info + + def get_next_packet_info_for_interface2(self, src_index, dst_index, info): + """ + Search the packet info list for the next packet info with same source + and destination interface indexes + + :param src_index: source interface index to search for + :param dst_index: destination interface index to search for + :param info: packet info - where to start the search + :returns: packet info or None + + """ + while True: + info = self.get_next_packet_info_for_interface(src_index, info) + if info is None: + return None + if info.dst == dst_index: + return info + + def assert_equal(self, real_value, expected_value, name_or_class=None): + if name_or_class is None: + self.assertEqual(real_value, expected_value) + return + try: + msg = "Invalid %s: %d('%s') does not match expected value %d('%s')" + msg = msg % (getdoc(name_or_class).strip(), + real_value, str(name_or_class(real_value)), + expected_value, str(name_or_class(expected_value))) + except Exception: + msg = "Invalid %s: %s does not match expected value %s" % ( + name_or_class, real_value, expected_value) + + self.assertEqual(real_value, expected_value, msg) + + def assert_in_range(self, + real_value, + expected_min, + expected_max, + name=None): + if name is None: + msg = None + else: + msg = "Invalid %s: %s out of range <%s,%s>" % ( + name, real_value, expected_min, expected_max) + self.assertTrue(expected_min <= real_value <= expected_max, msg) + + def assert_packet_checksums_valid(self, packet, + ignore_zero_udp_checksums=True): + received = packet.__class__(str(packet)) + self.logger.debug( + ppp("Verifying packet checksums for packet:", received)) + udp_layers = ['UDP', 'UDPerror'] + checksum_fields = ['cksum', 'chksum'] + checksums = [] + counter = 0 + temp = received.__class__(str(received)) + while True: + layer = temp.getlayer(counter) + if layer: + for cf in checksum_fields: + if hasattr(layer, cf): + if ignore_zero_udp_checksums and \ + 0 == getattr(layer, cf) and \ + layer.name in udp_layers: + continue + delattr(layer, cf) + checksums.append((counter, cf)) + else: + break + counter = counter + 1 + if 0 == len(checksums): + return + temp = temp.__class__(str(temp)) + for layer, cf in checksums: + calc_sum = getattr(temp[layer], cf) + self.assert_equal( + getattr(received[layer], cf), calc_sum, + "packet checksum on layer #%d: %s" % (layer, temp[layer].name)) + self.logger.debug( + "Checksum field `%s` on `%s` layer has correct value `%s`" % + (cf, temp[layer].name, calc_sum)) + + def assert_checksum_valid(self, received_packet, layer, + field_name='chksum', + ignore_zero_checksum=False): + """ Check checksum of received packet on given layer """ + received_packet_checksum = getattr(received_packet[layer], field_name) + if ignore_zero_checksum and 0 == received_packet_checksum: + return + recalculated = received_packet.__class__(str(received_packet)) + delattr(recalculated[layer], field_name) + recalculated = recalculated.__class__(str(recalculated)) + self.assert_equal(received_packet_checksum, + getattr(recalculated[layer], field_name), + "packet checksum on layer: %s" % layer) + + def assert_ip_checksum_valid(self, received_packet, + ignore_zero_checksum=False): + self.assert_checksum_valid(received_packet, 'IP', + ignore_zero_checksum=ignore_zero_checksum) + + def assert_tcp_checksum_valid(self, received_packet, + ignore_zero_checksum=False): + self.assert_checksum_valid(received_packet, 'TCP', + ignore_zero_checksum=ignore_zero_checksum) + + def assert_udp_checksum_valid(self, received_packet, + ignore_zero_checksum=True): + self.assert_checksum_valid(received_packet, 'UDP', + ignore_zero_checksum=ignore_zero_checksum) + + def assert_embedded_icmp_checksum_valid(self, received_packet): + if received_packet.haslayer(IPerror): + self.assert_checksum_valid(received_packet, 'IPerror') + if received_packet.haslayer(TCPerror): + self.assert_checksum_valid(received_packet, 'TCPerror') + if received_packet.haslayer(UDPerror): + self.assert_checksum_valid(received_packet, 'UDPerror', + ignore_zero_checksum=True) + if received_packet.haslayer(ICMPerror): + self.assert_checksum_valid(received_packet, 'ICMPerror') + + def assert_icmp_checksum_valid(self, received_packet): + self.assert_checksum_valid(received_packet, 'ICMP') + self.assert_embedded_icmp_checksum_valid(received_packet) + + def assert_icmpv6_checksum_valid(self, pkt): + if pkt.haslayer(ICMPv6DestUnreach): + self.assert_checksum_valid(pkt, 'ICMPv6DestUnreach', 'cksum') + self.assert_embedded_icmp_checksum_valid(pkt) + if pkt.haslayer(ICMPv6EchoRequest): + self.assert_checksum_valid(pkt, 'ICMPv6EchoRequest', 'cksum') + if pkt.haslayer(ICMPv6EchoReply): + self.assert_checksum_valid(pkt, 'ICMPv6EchoReply', 'cksum') + + def assert_packet_counter_equal(self, counter, expected_value): + counters = self.vapi.cli("sh errors").split('\n') + counter_value = -1 + for i in range(1, len(counters)-1): + results = counters[i].split() + if results[1] == counter: + counter_value = int(results[0]) + break + self.assert_equal(counter_value, expected_value, + "packet counter `%s'" % counter) + + @classmethod + def sleep(cls, timeout, remark=None): + if hasattr(cls, 'logger'): + cls.logger.debug("Starting sleep for %ss (%s)" % (timeout, remark)) + before = time.time() + time.sleep(timeout) + after = time.time() + if after - before > 2 * timeout: + cls.logger.error("unexpected time.sleep() result - " + "slept for %ss instead of ~%ss!" % ( + after - before, timeout)) + if hasattr(cls, 'logger'): + cls.logger.debug( + "Finished sleep (%s) - slept %ss (wanted %ss)" % ( + remark, after - before, timeout)) + + def send_and_assert_no_replies(self, intf, pkts, remark="", timeout=None): + self.vapi.cli("clear trace") + intf.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + if not timeout: + timeout = 1 + for i in self.pg_interfaces: + i.get_capture(0, timeout=timeout) + i.assert_nothing_captured(remark=remark) + timeout = 0.1 + + def send_and_expect(self, input, pkts, output): + self.vapi.cli("clear trace") + input.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + if isinstance(object, (list,)): + rx = [] + for o in output: + rx.append(output.get_capture(len(pkts))) + else: + rx = output.get_capture(len(pkts)) + return rx + + def send_and_expect_only(self, input, pkts, output, timeout=None): + self.vapi.cli("clear trace") + input.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + if isinstance(object, (list,)): + outputs = output + rx = [] + for o in outputs: + rx.append(output.get_capture(len(pkts))) + else: + rx = output.get_capture(len(pkts)) + outputs = [output] + if not timeout: + timeout = 1 + for i in self.pg_interfaces: + if i not in outputs: + i.get_capture(0, timeout=timeout) + i.assert_nothing_captured() + timeout = 0.1 + + return rx + + +def get_testcase_doc_name(test): + return getdoc(test.__class__).splitlines()[0] + + +def get_test_description(descriptions, test): + short_description = test.shortDescription() + if descriptions and short_description: + return short_description + else: + return str(test) + + +class TestCaseInfo(object): + def __init__(self, logger, tempdir, vpp_pid, vpp_bin_path): + self.logger = logger + self.tempdir = tempdir + self.vpp_pid = vpp_pid + self.vpp_bin_path = vpp_bin_path + self.core_crash_test = None + + +class VppTestResult(unittest.TestResult): + """ + @property result_string + String variable to store the test case result string. + @property errors + List variable containing 2-tuples of TestCase instances and strings + holding formatted tracebacks. Each tuple represents a test which + raised an unexpected exception. + @property failures + List variable containing 2-tuples of TestCase instances and strings + holding formatted tracebacks. Each tuple represents a test where + a failure was explicitly signalled using the TestCase.assert*() + methods. + """ + + failed_test_cases_info = set() + core_crash_test_cases_info = set() + current_test_case_info = None + + def __init__(self, stream, descriptions, verbosity, runner): + """ + :param stream File descriptor to store where to report test results. + Set to the standard error stream by default. + :param descriptions Boolean variable to store information if to use + test case descriptions. + :param verbosity Integer variable to store required verbosity level. + """ + unittest.TestResult.__init__(self, stream, descriptions, verbosity) + self.stream = stream + self.descriptions = descriptions + self.verbosity = verbosity + self.result_string = None + self.runner = runner + + def addSuccess(self, test): + """ + Record a test succeeded result + + :param test: + + """ + if self.current_test_case_info: + self.current_test_case_info.logger.debug( + "--- addSuccess() %s.%s(%s) called" % (test.__class__.__name__, + test._testMethodName, + test._testMethodDoc)) + unittest.TestResult.addSuccess(self, test) + self.result_string = colorize("OK", GREEN) + + self.send_result_through_pipe(test, PASS) + + def addSkip(self, test, reason): + """ + Record a test skipped. + + :param test: + :param reason: + + """ + if self.current_test_case_info: + self.current_test_case_info.logger.debug( + "--- addSkip() %s.%s(%s) called, reason is %s" % + (test.__class__.__name__, test._testMethodName, + test._testMethodDoc, reason)) + unittest.TestResult.addSkip(self, test, reason) + self.result_string = colorize("SKIP", YELLOW) + + self.send_result_through_pipe(test, SKIP) + + def symlink_failed(self): + if self.current_test_case_info: + try: + failed_dir = os.getenv('VPP_TEST_FAILED_DIR') + link_path = os.path.join( + failed_dir, + '%s-FAILED' % + os.path.basename(self.current_test_case_info.tempdir)) + if self.current_test_case_info.logger: + self.current_test_case_info.logger.debug( + "creating a link to the failed test") + self.current_test_case_info.logger.debug( + "os.symlink(%s, %s)" % + (self.current_test_case_info.tempdir, link_path)) + if os.path.exists(link_path): + if self.current_test_case_info.logger: + self.current_test_case_info.logger.debug( + 'symlink already exists') + else: + os.symlink(self.current_test_case_info.tempdir, link_path) + + except Exception as e: + if self.current_test_case_info.logger: + self.current_test_case_info.logger.error(e) + + def send_result_through_pipe(self, test, result): + if hasattr(self, 'test_framework_result_pipe'): + pipe = self.test_framework_result_pipe + if pipe: + pipe.send((test.id(), result)) + + def log_error(self, test, err, fn_name): + if self.current_test_case_info: + if isinstance(test, unittest.suite._ErrorHolder): + test_name = test.description + else: + test_name = '%s.%s(%s)' % (test.__class__.__name__, + test._testMethodName, + test._testMethodDoc) + self.current_test_case_info.logger.debug( + "--- %s() %s called, err is %s" % + (fn_name, test_name, err)) + self.current_test_case_info.logger.debug( + "formatted exception is:\n%s" % + "".join(format_exception(*err))) + + def add_error(self, test, err, unittest_fn, error_type): + if error_type == FAIL: + self.log_error(test, err, 'addFailure') + error_type_str = colorize("FAIL", RED) + elif error_type == ERROR: + self.log_error(test, err, 'addError') + error_type_str = colorize("ERROR", RED) + else: + raise Exception('Error type %s cannot be used to record an ' + 'error or a failure' % error_type) + + unittest_fn(self, test, err) + if self.current_test_case_info: + self.result_string = "%s [ temp dir used by test case: %s ]" % \ + (error_type_str, + self.current_test_case_info.tempdir) + self.symlink_failed() + self.failed_test_cases_info.add(self.current_test_case_info) + if is_core_present(self.current_test_case_info.tempdir): + if not self.current_test_case_info.core_crash_test: + if isinstance(test, unittest.suite._ErrorHolder): + test_name = str(test) + else: + test_name = "'{}' ({})".format( + get_testcase_doc_name(test), test.id()) + self.current_test_case_info.core_crash_test = test_name + self.core_crash_test_cases_info.add( + self.current_test_case_info) + else: + self.result_string = '%s [no temp dir]' % error_type_str + + self.send_result_through_pipe(test, error_type) + + def addFailure(self, test, err): + """ + Record a test failed result + + :param test: + :param err: error message + + """ + self.add_error(test, err, unittest.TestResult.addFailure, FAIL) + + def addError(self, test, err): + """ + Record a test error result + + :param test: + :param err: error message + + """ + self.add_error(test, err, unittest.TestResult.addError, ERROR) + + def getDescription(self, test): + """ + Get test description + + :param test: + :returns: test description + + """ + return get_test_description(self.descriptions, test) + + def startTest(self, test): + """ + Start a test + + :param test: + + """ + test.print_header() + + unittest.TestResult.startTest(self, test) + if self.verbosity > 0: + self.stream.writeln( + "Starting " + self.getDescription(test) + " ...") + self.stream.writeln(single_line_delim) + + def stopTest(self, test): + """ + Called when the given test has been run + + :param test: + + """ + unittest.TestResult.stopTest(self, test) + if self.verbosity > 0: + self.stream.writeln(single_line_delim) + self.stream.writeln("%-73s%s" % (self.getDescription(test), + self.result_string)) + self.stream.writeln(single_line_delim) + else: + self.stream.writeln("%-73s%s" % (self.getDescription(test), + self.result_string)) + + self.send_result_through_pipe(test, TEST_RUN) + + def printErrors(self): + """ + Print errors from running the test case + """ + if len(self.errors) > 0 or len(self.failures) > 0: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + # ^^ that is the last output from unittest before summary + if not self.runner.print_summary: + devnull = unittest.runner._WritelnDecorator(open(os.devnull, 'w')) + self.stream = devnull + self.runner.stream = devnull + + def printErrorList(self, flavour, errors): + """ + Print error list to the output stream together with error type + and test case description. + + :param flavour: error type + :param errors: iterable errors + + """ + for test, err in errors: + self.stream.writeln(double_line_delim) + self.stream.writeln("%s: %s" % + (flavour, self.getDescription(test))) + self.stream.writeln(single_line_delim) + self.stream.writeln("%s" % err) + + +class VppTestRunner(unittest.TextTestRunner): + """ + A basic test runner implementation which prints results to standard error. + """ + @property + def resultclass(self): + """Class maintaining the results of the tests""" + return VppTestResult + + def __init__(self, keep_alive_pipe=None, descriptions=True, verbosity=1, + result_pipe=None, failfast=False, buffer=False, + resultclass=None, print_summary=True): + + # ignore stream setting here, use hard-coded stdout to be in sync + # with prints from VppTestCase methods ... + super(VppTestRunner, self).__init__(sys.stdout, descriptions, + verbosity, failfast, buffer, + resultclass) + KeepAliveReporter.pipe = keep_alive_pipe + + self.orig_stream = self.stream + self.resultclass.test_framework_result_pipe = result_pipe + + self.print_summary = print_summary + + def _makeResult(self): + return self.resultclass(self.stream, + self.descriptions, + self.verbosity, + self) + + def run(self, test): + """ + Run the tests + + :param test: + + """ + faulthandler.enable() # emit stack trace to stderr if killed by signal + + result = super(VppTestRunner, self).run(test) + if not self.print_summary: + self.stream = self.orig_stream + result.stream = self.orig_stream + return result + + +class Worker(Thread): + def __init__(self, args, logger, env={}): + self.logger = logger + self.args = args + self.result = None + self.env = copy.deepcopy(env) + 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.update(self.env) + 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.info(err) + self.logger.info(single_line_delim) + self.result = self.process.returncode diff --git a/Test/test_jvpp.py b/Test/test_jvpp.py new file mode 100644 index 0000000..6151ebd --- /dev/null +++ b/Test/test_jvpp.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python + +import os +import subprocess +import unittest + +from framework import VppTestCase, running_extended_tests + +# Api files path +API_FILES_PATH = "japi/java" + +# Registry jar file name prefix +REGISTRY_JAR_PREFIX = "jvpp-registry" + + +@unittest.skipUnless(running_extended_tests(), "part of extended tests") +class TestJVpp(VppTestCase): + """ JVPP Core Test Case """ + + def invoke_for_jvpp_core(self, api_jar_name, test_class_name): + self.jvpp_connection_test(api_jar_name=api_jar_name, + test_class_name=test_class_name, + timeout=10) + + def test_vpp_core_callback_api(self): + """ JVPP Core Callback Api Test Case """ + self.invoke_for_jvpp_core(api_jar_name="jvpp-core", + test_class_name="io.fd.vpp.jvpp.core.test." + "CallbackApiTest") + + def test_vpp_core_future_api(self): + """JVPP Core Future Api Test Case""" + self.invoke_for_jvpp_core(api_jar_name="jvpp-core", + test_class_name="io.fd.vpp.jvpp.core.test." + "FutureApiTest") + + def test_vpp_acl_callback_api(self): + """ JVPP Acl Callback Api Test Case """ + self.invoke_for_jvpp_core(api_jar_name="jvpp-acl", + test_class_name="io.fd.vpp.jvpp.acl.test." + "CallbackApiTest") + + def test_vpp_acl_future_api(self): + """JVPP Acl Future Api Test Case""" + self.invoke_for_jvpp_core(api_jar_name="jvpp-acl", + test_class_name="io.fd.vpp.jvpp.acl.test." + "FutureApiTest") + + def test_vpp_ioamexport_callback_api(self): + """ JVPP Ioamexport Callback Api Test Case """ + self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamexport", + test_class_name="io.fd.vpp.jvpp.ioamexport." + "test.CallbackApiTest") + + def test_vpp_ioamexport_future_api(self): + """JVPP Ioamexport Future Api Test Case""" + self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamexport", + test_class_name="io.fd.vpp.jvpp.ioamexport." + "test.FutureApiTest") + + def test_vpp_ioampot_callback_api(self): + """ JVPP Ioampot Callback Api Test Case """ + self.invoke_for_jvpp_core(api_jar_name="jvpp-ioampot", + test_class_name="io.fd.vpp.jvpp.ioampot." + "test.CallbackApiTest") + + def test_vpp_ioampot_future_api(self): + """JVPP Ioampot Future Api Test Case""" + self.invoke_for_jvpp_core(api_jar_name="jvpp-ioampot", + test_class_name="io.fd.vpp.jvpp.ioampot." + "test.FutureApiTest") + + def test_vpp_ioamtrace_callback_api(self): + """ JVPP Ioamtrace Callback Api Test Case """ + self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamtrace", + test_class_name="io.fd.vpp.jvpp.ioamtrace." + "test.CallbackApiTest") + + def test_vpp_ioamtrace_future_api(self): + """JVPP Ioamtrace Future Api Test Case""" + self.invoke_for_jvpp_core(api_jar_name="jvpp-ioamtrace", + test_class_name="io.fd.vpp.jvpp.ioamtrace." + "test.FutureApiTest") + + def test_vpp_snat_callback_api(self): + """ JVPP Snat Callback Api Test Case """ + self.invoke_for_jvpp_core(api_jar_name="jvpp-nat", + test_class_name="io.fd.vpp.jvpp.nat.test." + "CallbackApiTest") + + def test_vpp_snat_future_api(self): + """JVPP Snat Future Api Test Case""" + self.invoke_for_jvpp_core(api_jar_name="jvpp-nat", + test_class_name="io.fd.vpp.jvpp.nat.test." + "FutureApiTest") + + def full_jar_name(self, install_dir, jar_name, version): + return os.path.join(install_dir, API_FILES_PATH, + "{0}-{1}.jar".format(jar_name, version)) + + def jvpp_connection_test(self, api_jar_name, test_class_name, timeout): + install_dir = os.getenv('VPP_TEST_BUILD_DIR') + self.logger.info("Install directory : {0}".format(install_dir)) + + version_reply = self.vapi.show_version() + version = version_reply.version.split("-")[0] + registry_jar_path = self.full_jar_name(install_dir, + REGISTRY_JAR_PREFIX, version) + self.logger.info("JVpp Registry jar path : {0}" + .format(registry_jar_path)) + if (not os.path.isfile(registry_jar_path)): + raise Exception( + "JVpp Registry jar has not been found: {0}" + .format(registry_jar_path)) + + api_jar_path = self.full_jar_name(install_dir, api_jar_name, version) + self.logger.info("Api jar path : {0}".format(api_jar_path)) + if (not os.path.isfile(api_jar_path)): + raise Exception( + "Api jar has not been found: {0}".format(api_jar_path)) + + # passes shm prefix as parameter to create connection with same value + command = ["java", "-cp", + "{0}:{1}".format(registry_jar_path, api_jar_path), + test_class_name, "/{0}-vpe-api".format(self.shm_prefix)] + self.logger.info("Test Command : {0}, Timeout : {1}". + format(command, timeout)) + + self.process = subprocess.Popen(command, shell=False, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, bufsize=1, + universal_newlines=True) + + out, err = self.process.communicate() + self.logger.info("Process output : {0}{1}".format(os.linesep, out)) + + if self.process.returncode != 0: + raise subprocess.CalledProcessError( + "Command {0} failed with return code: {1}.{2}" + "Process error output: {2}{3}" + .format(command, self.process.returncode, os.linesep, err)) + + def tearDown(self): + self.logger.info("Tearing down jvpp test") + super(TestJVpp, self).tearDown() + if hasattr(self, 'process') and self.process.poll() is None: + self.process.kill() diff --git a/clean.sh b/clean.sh new file mode 100755 index 0000000..4f81a09 --- /dev/null +++ b/clean.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash +echo "JVPP cleanup started" +make clean +rm -rf build-root/packages/* +rm -rf java/*.jar +# clean cmake cache +find . -iwholename '*cmake*' -not -name CMakeLists.txt -delete +# clean cpack cache +find . -iwholename '*cpack*' -delete +echo "... cleanup finished." diff --git a/docs/jvpp.rst b/docs/jvpp.rst new file mode 100644 index 0000000..4fbeb39 --- /dev/null +++ b/docs/jvpp.rst @@ -0,0 +1,140 @@ +.. _JVPP: + +.. toctree:: + +Building JVPP from source +========================= + +JVPP uses cmake to configure and build source code. The minimal version of cmake is 3.5. +Ensure that you have proper version of cmake installed. + +JVPP depends on VPP packages (Debian or Centos): +* vpp +* vpp-plugins +* vpp-dev (vpp-devel on Centos) + +Vpp and vpp-plugins packages contain API source file, which are used to generate Java API bindings. Vpp dev package +contains header files needed for build. + +Obtaining the source +-------------------- + +You can get JVPP source code from gerrit.fd.io using git: +git clone https://gerrit.fd.io/r/jvpp + +or from github: +https://github.com/FDio/jvpp.git + + +Install dependencies (Optional, one time only) +------------------ + +You can install all dependencies using provided install-dep target: + +.. code-block:: console + + make install-dep + +Cleanup (Optional) +------------------ + +Whenever you need to clean the setup you can use "clean.sh" script in the root folder. This cleans the build and clears +the cache. This is usefull mostly when you change something in JVPP to ensure everything is rebuild from scratch. + +Configuring using cmake +----------------------- + +Configuration using cmake is very easy, the whole process is automated. All you need to do is issue following command +from JVPP's root directory: + +.. code-block:: console + + cmake . + +This will configure all variables and setup the build. + +Building the source +------------------- + +To build the source use make command: + +.. code-block:: console + + make + +You can also install the library using (you need to use sudo or have root privileges to install libraries): + +.. code-block:: console + + sudo make install + +Building the packages +--------------------- + +During the setup using cmake the operating system is automatically recognised and everything is set up so you can build +packages for your current operating system. Only Debian based (tested on Ubuntu) and Centos (tested on Centos 7) +are supported for now. + +To build the package you need to call: + +.. code-block:: console + + make package + +You can find the packages in build-root/packages folder. + +Getting JVPP jar +================ + +VPP provides java bindings which can be downloaded at: + +* https://nexus.fd.io/content/repositories/fd.io.release/io/fd/vpp/jvpp-core/19.01/jvpp-core-19.01.jar + +Getting JVPP via maven +------------------------------------ + +**1. Add the following to the repositories section in your ~/.m2/settings.xml to pick up the fd.io maven repo:** + +.. code-block:: console + + <repository> + <id>fd.io-release</id> + <name>fd.io-release</name> + <url>https://nexus.fd.io/content/repositories/fd.io.release/</url> + <releases> + <enabled>false</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + +For more information on setting up maven repositories in settings.xml, please look at: + +* https://maven.apache.org/guides/mini/guide-multiple-repositories.html + +**2. Then you can get JVPP by putting in the dependencies section of your pom.xml file:** + +.. code-block:: console + + <dependency> + <groupId>io.fd.vpp</groupId> + <artifactId>jvpp-core</artifactId> + <version>19.01</version> + </dependency> + +For more information on maven dependency managment, please look at: + +* https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html + + +Installing JVPP manually to local maven repository +-------------------------------------------------- + +Once JVPP is successfully built, you can install it to local .m2 repository. +To do so use provided script in JVPP root directory: + +.. code-block:: console + + ./install_jvpp.sh + diff --git a/install_jvpp.sh b/install_jvpp.sh new file mode 100755 index 0000000..1c3a101 --- /dev/null +++ b/install_jvpp.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +MAIN_VER="$(./version | cut -f1 -d"-")" +VERSION="$(./version | cut -f1 -d"~")" + +echo "Main version: ${MAIN_VER}" +echo "full version: ${VERSION}" +cd ./java + +echo "Installing jars to Maven:" +mvn install:install-file -Dfile=jvpp-registry-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-registry -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-core-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-core -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-ioampot-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-ioampot -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-ioamtrace-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-ioamtrace -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-ioamexport-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-ioamexport -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-nat-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-nat -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-nsh-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-nsh -Dversion=${VERSION} -Dpackaging=jar +mvn install:install-file -Dfile=jvpp-acl-${MAIN_VER}.jar -DgroupId=io.fd.vpp -DartifactId=jvpp-acl -Dversion=${VERSION} -Dpackaging=jar + +echo "all done."
\ No newline at end of file diff --git a/java/.gitignore b/java/.gitignore new file mode 100644 index 0000000..c69ba33 --- /dev/null +++ b/java/.gitignore @@ -0,0 +1,6 @@ +# Negate "No core files" pattern from the toplevel .gitignore +!**/core +/CMakeFiles +Makefile +cmake_install.cmake +*.jar
\ No newline at end of file diff --git a/java/CMakeLists.txt b/java/CMakeLists.txt new file mode 100644 index 0000000..2da75d6 --- /dev/null +++ b/java/CMakeLists.txt @@ -0,0 +1,331 @@ +# Copyright (c) 2018 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. + +find_path(VNET_INCLUDE_DIR NAMES vnet/api_errno.h) +find_library(VPPINFRA_DIR NAMES vppinfra REQUIRED) +find_library(VLIBMEMORYCLIENT_DIR NAMES vlibmemoryclient REQUIRED) +find_library(SVM_DIR NAMES svm REQUIRED) + +include_directories(${VNET_INCLUDE_DIR} + ${VNET_INCLUDE_DIR}/vpp_plugins + ${VNET_INCLUDE_DIR}/vpp_api + ${Java_INCLUDE_DIRS} + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR/../vpp/plugins}) + +add_compile_options(-Wall) +############# Common package ################## +add_library(jvpp_common SHARED jvpp-common/jvpp_common.c) +set_target_properties(jvpp_common PROPERTIES SOVERSION ${JAPI_LIB_VERSION}) +target_link_libraries(jvpp_common ${VPPINFRA_DIR}) +install(TARGETS jvpp_common DESTINATION lib COMPONENT libjvpp_common) +install(FILES jvpp-common/jvpp_common.h DESTINATION include/japi/) + +set(JVPP_LIBS jvpp_common ${VPPINFRA_DIR} ${VLIBMEMORYCLIENT_DIR} ${SVM_DIR} + Threads::Threads m rt) + +############# Registry package ################## +set(PACKAGE_DIR_JVPP_REGISTRY io/fd/vpp/jvpp) +unset(files) +FILE(GLOB files RELATIVE + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-registry/${PACKAGE_DIR_JVPP_REGISTRY}/*.java + ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-registry/${PACKAGE_DIR_JVPP_REGISTRY}/*/*.java +) + +add_custom_target (jvpp-registry-classes) +add_custom_command (TARGET jvpp-registry-classes + PRE_BUILD + COMMAND mkdir -p jvpp-registry/target + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry + COMMAND ${Java_JAVAC_EXECUTABLE} + ARGS -d ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-registry/target -h jvpp-registry ${files} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} +) + +add_library(jvpp_registry SHARED jvpp-registry/jvpp_registry.c) +target_link_libraries(jvpp_registry ${JVPP_LIBS}) +include_directories(jvpp-registry) +add_dependencies(jvpp_registry jvpp_common jvpp-registry-classes) + +add_custom_target (jvpp-registry) +add_dependencies(jvpp-registry jvpp_registry) +add_custom_command(TARGET jvpp-registry + PRE_BUILD + COMMAND cp ${CMAKE_BINARY_DIR}/build-root/lib/libjvpp_registry.so jvpp-registry/target + COMMAND ${Java_JAR_EXECUTABLE} ARGS cf + ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry-${JAPI_LIB_VERSION}.jar + -C jvpp-registry/target . + COMMAND rm ARGS -rf jvpp-registry/target + jvpp-registry/io_fd_vpp_jvpp_*.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "JAR_GEN registry" +) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry-${JAPI_LIB_VERSION}.jar + DESTINATION share/java +) + +############## Functions ######################### +function(japigen name) + if(NOT VPP_JAVA_APIGEN) + set(VPP_JAVA_APIGEN ${CMAKE_CURRENT_SOURCE_DIR}/jvpp/gen/jvpp_gen.py) + endif() + add_custom_target(japigen-${name} DEPENDS jvpp-registry) + add_custom_command(TARGET japigen-${name} + POST_BUILD + COMMAND mkdir -p jvpp-${name}/target + COMMAND mkdir -p ${CMAKE_CURRENT_BINARY_DIR}/jvpp-${name} + COMMAND ${VPP_JAVA_APIGEN} + ARGS --plugin_name ${name} --root_dir jvpp-${name} -i ${ARGN} + COMMAND find jvpp-${name} -name \*.java > jvpp-${name}/jvpp-${name}.files + COMMAND ${Java_JAVAC_EXECUTABLE} + ARGS -cp ${CMAKE_CURRENT_BINARY_DIR}/jvpp-registry-${JAPI_LIB_VERSION}.jar -d + ${CMAKE_CURRENT_SOURCE_DIR}/jvpp-${name}/target -h jvpp-${name} + @jvpp-${name}/jvpp-${name}.files + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "JAVA_API_GEN ${name}" + ) +endfunction() + +function(jargen name) + add_custom_command(TARGET jvpp_${name} + POST_BUILD + COMMAND cp ${CMAKE_BINARY_DIR}/build-root/lib/libjvpp_${name}.so jvpp-${name}/target + COMMAND ${Java_JAR_EXECUTABLE} ARGS cf + ${CMAKE_CURRENT_BINARY_DIR}/jvpp-${name}-${JAPI_LIB_VERSION}.jar + -C jvpp-${name}/target . + COMMAND rm ARGS -rf jvpp-${name}/target jvpp-${name}/jvpp-${name}.files + jvpp-${name}/jvpp_${name}_gen.h jvpp-${name}/io_fd_vpp_jvpp_*.h + jvpp-registry/io_fd_vpp_jvpp_*.h + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMENT "JAR_GEN ${name}" + ) + install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/jvpp-${name}-${JAPI_LIB_VERSION}.jar + DESTINATION share/java + ) +endfunction() + +function(java_api_binding name src_file) + japigen (${name} ${ARGN}) + add_library(jvpp_${name} SHARED jvpp-${name}/jvpp_${src_file}.c) + target_link_libraries(jvpp_${name} ${JVPP_LIBS}) + include_directories(jvpp-${name}) + add_dependencies(jvpp_${name} jvpp_common jvpp_registry japigen-${name}) + jargen (${name}) +endfunction() + +############ Core Package ####################### +unset (files) +unset (corefiles) + +if(${RELEASE_ID} MATCHES Ubuntu) + execute_process(COMMAND dpkg-query -L vpp + COMMAND grep -v memclnt + COMMAND grep api.json OUTPUT_VARIABLE corefiles) + STRING(REGEX REPLACE "\n" ";" corefiles "${corefiles}") +elseif(${RELEASE_ID} MATCHES CentOS) + # Exclude vpp plugin api files. VPP package should not contain VPP-plugins api files and vice-versa. Temporary fixing + # by excluding them manually. + execute_process(COMMAND rpm -ql vpp + COMMAND grep -v memclnt + COMMAND grep -v abf + COMMAND grep -v acl + COMMAND grep -v avf + COMMAND grep -v cdp + COMMAND grep -v dpdk + COMMAND grep -v flowprobe + COMMAND grep -v gbp + COMMAND grep -v gtpu + COMMAND grep -v igmp + COMMAND grep -v ioam_cache + COMMAND grep -v ioam_export + COMMAND grep -v ioam_vxlan_gpe + COMMAND grep -v l2e + COMMAND grep -v lacp + COMMAND grep -v lb + COMMAND grep -v mactime + COMMAND grep -v map + COMMAND grep -v memif + COMMAND grep -v nat + COMMAND grep -v nsh + COMMAND grep -v nsim + COMMAND grep -v pot + COMMAND grep -v pppoe + COMMAND grep -v stn + COMMAND grep -v svs + COMMAND grep -v trace + COMMAND grep -v udp_ping + COMMAND grep -v vmxnet3 + COMMAND grep -v vxlan_gpe_ioam_export + COMMAND grep api.json OUTPUT_VARIABLE corefiles) + STRING(REGEX REPLACE "\n" ";" corefiles "${corefiles}") +endif() + +FILE(GLOB_RECURSE files RELATIVE + ${CMAKE_CURRENT_SOURCE_DIR} + ${corefiles} +) + +# message("COREFILES: ${corefiles}") +java_api_binding (core core ${files}) + +############ Plugin Packages ####################### +unset (files) +unset (pluginfiles) +unset (ACL_JSON_FILE) +unset (NAT_JSON_FILE) +unset (NSH_JSON_FILE) +unset (GTPU_JSON_FILE) +unset (PPPOE_JSON_FILE) +unset (IOAM_TRACE_JSON_FILE) +unset (IOAM_POT_JSON_FILE) +unset (IOAM_EXPORT_JSON_FILE) + +execute_process(COMMAND dpkg-query -L vpp-plugins + COMMAND grep api.json OUTPUT_VARIABLE pluginfiles) +STRING(REGEX REPLACE "\n" ";" pluginfiles "${pluginfiles}") +if(${RELEASE_ID} MATCHES Ubuntu) + execute_process(COMMAND dpkg-query -L vpp-plugins + COMMAND grep api.json OUTPUT_VARIABLE pluginfiles) + STRING(REGEX REPLACE "\n" ";" pluginfiles "${pluginfiles}") +elseif(${RELEASE_ID} MATCHES CentOS) + execute_process(COMMAND rpm -ql vpp-plugins + COMMAND grep api.json OUTPUT_VARIABLE pluginfiles) + STRING(REGEX REPLACE "\n" ";" pluginfiles "${pluginfiles}") +endif() + +# message("PLUGINFILES: ${pluginfiles}") +FILE(GLOB_RECURSE files RELATIVE + ${CMAKE_CURRENT_SOURCE_DIR} + ${pluginfiles} + ) + +foreach (FILE ${pluginfiles}) + if ("${FILE}" MATCHES "/acl.api.json") + set(ACL_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/nat.api.json") + set(NAT_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/nsh.api.json") + set(NSH_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/gtpu.api.json") + set(GTPU_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/pppoe.api.json") + set(PPPOE_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/trace.api.json") + set(IOAM_TRACE_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/pot.api.json") + set(IOAM_POT_JSON_FILE ${FILE}) + endif () + if ("${FILE}" MATCHES "/ioam_export.api.json") + set(IOAM_EXPORT_JSON_FILE ${FILE}) + endif () +endforeach() + +if(ACL_JSON_FILE) + java_api_binding (acl acl ${ACL_JSON_FILE}) +endif() + +if(NAT_JSON_FILE) + java_api_binding (nat nat ${NAT_JSON_FILE}) +endif() + +if(NSH_JSON_FILE) + java_api_binding (nsh nsh ${NSH_JSON_FILE}) +endif() + +if(GTPU_JSON_FILE) + java_api_binding (gtpu gtpu ${GTPU_JSON_FILE}) +endif() + +if(PPPOE_JSON_FILE) + java_api_binding (pppoe pppoe ${PPPOE_JSON_FILE}) +endif() + +if(IOAM_TRACE_JSON_FILE) + java_api_binding (ioamtrace ioam_trace ${IOAM_TRACE_JSON_FILE}) +endif() + +if(IOAM_POT_JSON_FILE) + java_api_binding (ioampot ioam_pot ${IOAM_POT_JSON_FILE}) +endif() + +if(IOAM_EXPORT_JSON_FILE) + java_api_binding (ioamexport ioam_export ${IOAM_EXPORT_JSON_FILE}) +endif() + +# Package Generator ####################################################### +unset(vpp_version) +set(COLUMNS 200) +execute_process( + COMMAND dpkg -l vpp + COMMAND grep vpp + COMMAND awk "{print $3}" OUTPUT_VARIABLE vpp_version) +string(REGEX REPLACE "\n$" "" vpp_version "${vpp_version}") + +set(CPACK_PACKAGE_DESCRIPTION "VPP-Java-API-bindings + This package contains VPP java api bindings.") +set(CPACK_PACKAGE_NAME "vpp-api-java") +set(CPACK_PACKAGE_VERSION "${JVPP_VERSION}") +set(CPACK_PACKAGE_SECTION net) +set(CPACK_PACKAGE_VENDOR "Cisco") +set(CPACK_PACKAGE_CONTACT "hc2vpp@lists.fd.io") +set(CPACK_OUTPUT_FILE_PREFIX build-root/packages) +set(CPACK_PACKAGE_FILE_NAME "vpp-api-java_${JVPP_VERSION}") +if (${RELEASE_ID} MATCHES "Ubuntu") + set(CPACK_GENERATOR "DEB") + set(CPACK_DEBIAN_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_DEBIAN_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") + set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}") + set(CPACK_DEBIAN_PACKAGE_SECTION "${CPACK_PACKAGE_SECTION}") + set(CPACK_DEBIAN_PACKAGE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}) + set(CPACK_DEBIAN_PACKAGE_PRIORITY extra) + set(CPACK_DEBIAN_PACKAGE_DEPENDS "vpp (>= ${vpp_version}), vpp-plugins (>= ${vpp_version})") + + # Print Debian package summary + message("\nDebian package:") + message(" Name: ${CPACK_DEBIAN_PACKAGE_NAME}") + message(" Version: ${CPACK_DEBIAN_PACKAGE_VERSION}") + message(" Description: ${CPACK_DEBIAN_PACKAGE_DESCRIPTION}") + message(" File name: ${CPACK_DEBIAN_PACKAGE_FILE_NAME}") + message(" Depends on: ${CPACK_DEBIAN_PACKAGE_DEPENDS}\n") + include (CPack) +elseif(${RELEASE_ID} MATCHES "CentOS") + set(CPACK_GENERATOR "RPM") + # Excluding /usr/share/java top level directory due to conflict with javapackages-tools (openjdk dependency) + set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/usr/share/java") + set(CPACK_RPM_PACKAGE_NAME "${CPACK_PACKAGE_NAME}") + set(CPACK_RPM_PACKAGE_VERSION "${CPACK_PACKAGE_VERSION}") + set(CPACK_RPM_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION}") + set(CPACK_RPM_PACKAGE_SECTION "${CPACK_PACKAGE_SECTION}") + set(CPACK_RPM_PACKAGE_FILE_NAME ${CPACK_PACKAGE_FILE_NAME}) + set(CPACK_RPM_PACKAGE_PRIORITY extra) + set(CPACK_RPM_PACKAGE_DEPENDS "vpp (>= ${vpp_version}), vpp-plugins (>= ${vpp_version})") + + # Print CentOS package summary + message("\nRPM package:") + message(" Name: ${CPACK_RPM_PACKAGE_NAME}") + message(" Version: ${CPACK_RPM_PACKAGE_VERSION}") + message(" Description: ${CPACK_RPM_PACKAGE_DESCRIPTION}") + message(" File name: ${CPACK_RPM_PACKAGE_FILE_NAME}") + message(" Depends on: ${CPACK_RPM_PACKAGE_DEPENDS}\n") + include (CPack) +endif () + diff --git a/java/Readme.txt b/java/Readme.txt new file mode 100644 index 0000000..689b9b3 --- /dev/null +++ b/java/Readme.txt @@ -0,0 +1,236 @@ += JVpp + +JVpp is JNI based Java API for VPP. + +== Features +It is: + +* Asynchronous +* Fully generated +* Lightweight + +== Architecture + +=== Plugin support + + /-------------\ /--------------\ /---------------\ + | JvppPlugin1 +<-------+ JVppRegistry +--------->+ VppConnection | + \-------------/ inits \--+-----------/ uses \---------------/ + | + /-------------\ | + | JvppPlugin2 +<----------+ inits + \-------------/ | + | + ... | + | + /----------\ | + | JVppCore +<-------------+ + \----------/ + + +VppRegistry opens connection to vpp (VppConnection) and manages jvpp plugins. +Each plugin needs to be registered in the VppRegistry. Registration involves +plugin initialization (providing JNI implementation with JVppCallback reference, +vpp client identifier and vpp shared memory queue address). + +API user sends message by calling a method of appropriate plugin interface. +The call is delegated to JNI implementation provided by the particular plugin. +When JNI code receives reply, it invokes callback method of JVppCallback +that corresponds to the received message reply. + +=== JVppCore as an example of JVpp plugin architecture + + JVpp Java + + /--------------\ /----------\ /------------\ /------\ + | JVppRegistry | | JVppCore | | Callbacks | | DTOs | + \----+---------/ \----+-----/ \------+-----/ \------/ + ^ ^ ^ + | implements | implements | implements + /----+--------------\ /---+----------\ /-----+---------\ + | JVppRegistryImpl* +-------->+ JVppCoreImpl | | JVppCallback | + \-------+-----------/ inits \---+----------/ \-------+-------/ + | | ^ + | | uses | calls back + | | | +----------|--------------------------|-----------------------|--------------------- + | | | + C JNI | +-------------------+ | /-----------------\ + v | | +-->+ jvpp_core_gen.h | + /--------+--------\ | | | \-----------------/ + | jpp_registry.c* +---+ /--------+----+----\ | | | + \-----------------/ | | << shared lib >> | /-+--+---+------\ + + ->+ jvpp_common* <--------+ jvpp_core.c* | + uses \------------------/ uses \---------------/ + + +* Components marked with an asterisk contain manually crafted code, which in addition +to generated classes form jvpp. Exception applies to Callbacks and DTOs, since there are +manually crafted marker interfaces in callback and dto package (dto/JVppRequest, dto/JVppReply, +dto/JVppDump, dto/JVppReplyDump, callback/JVppCallback) + +Note: jvpp_core.c calls back the JVppCallback instance with every response. An instance of the +JVppCallback is provided to jvpp_core.c by JVppRegistryImpl on JVppCoreImpl initialization. + +Part of the JVpp is also Future facade. It is asynchronous API returning Future objects +on top of low level JVpp. It wraps dump reply messages in one DTO using control_ping message +(provided by JVppRegistry). + + +Future facade + + /----------------\ /---------------\ + | FutureJVppCore | +-->+ JVppRegistry* | + \-----+----------/ | \---------------/ + ^ | + | implements | uses + | | + /--------+-------------\ | /------------------------------\ + | FutureJVppCoreFacade +---+--->+ FutureJVppCoreFacadeCallback | + \---------+------------/ uses \-------+----------------------/ + | | +---------------|-----------------------------|------------------------------- + | uses | implements +JVpp Java | | + | | + /----------\ | | + | JVppCore +<-+ | + \----+-----/ | + ^ | + | implements v + /----+---------\ /--------+---------------\ + | JVppCoreImpl | | JVppCoreGlobalCallback | + \--------------/ \------------------------/ + + + +Another useful utility of the JVpp is Callback facade. It is asynchronous API +capable of calling specific callback instance (provided when performing a call) +per call. + + +Callback facade + + /------------------\ /---------------\ + | CallbackJVppCore | +-->+ JVppRegistry* | + \-----+------------/ | \---------------/ + ^ | + | implements | uses + | | + /--------+---------------\ | /--------------------------\ + | CallbackJVppCoreFacade +---+--->+ CallbackJVppCoreCallback | + \---------+--------------/ uses \-----+--------------------/ + | | +---------------|-----------------------------|------------------------------- + | uses | implements +JVpp Java | | + | | + /----------\ | | + | JVppCore +<-+ | + \----+-----/ | + ^ | + | implements v + /----+---------\ /----------+-------------\ + | JVppCoreImpl | | JVppCoreGlobalCallback | + \--------------/ \------------------------/ + + +== Package structure + +* *io.fd.vpp.jvpp* - top level package for generated JVpp interface+ implementation and hand-crafted +VppConnection interface + implementation - packaged as jvpp-registry-version.jar + +* *io.fd.vpp.jvpp.[plugin]* - top level package for generated JVpp interface + implementation ++ plugin's API tests - packaged as jvpp-[plugin]-version.jar + +** *dto* - package for DTOs generated from VPP API structures + base/marker hand-crafted interfaces +(in case of jvpp-registry) +** *callback* - package for low-level JVpp callbacks and a global callback interface implementing each of +the low-level JVppcallbacks +** *future* - package for future based facade on top of JVpp and callbacks +** *callfacade* - package for callback based facade on top of JVpp and callbacks. Allowing +users to provide callback per request +** *test* - package for JVpp standalone tests. Can also serve as samples for JVpp. + +C code is structured into modules: + +* *jvpp_common* - shared library that provides jvpp_main_t reference used by jvpp_registry and plugins. + +* *jvpp_registry* - native library used by JVppRegistryImpl, responsible for: + +** VPP connection open/close +** Rx thread to java thread attach +** control ping message handling + +* *jvpp_core* - native library used by jvpp core plugin: +** *jvpp_core.c* - contains hand crafted code for core plugin initialization +** *jvpp_core_gen.h* - contains generated JNI compatible handlers for all requests and replies defined in vpe.api + +== Code generators +All of the required code except the base/marker interfaces is generated using +simple python2 code generators. The generators use __defs_vpp_papi.py__ input +file produced by __vppapigen__ from vpe.api file. + +=== JNI compatible C code +Produces __jvpp_[plugin]_gen.h__ file containing JNI compatible handlers for each VPP +request and reply. + +[NOTE] +==== +Source: jvpp_c_gen.py +==== + +=== Request/Reply DTOs +For all the structures in __defs_vpp_papi.py__ a POJO DTO is produced. Logically, +there are 4 types of DTOs: + +* Request - requests that can be sent to VPP and only a single response is expected +* DumpRequest - requests that can be sent to VPP and a stream of responses is expected +* Reply - reply to a simple request or a single response from dump triggered response stream +* ReplyDump - collection of replies from a single dump request +* Notifications/Events - Not implemented yet + +[NOTE] +==== +Source: dto_gen.py +==== + +=== JVpp +Produces __JVpp.java__ and __JVppImpl.java__. This is the layer right above JNI compatible C +code. + +[NOTE] +==== +Source: jvpp_impl_gen.py +==== + +=== Callbacks +Produces callback interface for each VPP reply + a global callback interface called +__JVpp[plugin]GlobalCallback.java__ aggregating all of the callback interfaces. The JNI +compatible C code expects only a single instance of this global callback and calls +it with every reply. + +[NOTE] +==== +Source: callback_gen.py +==== + +=== Future facade +Produces an asynchronous facade on top of JVpp and callbacks, which returns a Future that provides +matching reply once VPP invocation finishes. Sources produced: +__FutureJVpp[plugin].java, FutureJVpp[plugin]Facade.java and FutureJVpp[plugin]Callback.java__ + +[NOTE] +==== +Source: jvpp_future_facade_gen.py +==== + +=== Callback facade +Similar to future facade, only this facade takes callback objects as part of the invocation +and the callback is called with result once VPP invocation finishes. Sources produced: +__CallbackJVpp[plugin].java, CallbackJVpp[plugin]Facade.java and CallbackJVpp[plugin]Callback.java__ + +[NOTE] +==== +Source: jvpp_callback_facade_gen.py +==== diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java new file mode 100644 index 0000000..4806052 --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclExpectedDumpData.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + + +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_ADDRESS_2_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_ADDRESS_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_DST_ICMP_TYPE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_DST_ICMP_TYPE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_MAC; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_MAC_MASK; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_PREFIX; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_PREFIX_2; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_SRC_ICMP_TYPE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.FIRST_RULE_SRC_ICMP_TYPE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.ICMP_PROTOCOL; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_ADDRESS_2_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_ADDRESS_AS_ARRAY; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_DST_PORT_RANGE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_DST_PORT_RANGE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_MAC; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_MAC_MASK; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_PREFIX; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_PREFIX_2; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_SRC_PORT_RANGE_END; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.SECOND_RULE_SRC_PORT_RANGE_START; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.UDP_PROTOCOL; + +import io.fd.vpp.jvpp.acl.dto.AclDetails; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceListDetails; +import io.fd.vpp.jvpp.acl.dto.MacipAclDetails; +import io.fd.vpp.jvpp.acl.types.AclRule; +import io.fd.vpp.jvpp.acl.types.MacipAclRule; +import java.util.Arrays; + +class AclExpectedDumpData { + + static void verifyMacIpDump(final MacipAclDetails macipAclDetails) { + // asserting data create by previous call + assertEquals(0, macipAclDetails.aclIndex); + assertEquals(2, macipAclDetails.count); + + final MacipAclRule currentIpv4Rule = macipAclDetails.r[0]; + final MacipAclRule currentIpv6Rule = macipAclDetails.r[1]; + + // Comparing one property at the time to better pointer if something is wrong + //Ipv4 rule + assertEquals(0, currentIpv4Rule.isIpv6); + assertEquals(1, currentIpv4Rule.isPermit); + + // cutting expected ipv4 to 4 bytes,vpp sends it as 16 always + assertArrays(FIRST_RULE_ADDRESS_AS_ARRAY, Arrays.copyOfRange(currentIpv4Rule.srcIpAddr, 0, 4)); + assertEquals(FIRST_RULE_PREFIX, currentIpv4Rule.srcIpPrefixLen); + assertArrays(FIRST_RULE_MAC, currentIpv4Rule.srcMac); + assertArrays(FIRST_RULE_MAC_MASK, currentIpv4Rule.srcMacMask); + + //Ipv6 rule + assertEquals(1, currentIpv6Rule.isIpv6); + assertEquals(0, currentIpv6Rule.isPermit); + assertArrays(SECOND_RULE_ADDRESS_AS_ARRAY, currentIpv6Rule.srcIpAddr); + assertEquals(SECOND_RULE_PREFIX, currentIpv6Rule.srcIpPrefixLen); + assertArrays(SECOND_RULE_MAC, currentIpv6Rule.srcMac); + assertArrays(SECOND_RULE_MAC_MASK, currentIpv6Rule.srcMacMask); + } + + static void verifyAclDump(final AclDetails aclDetails) { + assertEquals(0, aclDetails.aclIndex); + assertEquals(2, aclDetails.count); + + final AclRule currentIpv4Rule = aclDetails.r[0]; + final AclRule currentIpv6Rule = aclDetails.r[1]; + + // Comparing one property at the time to better pointer if something is wrong + //Ipv4 rule + assertEquals(0, currentIpv4Rule.isIpv6); + assertEquals(1, currentIpv4Rule.isPermit); + + // cutting expected ipv4 to 4 bytes,vpp sends it as 16 always + assertArrays(FIRST_RULE_ADDRESS_AS_ARRAY, Arrays.copyOfRange(currentIpv4Rule.srcIpAddr, 0, 4)); + assertEquals(FIRST_RULE_PREFIX, currentIpv4Rule.srcIpPrefixLen); + assertArrays(FIRST_RULE_ADDRESS_2_AS_ARRAY, Arrays.copyOfRange(currentIpv4Rule.dstIpAddr, 0, 4)); + assertEquals(FIRST_RULE_PREFIX_2, currentIpv4Rule.dstIpPrefixLen); + + assertEquals(ICMP_PROTOCOL, currentIpv4Rule.proto); + assertEquals(FIRST_RULE_SRC_ICMP_TYPE_START, currentIpv4Rule.srcportOrIcmptypeFirst); + assertEquals(FIRST_RULE_SRC_ICMP_TYPE_END, currentIpv4Rule.srcportOrIcmptypeLast); + assertEquals(FIRST_RULE_DST_ICMP_TYPE_START, currentIpv4Rule.dstportOrIcmpcodeFirst); + assertEquals(FIRST_RULE_DST_ICMP_TYPE_END, currentIpv4Rule.dstportOrIcmpcodeLast); + + assertArrays(SECOND_RULE_ADDRESS_AS_ARRAY, currentIpv6Rule.srcIpAddr); + assertEquals(SECOND_RULE_PREFIX, currentIpv6Rule.srcIpPrefixLen); + assertArrays(SECOND_RULE_ADDRESS_2_AS_ARRAY, currentIpv6Rule.dstIpAddr); + assertEquals(SECOND_RULE_PREFIX_2, currentIpv6Rule.dstIpPrefixLen); + + assertEquals(UDP_PROTOCOL, currentIpv6Rule.proto); + assertEquals(SECOND_RULE_SRC_PORT_RANGE_START, currentIpv6Rule.srcportOrIcmptypeFirst); + assertEquals(SECOND_RULE_SRC_PORT_RANGE_END, currentIpv6Rule.srcportOrIcmptypeLast); + assertEquals(SECOND_RULE_DST_PORT_RANGE_START, currentIpv6Rule.dstportOrIcmpcodeFirst); + assertEquals(SECOND_RULE_DST_PORT_RANGE_END, currentIpv6Rule.dstportOrIcmpcodeLast); + } + + static void verifyAclInterfaceList(final AclInterfaceListDetails aclInterfaceListDetails) { + assertEquals(1, aclInterfaceListDetails.count); + assertEquals(1, aclInterfaceListDetails.acls[0]); + assertEquals(0, aclInterfaceListDetails.nInput); + assertEquals(0, aclInterfaceListDetails.swIfIndex); + } + + private static void assertArrays(final byte[] expected, final byte[] actual) { + if (!Arrays.equals(expected, actual)) { + throw new IllegalArgumentException( + String.format("Expected[%s]/Actual[%s]", Arrays.toString(expected), Arrays.toString(actual))); + } + } + + private static void assertEquals(final int expected, final int actual) { + if (expected != actual) { + throw new IllegalArgumentException(String.format("Expected[%s]/Actual[%s]", expected, actual)); + } + } +} diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java new file mode 100644 index 0000000..199b1b6 --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestData.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + + +import io.fd.vpp.jvpp.acl.types.AclRule; +import io.fd.vpp.jvpp.acl.types.MacipAclRule; + +class AclTestData { + + static final byte[] FIRST_RULE_ADDRESS_AS_ARRAY = {-64, -88, 2, 1}; + static final byte[] FIRST_RULE_ADDRESS_2_AS_ARRAY = {-64, -88, 2, 3}; + static final byte[] SECOND_RULE_ADDRESS_AS_ARRAY = + {32, 1, 13, -72, 10, 11, 18, -16, 0, 0, 0, 0, 0, 0, 0, 1}; + static final byte[] SECOND_RULE_ADDRESS_2_AS_ARRAY = + {32, 1, 13, -72, 10, 11, 18, -16, 0, 0, 0, 0, 0, 0, 0, 1}; + static final byte[] FIRST_RULE_MAC = {11, 11, 11, 11, 11, 11}; + static final byte[] FIRST_RULE_MAC_MASK = {0, 0, 0, 0, 0, 0}; + static final byte[] SECOND_RULE_MAC = {11, 12, 11, 11, 12, 11}; + static final byte[] SECOND_RULE_MAC_MASK = {(byte) 170, 0, 0, 0, 0, 0}; + static final int FIRST_RULE_PREFIX = 32; + static final int FIRST_RULE_PREFIX_2 = 24; + static final int SECOND_RULE_PREFIX = 64; + static final int SECOND_RULE_PREFIX_2 = 62; + static final int FIRST_RULE_DST_ICMP_TYPE_START = 0; + static final int FIRST_RULE_DST_ICMP_TYPE_END = 8; + static final int FIRST_RULE_SRC_ICMP_TYPE_START = 1; + static final int FIRST_RULE_SRC_ICMP_TYPE_END = 7; + static final int ICMP_PROTOCOL = 1; + static final int SECOND_RULE_DST_PORT_RANGE_START = 2000; + static final int SECOND_RULE_DST_PORT_RANGE_END = 6000; + static final int SECOND_RULE_SRC_PORT_RANGE_START = 400; + static final int SECOND_RULE_SRC_PORT_RANGE_END = 2047; + static final int UDP_PROTOCOL = 17; + + + static MacipAclRule[] createMacipRules() { + MacipAclRule ruleOne = new MacipAclRule(); + ruleOne.isIpv6 = 0; + ruleOne.isPermit = 1; + ruleOne.srcIpAddr = FIRST_RULE_ADDRESS_AS_ARRAY; + ruleOne.srcIpPrefixLen = FIRST_RULE_PREFIX; + ruleOne.srcMac = FIRST_RULE_MAC; + ruleOne.srcMacMask = FIRST_RULE_MAC_MASK;// no mask + + MacipAclRule ruleTwo = new MacipAclRule(); + ruleTwo.isIpv6 = 1; + ruleTwo.isPermit = 0; + ruleTwo.srcIpAddr = SECOND_RULE_ADDRESS_AS_ARRAY; + ruleTwo.srcIpPrefixLen = SECOND_RULE_PREFIX; + ruleTwo.srcMac = SECOND_RULE_MAC; + ruleTwo.srcMacMask = SECOND_RULE_MAC_MASK; + + return new MacipAclRule[]{ruleOne, ruleTwo}; + } + + static AclRule[] createAclRules() { + AclRule ruleOne = new AclRule(); + + ruleOne.isIpv6 = 0; + ruleOne.isPermit = 1; + ruleOne.srcIpAddr = FIRST_RULE_ADDRESS_AS_ARRAY; + ruleOne.srcIpPrefixLen = FIRST_RULE_PREFIX; + ruleOne.dstIpAddr = FIRST_RULE_ADDRESS_2_AS_ARRAY; + ruleOne.dstIpPrefixLen = FIRST_RULE_PREFIX_2; + ruleOne.dstportOrIcmpcodeFirst = FIRST_RULE_DST_ICMP_TYPE_START; + ruleOne.dstportOrIcmpcodeLast = FIRST_RULE_DST_ICMP_TYPE_END; + ruleOne.srcportOrIcmptypeFirst = FIRST_RULE_SRC_ICMP_TYPE_START; + ruleOne.srcportOrIcmptypeLast = FIRST_RULE_SRC_ICMP_TYPE_END; + ruleOne.proto = ICMP_PROTOCOL; //ICMP + + AclRule ruleTwo = new AclRule(); + ruleTwo.isIpv6 = 1; + ruleTwo.isPermit = 0; + ruleTwo.srcIpAddr = SECOND_RULE_ADDRESS_AS_ARRAY; + ruleTwo.srcIpPrefixLen = SECOND_RULE_PREFIX; + ruleTwo.dstIpAddr = SECOND_RULE_ADDRESS_2_AS_ARRAY; + ruleTwo.dstIpPrefixLen = SECOND_RULE_PREFIX_2; + ruleTwo.dstportOrIcmpcodeFirst = SECOND_RULE_DST_PORT_RANGE_START; + ruleTwo.dstportOrIcmpcodeLast = SECOND_RULE_DST_PORT_RANGE_END; + ruleTwo.srcportOrIcmptypeFirst = SECOND_RULE_SRC_PORT_RANGE_START; + ruleTwo.srcportOrIcmptypeLast = SECOND_RULE_SRC_PORT_RANGE_END; + ruleTwo.proto = UDP_PROTOCOL; //UDP + + return new AclRule[]{ruleOne, ruleTwo}; + } +} diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java new file mode 100644 index 0000000..149ea46 --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/AclTestRequests.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + +import static io.fd.vpp.jvpp.acl.examples.AclTestData.createAclRules; +import static io.fd.vpp.jvpp.acl.examples.AclTestData.createMacipRules; + +import io.fd.vpp.jvpp.VppInvocationException; +import io.fd.vpp.jvpp.acl.dto.AclAddReplace; +import io.fd.vpp.jvpp.acl.dto.AclAddReplaceReply; +import io.fd.vpp.jvpp.acl.dto.AclDel; +import io.fd.vpp.jvpp.acl.dto.AclDelReply; +import io.fd.vpp.jvpp.acl.dto.AclDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.AclDump; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceListDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceListDump; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceSetAclList; +import io.fd.vpp.jvpp.acl.dto.AclInterfaceSetAclListReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclAdd; +import io.fd.vpp.jvpp.acl.dto.MacipAclAddReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclAddReplace; +import io.fd.vpp.jvpp.acl.dto.MacipAclAddReplaceReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclDel; +import io.fd.vpp.jvpp.acl.dto.MacipAclDelReply; +import io.fd.vpp.jvpp.acl.dto.MacipAclDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.MacipAclDump; +import io.fd.vpp.jvpp.acl.future.FutureJVppAclFacade; +import java.util.concurrent.ExecutionException; + +class AclTestRequests { + + static MacipAclDetailsReplyDump sendMacIpDumpRequest(final FutureJVppAclFacade jvpp) + throws ExecutionException, InterruptedException { + System.out.println("Sending MacipAclDump request..."); + MacipAclDetailsReplyDump dump = jvpp.macipAclDump(new MacipAclDump()).toCompletableFuture().get(); + System.out.println("MacipAclDump returned"); + return dump; + } + + static void sendMacIpAddRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final MacipAclAdd request = createMacIpAddRequest(); + System.out.printf("Sending MacipAclAdd request %s%n", request.toString()); + final MacipAclAddReply reply = jvpp.macipAclAdd(createMacIpAddRequest()).toCompletableFuture().get(); + System.out.printf("MacipAclAdd send result = %s%n", reply); + } + + static void sendMacIpAddReplaceRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final MacipAclAddReplace request = createMacIpAddReplaceRequest(); + System.out.printf("Sending MacipAclAddReplace request %s%n", request.toString()); + final MacipAclAddReplaceReply reply = jvpp.macipAclAddReplace(createMacIpAddReplaceRequest()).toCompletableFuture().get(); + System.out.printf("MacipAclAddReplace send result = %s%n", reply); + } + + static void sendMacIpDelRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final MacipAclDel request = new MacipAclDel(); + request.aclIndex = 0; + System.out.printf("Sending MacipAclDel request %s%n", request.toString()); + final MacipAclDelReply reply = jvpp.macipAclDel(request).toCompletableFuture().get(); + System.out.printf("MacipAclDel send result = %s%n", reply); + } + + static void sendAclAddRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final AclAddReplace request = createAclAddRequest(); + System.out.printf("Sending AclAddReplace request %s%n", request.toString()); + final AclAddReplaceReply reply = jvpp.aclAddReplace(request).toCompletableFuture().get(); + System.out.printf("AclAddReplace send result = %s%n", reply); + } + + static AclDetailsReplyDump sendAclDumpRequest(final FutureJVppAclFacade jvpp) + throws InterruptedException, VppInvocationException, ExecutionException { + System.out.println("Sending AclDump request..."); + final AclDetailsReplyDump dump = jvpp.aclDump(new AclDump()).toCompletableFuture().get(); + System.out.printf("AclDump send result = %s%n", dump); + return dump; + } + + static void sendAclDelRequest(final FutureJVppAclFacade jvpp) throws InterruptedException, ExecutionException { + final AclDel request = new AclDel(); + request.aclIndex = 0; + System.out.printf("Sending AclDel request %s%n", request.toString()); + final AclDelReply reply = jvpp.aclDel(request).toCompletableFuture().get(); + System.out.printf("AclDel send result = %s%n", reply); + } + + static AclInterfaceListDetailsReplyDump sendAclInterfaceListDumpRequest(final FutureJVppAclFacade jvpp) + throws InterruptedException, ExecutionException { + final AclInterfaceListDump request = new AclInterfaceListDump(); + request.swIfIndex = 0; + System.out.printf("Sending AclInterfaceListDump request %s%n", request.toString()); + final AclInterfaceListDetailsReplyDump dump = jvpp.aclInterfaceListDump(request).toCompletableFuture().get(); + System.out.printf("AclInterfaceListDump send result = %s%n", dump); + return dump; + } + + static void sendAclInterfaceSetAclList(final FutureJVppAclFacade jvpp) + throws InterruptedException, ExecutionException { + final AclInterfaceSetAclList request = new AclInterfaceSetAclList(); + request.count = 1; + request.acls = new int[]{1}; + request.swIfIndex = 0; + request.nInput = 0; + System.out.printf("Sending AclInterfaceSetAclList request %s%n", request.toString()); + final AclInterfaceSetAclListReply reply = jvpp.aclInterfaceSetAclList(request).toCompletableFuture().get(); + System.out.printf("AclInterfaceSetAclList send result = %s%n", reply); + } + + static void sendAclInterfaceDeleteList(final FutureJVppAclFacade jvpp) + throws InterruptedException, ExecutionException { + // uses same api but sets list to empty + final AclInterfaceSetAclList request = new AclInterfaceSetAclList(); + request.count = 0; + request.acls = new int[]{}; + request.swIfIndex = 0; + request.nInput = 0; + System.out.printf("Sending AclInterfaceSetAclList(Delete) request %s%n", request.toString()); + final AclInterfaceSetAclListReply reply = jvpp.aclInterfaceSetAclList(request).toCompletableFuture().get(); + System.out.printf("AclInterfaceSetAclList(Delete) send result = %s%n", reply); + } + + private static MacipAclAdd createMacIpAddRequest() { + MacipAclAdd request = new MacipAclAdd(); + + request.count = 2; + request.r = createMacipRules(); + return request; + } + + private static MacipAclAddReplace createMacIpAddReplaceRequest() { + MacipAclAddReplace request = new MacipAclAddReplace(); + + request.count = 2; + request.aclIndex = 0; + request.r = createMacipRules(); + return request; + } + + private static AclAddReplace createAclAddRequest() { + AclAddReplace request = new AclAddReplace(); + + request.aclIndex = -1;// to define new one + request.count = 2; + request.r = createAclRules(); + return request; + } +} diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java new file mode 100644 index 0000000..862df8d --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/FutureApiExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.examples; + +import static io.fd.vpp.jvpp.acl.examples.AclExpectedDumpData.verifyAclDump; +import static io.fd.vpp.jvpp.acl.examples.AclExpectedDumpData.verifyAclInterfaceList; +import static io.fd.vpp.jvpp.acl.examples.AclExpectedDumpData.verifyMacIpDump; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclAddRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclDelRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclDumpRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclInterfaceDeleteList; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclInterfaceListDumpRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendAclInterfaceSetAclList; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendMacIpAddRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendMacIpDelRequest; +import static io.fd.vpp.jvpp.acl.examples.AclTestRequests.sendMacIpDumpRequest; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.acl.JVppAclImpl; +import io.fd.vpp.jvpp.acl.future.FutureJVppAclFacade; + +public class FutureApiExample { + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for acl plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("macipAclAddTest"); + final FutureJVppAclFacade jvpp = new FutureJVppAclFacade(registry, new JVppAclImpl())) { + + // adds,dump and verifies Mac-Ip acl + sendMacIpAddRequest(jvpp); + verifyMacIpDump(sendMacIpDumpRequest(jvpp).macipAclDetails.get(0)); + + // adds,dumps and verifies Acl acl + sendAclAddRequest(jvpp); + verifyAclDump(sendAclDumpRequest(jvpp).aclDetails.get(0)); + + // adds,dumps and verifies Interface for acl + sendAclInterfaceSetAclList(jvpp); + verifyAclInterfaceList(sendAclInterfaceListDumpRequest(jvpp).aclInterfaceListDetails.get(0)); + + // deletes all created data + sendAclInterfaceDeleteList(jvpp); + sendAclDelRequest(jvpp); + sendMacIpDelRequest(jvpp); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt new file mode 100644 index 0000000..d17fbfc --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.examples.FutureApiExample +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-debug-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.examples.FutureApiExample diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java new file mode 100644 index 0000000..a7bbb7f --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.acl.JVppAclImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for core plugin"); + testControlPing(args[0], new JVppAclImpl()); + } +} diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java new file mode 100644 index 0000000..ff1c73c --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/FutureApiTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.acl.test; + +import io.fd.vpp.jvpp.Assertions; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.acl.JVppAclImpl; +import io.fd.vpp.jvpp.acl.dto.AclDetailsReplyDump; +import io.fd.vpp.jvpp.acl.dto.AclDump; +import io.fd.vpp.jvpp.acl.future.FutureJVppAclFacade; + +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testFutureApi(args); + } + + private static void testFutureApi(String[] args) throws Exception { + LOG.info("Testing Java future API for core plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppAclFacade jvppFacade = new FutureJVppAclFacade(registry, new JVppAclImpl())) { + LOG.info("Successfully connected to VPP"); + + testAclDump(jvppFacade); + + LOG.info("Disconnecting..."); + } + } + + private static void testAclDump(final FutureJVppAclFacade jvpp) throws Exception { + LOG.info("Sending AclDump request..."); + final AclDump request = new AclDump(); + + final CompletableFuture<AclDetailsReplyDump> + replyFuture = jvpp.aclDump(request).toCompletableFuture(); + final AclDetailsReplyDump reply = replyFuture.get(); + + Assertions.assertNotNull(reply); + } + + +} diff --git a/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt new file mode 100644 index 0000000..1b46585 --- /dev/null +++ b/java/jvpp-acl/io/fd/vpp/jvpp/acl/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-acl-17.10.jar io.fd.vpp.jvpp.acl.test.[test-name] diff --git a/java/jvpp-acl/jvpp_acl.c b/java/jvpp-acl/jvpp_acl.c new file mode 100644 index 0000000..22e44f1 --- /dev/null +++ b/java/jvpp-acl/jvpp_acl.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <acl/acl_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <acl/acl_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-acl/io_fd_vpp_jvpp_acl_JVppAclImpl.h" +#include "jvpp_acl.h" +#include "jvpp-acl/jvpp_acl_gen.h" + +/* + * Class: io_fd_vpp_jvpp_acl_JVppaclImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_acl_JVppAclImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + acl_main_t * plugin_main = &acl_main; + clib_warning ("Java_io_fd_vpp_jvpp_acl_JVppAclImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_acl_JVppAclImpl_close0 +(JNIEnv *env, jclass clazz) { + acl_main_t * plugin_main = &acl_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-acl/jvpp_acl.h b/java/jvpp-acl/jvpp_acl.h new file mode 100644 index 0000000..d1ec78f --- /dev/null +++ b/java/jvpp-acl/jvpp_acl.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_acl_h__ +#define __included_jvpp_acl_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-acl */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} acl_main_t; + +acl_main_t acl_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_acl_h__ */ diff --git a/java/jvpp-common/jvpp_common.c b/java/jvpp-common/jvpp_common.c new file mode 100644 index 0000000..2425607 --- /dev/null +++ b/java/jvpp-common/jvpp_common.c @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2016 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 _GNU_SOURCE /* for strcasestr(3) */ + +#include <vnet/api_errno.h> +#include "jvpp_common.h" + +#ifndef JVPP_DEBUG +#define JVPP_DEBUG 0 +#endif + +#if JVPP_DEBUG == 1 +#define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else +#define DEBUG_LOG(...) +#endif + +#define _(error,errorCode,msg) \ +if (errorCode == code) \ + message = msg; \ +else + +#define get_error_message(errno) \ +int code = errno; \ +foreach_vnet_api_error \ + message = "Reason unknown"; + +/* shared jvpp main structure */ +jvpp_main_t jvpp_main __attribute__((aligned (64))); + +void call_on_error(const char* callName, int contextId, int retval, + jclass callbackClass, jobject callbackObject, + jclass callbackExceptionClass) { + DEBUG_LOG("\nCallOnError : callback=%s, retval=%d, context=%d\n", callName, + clib_net_to_host_u32(retval), clib_net_to_host_u32(context)); + JNIEnv *env = jvpp_main.jenv; + if (!callbackClass) { + DEBUG_LOG("CallOnError : jm->callbackClass is null!\n"); + return; + } + jmethodID excConstructor = (*env)->GetMethodID(env, callbackExceptionClass, + "<init>", "(Ljava/lang/String;Ljava/lang/String;II)V"); + if (!excConstructor) { + DEBUG_LOG("CallOnError : excConstructor is null!\n"); + return; + } + jmethodID callbackExcMethod = (*env)->GetMethodID(env, callbackClass, + "onError", "(Lio/fd/vpp/jvpp/VppCallbackException;)V"); + if (!callbackExcMethod) { + DEBUG_LOG("CallOnError : callbackExcMethod is null!\n"); + return; + } + + char *message; + get_error_message(clib_net_to_host_u32(retval)); + jobject excObject = (*env)->NewObject(env, callbackExceptionClass, + excConstructor, (*env)->NewStringUTF(env, callName), + (*env)->NewStringUTF(env, message), + clib_net_to_host_u32(contextId), clib_net_to_host_u32(retval)); + if (!excObject) { + DEBUG_LOG("CallOnError : excObject is null!\n"); + return; + } + + (*env)->CallVoidMethod(env, callbackObject, callbackExcMethod, excObject); + DEBUG_LOG("CallOnError : Response sent\n"); +} +#undef _ + +u32 get_message_id(JNIEnv *env, const char *key) { + uword *p = hash_get(jvpp_main.messages_hash, key); + if (!p) { + jclass exClass = (*env)->FindClass(env, "java/lang/IllegalStateException"); + char *msgBuf = clib_mem_alloc(strlen(key) + 70); + strcpy(msgBuf, "API mismatch detected: "); + strcat(msgBuf, key); + strcat(msgBuf, " is missing in global name_crc hash table."); + DEBUG_LOG("%s", msgBuf); + DEBUG_LOG("Possible reasons:"); + DEBUG_LOG("1) incompatible VPP version used"); + DEBUG_LOG("2) message present in JSON file but not in global name_crc table"); + (*env)->ThrowNew(env, exClass, msgBuf); + clib_mem_free(msgBuf); + return 0; + } + return (u32) p[0]; +} diff --git a/java/jvpp-common/jvpp_common.h b/java/jvpp-common/jvpp_common.h new file mode 100644 index 0000000..e12141b --- /dev/null +++ b/java/jvpp-common/jvpp_common.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2016 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_jvpp_common_h__ +#define __included_jvpp_common_h__ +// +#include <vppinfra/types.h> +#include <vlibapi/api.h> +#include <vlibapi/api_types.h> +#include <vlibmemory/api.h> +#include <jni.h> + +typedef struct { + /* Unique identifier used for matching replays with requests */ + volatile u32 context_id; + + /* Spinlock */ + volatile u32 lock; + u32 tag; + + /* JNI Native Method Interface pointer for message handlers */ + JNIEnv *jenv; + + /* JNI Invoke Interface pointer for attachment of rx thread to java thread */ + JavaVM *jvm; + + /* Convenience */ + svm_queue_t * vl_input_queue; + u32 my_client_index; + uword *messages_hash; +} jvpp_main_t; + +extern jvpp_main_t jvpp_main __attribute__((aligned (64))); + +static_always_inline u32 vppjni_get_context_id(jvpp_main_t * jm) { + return clib_atomic_add_fetch(&jm->context_id, 1); +} + +static_always_inline void vppjni_lock(jvpp_main_t * jm, u32 tag) { + while (clib_atomic_test_and_set(&jm->lock)) + ; + jm->tag = tag; +} + +static_always_inline void vppjni_unlock(jvpp_main_t * jm) { + jm->tag = 0; + CLIB_MEMORY_BARRIER(); + jm->lock = 0; +} + +/** + * Calls onError callback on callbackObject reference. Passes instance of callbackExceptionClass as parameter. + */ +void call_on_error(const char* callName, int contextId, int retval, + jclass callbackClass, jobject callbackObject, + jclass callbackExceptionClass); + +/** + * Retrieves message id based on message name and crc (key format: name_crc). + * Throws java/lang/IllegalStateException on failure. + */ +u32 get_message_id(JNIEnv *env, const char* key); + +#endif /* __included_jvpp_common_h__ */ diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java new file mode 100644 index 0000000..b6558ff --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiExample.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.GetNodeIndexReplyCallback; +import io.fd.vpp.jvpp.core.callback.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.callback.SwInterfaceDetailsCallback; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersion; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetails; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import java.nio.charset.StandardCharsets; + +public class CallbackApiExample { + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API with JVppRegistry"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackApiExample"); + final JVpp jvpp = new JVppCoreImpl()) { + registry.register(jvpp, new TestCallback()); + + System.out.println("Sending ShowVersion request..."); + final int result = jvpp.send(new ShowVersion()); + System.out.printf("ShowVersion send result = %d%n", result); + + System.out.println("Sending GetNodeIndex request..."); + GetNodeIndex getNodeIndexRequest = new GetNodeIndex(); + getNodeIndexRequest.nodeName = "non-existing-node".getBytes(StandardCharsets.UTF_8); + jvpp.send(getNodeIndexRequest); + + System.out.println("Sending SwInterfaceDump request..."); + SwInterfaceDump swInterfaceDumpRequest = new SwInterfaceDump(); + swInterfaceDumpRequest.nameFilterValid = 0; + swInterfaceDumpRequest.nameFilter = "".getBytes(StandardCharsets.UTF_8); + jvpp.send(swInterfaceDumpRequest); + + Thread.sleep(1000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements GetNodeIndexReplyCallback, ShowVersionReplyCallback, SwInterfaceDetailsCallback { + + @Override + public void onGetNodeIndexReply(final GetNodeIndexReply msg) { + System.out.printf("Received GetNodeIndexReply: %s%n", msg); + } + + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("Received ShowVersionReply: context=%d, program=%s, version=%s, " + + "buildDate=%s, buildDirectory=%s%n", + msg.context, + msg.program, + msg.version, + msg.buildDate, + msg.buildDirectory); + } + + @Override + public void onSwInterfaceDetails(final SwInterfaceDetails msg) { + System.out.printf("Received SwInterfaceDetails: interfaceName=%s, l2AddressLength=%d, adminUpDown=%d, " + + "linkUpDown=%d, linkSpeed=%d, linkMtu=%d%n", + msg.interfaceName, msg.l2AddressLength, msg.adminUpDown, + msg.linkUpDown, msg.linkSpeed, (int) msg.linkMtu); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiReadPerfTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiReadPerfTest.java new file mode 100644 index 0000000..6ff440d --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiReadPerfTest.java @@ -0,0 +1,146 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.dto.*; + +import java.util.logging.Logger; + +public class CallbackApiReadPerfTest { + + private static final Logger LOG = Logger.getLogger(CallbackApiReadPerfTest.class.getName()); + private static final ShowVersion REQUEST = new ShowVersion(); + + /** + * + * @param args - for running for one sec requires no parameter + * - for running for set amount of requests requires one parameters, desired REQUEST amount + * @throws Exception if arguments aren't String representations of numbers + */ + public static void main(String[] args) throws Exception { + if (args.length != 0) { + testInvokeCounter(true, Integer.parseUnsignedInt(args[0])); + } else { + testInvokeCounter(false, 0); + } + } + + /** + * + * @param setCount true = run with set amount of requests, false = run for 1 sec + * @param count number of request with which test should be run + * @throws Exception + */ + private static void testInvokeCounter(boolean setCount, int count) throws Exception { + LOG.info("Testing callback API Invocation Counter"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackApiReadPerfTest"); + final JVpp jvpp = new JVppCoreImpl()) { + TestCallback callback = new TestCallback(count); + registry.register(jvpp, callback); + if (!setCount) { + for(int i = 0; i < 5; i++) { + callback.reset(); + LOG.info("Starting invocation for 1sec"); + long time = System.nanoTime(); + do { + jvpp.send(REQUEST); + } while (System.nanoTime() - time < 1000000000 || callback.stop()); + int replyCount = callback.getReplyCounter(); + LOG.info(String.format("Invocation count within 1 second: %d", replyCount)); + } + } else { + for (int i = 0; i < 5; i++) { + LOG.info("Starting invocations"); + callback.reset(); + long time = System.nanoTime(); + for (int x = 0; x < count; x++) { + jvpp.send(REQUEST); + } + long timeAfter = callback.getTime(); + LOG.info(String.format("Invocations took %d ns (%f invocations/s)", timeAfter - time, + count * (1000000000.0/(timeAfter - time)))); + } + } + + + Thread.sleep(1000); + LOG.info("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements ShowVersionReplyCallback { + + private int replyCounter = 0; + private int count; + private long time = 0; + private boolean stop = false; + + public TestCallback(int count) throws Exception { + this.count = count; + } + + public int getReplyCounter() { + return replyCounter; + } + + public void reset() { + replyCounter = 0; + time = 0; + stop = false; + } + + public boolean stop() { + this.stop = true; + return false; + } + + /* actual method called from VPP + not thread safe but since there's only one VPP thread listening for requests and calling + this method it's OK + */ + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + if (stop) { + return; + } + replyCounter++; + if (replyCounter == count ) { + time = System.nanoTime(); + } + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + + public long getTime() throws Exception { + while(time == 0) { + Thread.sleep(1000); + } + return time; + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiWritePerfTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiWritePerfTest.java new file mode 100644 index 0000000..1940ddc --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackApiWritePerfTest.java @@ -0,0 +1,162 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.*; +import io.fd.vpp.jvpp.core.callback.ClassifyAddDelTableReplyCallback; + +import java.util.logging.Logger; + +public class CallbackApiWritePerfTest { + + private static final Logger LOG = Logger.getLogger(CallbackApiWritePerfTest.class.getName()); + private static final ClassifyAddDelTable REQUEST = createAddDelTable(); + + private static ClassifyAddDelTable createAddDelTable () { + ClassifyAddDelTable addDelTable = new ClassifyAddDelTable(); + addDelTable.isAdd = 1; + addDelTable.tableIndex = -1; + addDelTable.nbuckets = 2; + addDelTable.memorySize = 2 << 20; + addDelTable.nextTableIndex = ~0; // default + addDelTable.missNextIndex = ~0; // default + addDelTable.skipNVectors = 0; + addDelTable.matchNVectors = 1; + addDelTable.mask = + new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00}; + return addDelTable; + }; + + /** + * + * @param args - for running for one sec requires no parameter + * - for running for set amount of requests requires one parameters, desired REQUEST amount + * @throws Exception if arguments aren't String representations of numbers + */ + public static void main(String[] args) throws Exception { + if (args.length != 0) { + testInvokeCounter(true, Integer.parseUnsignedInt(args[0])); + } else { + testInvokeCounter(false, 0); + } + } + + /** + * + * @param setCount true = run with set amount of requests, false = run for 1 sec + * @param count number of requests with which test should be run + * @throws Exception + */ + private static void testInvokeCounter(boolean setCount, int count) throws Exception { + LOG.info("Testing callback API Invocation Counter"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackApiWritePerfTest"); + final JVpp jvpp = new JVppCoreImpl()) { + TestCallback callback = new TestCallback(count); + registry.register(jvpp, callback); + if (!setCount) { + for(int i = 0; i < 5; i++) { + callback.reset(); + LOG.info("Starting invocation for 1sec"); + long time = System.nanoTime(); + do { + jvpp.send(REQUEST); + } while (System.nanoTime() - time < 1000000000 || callback.stop()); + int replyCount = callback.getReplyCounter(); + LOG.info(String.format("Invocation count within 1 second: %d", replyCount)); + } + } else { + for(int i = 0; i < 5; i++) { + LOG.info("Starting invocations"); + callback.reset(); + long time = System.nanoTime(); + for (int x = 1; x <= count; x++) { + jvpp.send(REQUEST); + } + long timeAfter = callback.getTime(); + LOG.info(String.format("Invocations took %d ns (%f invocations/s)", timeAfter - time, + count * (1000000000.0 / (timeAfter - time)))); + } + } + + + Thread.sleep(1000); + LOG.info("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements ClassifyAddDelTableReplyCallback { + + private int replyCounter = 0; + private int count; + private long time = 0; + private boolean stop = false; + + public TestCallback(int count) throws Exception { + this.count = count; + } + + public int getReplyCounter() { + return replyCounter; + } + + public void reset() { + replyCounter = 0; + time = 0; + stop = false; + } + + public boolean stop() { + this.stop = true; + return false; + } + + /* actual method called from VPP + not thread safe but since there's only one VPP thread listening for requests and calling + this method it's OK + */ + @Override + public void onClassifyAddDelTableReply(final ClassifyAddDelTableReply msg) { + if (stop) { + return; + } + replyCounter++; + if (replyCounter == count ) { + time = System.nanoTime(); + } + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + + public long getTime() throws Exception { + while(time == 0) { + Thread.sleep(1000); + } + return time; + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackCliApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackCliApiExample.java new file mode 100644 index 0000000..b692291 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackCliApiExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2018 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.CliInbandReplyCallback; +import io.fd.vpp.jvpp.core.dto.CliInband; +import io.fd.vpp.jvpp.core.dto.CliInbandReply; + +import java.nio.charset.StandardCharsets; + +public class CallbackCliApiExample { + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for Cli with JVppRegistry"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackCliApiExample"); + final JVpp jvpp = new JVppCoreImpl()) { + registry.register(jvpp, new TestCallback()); + + System.out.println("Sending CliInband request..."); + CliInband req = new CliInband(); + req.cmd = "create loopback interface"; + final int result = jvpp.send(req); + System.out.printf("CliInband send result = %d%n", result); + + Thread.sleep(1000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + static class TestCallback implements CliInbandReplyCallback { + + @Override + public void onCliInbandReply(final CliInbandReply msg) { + System.out.printf("%s", msg.toString()); + System.out.printf("Received CliInbandReply: context=%d, reply=%s", msg.context, msg.reply); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java new file mode 100644 index 0000000..a8b9186 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeExample.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.GetNodeIndexReplyCallback; +import io.fd.vpp.jvpp.core.callback.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.callfacade.CallbackJVppCoreFacade; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import java.nio.charset.StandardCharsets; + +/** + * CallbackJVppFacade together with CallbackJVppFacadeCallback allow for setting different callback for each request. + * This is more convenient than the approach shown in CallbackApiExample. + */ +public class CallbackJVppFacadeExample { + + private static ShowVersionReplyCallback showVersionCallback1 = new ShowVersionReplyCallback() { + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("ShowVersionCallback1 received ShowVersionReply: context=%d, program=%s," + + "version=%s, buildDate=%s, buildDirectory=%s%n", msg.context, + msg.program, + msg.version, + msg.buildDate, + msg.buildDirectory); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in showVersionCallback1: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + } + }; + + private static ShowVersionReplyCallback showVersionCallback2 = new ShowVersionReplyCallback() { + @Override + public void onShowVersionReply(final ShowVersionReply msg) { + System.out.printf("ShowVersionCallback2 received ShowVersionReply: context=%d, program=%s," + + "version=%s, buildDate=%s, buildDirectory=%s%n", msg.context, + msg.program, + msg.version, + msg.buildDate, + msg.buildDirectory); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in showVersionCallback2: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + } + + }; + + private static GetNodeIndexReplyCallback getNodeIndexCallback = new GetNodeIndexReplyCallback() { + @Override + public void onGetNodeIndexReply(final GetNodeIndexReply msg) { + System.out.printf("Received GetNodeIndexReply: %s%n", msg); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in getNodeIndexCallback: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + } + }; + + private static void testCallbackFacade() throws Exception { + System.out.println("Testing CallbackJVppFacade"); + + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackFacadeExample"); + final CallbackJVppCoreFacade callbackFacade = new CallbackJVppCoreFacade(registry, new JVppCoreImpl())) { + System.out.println("Successfully connected to VPP"); + + callbackFacade.showVersion(showVersionCallback1); + callbackFacade.showVersion(showVersionCallback2); + + GetNodeIndex getNodeIndexRequest = new GetNodeIndex(); + getNodeIndexRequest.nodeName = "dummyNode".getBytes(StandardCharsets.UTF_8); + callbackFacade.getNodeIndex(getNodeIndexRequest, getNodeIndexCallback); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackFacade(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java new file mode 100644 index 0000000..832464a --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackJVppFacadeNotificationExample.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCore; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.WantInterfaceEventsReplyCallback; +import io.fd.vpp.jvpp.core.callback.SwInterfaceEventCallback; +import io.fd.vpp.jvpp.core.callfacade.CallbackJVppCoreFacade; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEventsReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; + +public class CallbackJVppFacadeNotificationExample { + + private static void testCallbackFacade() throws Exception { + System.out.println("Testing CallbackJVppFacade for notifications"); + + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackFacadeExample"); + final JVppCore jvpp = new JVppCoreImpl()) { + final CallbackJVppCoreFacade jvppCallbackFacade = new CallbackJVppCoreFacade(registry, jvpp); + System.out.println("Successfully connected to VPP"); + + final AutoCloseable notificationListenerReg = + jvppCallbackFacade.getEventRegistry().registerSwInterfaceEventCallback( + new SwInterfaceEventCallback() { + public void onSwInterfaceEvent(SwInterfaceEvent reply) { + System.out.printf("Received interface notification: ifc: %s%n", reply); + } + + public void onError (VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + }); + + jvppCallbackFacade.wantInterfaceEvents(NotificationUtils.getEnableInterfaceNotificationsReq(), + new WantInterfaceEventsReplyCallback() { + @Override + public void onWantInterfaceEventsReply(final WantInterfaceEventsReply reply) { + System.out.println("Interface events started"); + } + + @Override + public void onError(final VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + }); + + System.out.println("Changing interface configuration"); + NotificationUtils.getChangeInterfaceState().send(jvpp); + + Thread.sleep(1000); + + jvppCallbackFacade.wantInterfaceEvents(NotificationUtils.getDisableInterfaceNotificationsReq(), + new WantInterfaceEventsReplyCallback() { + @Override + public void onWantInterfaceEventsReply(final WantInterfaceEventsReply reply) { + System.out.println("Interface events stopped"); + } + + @Override + public void onError(final VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + }); + + notificationListenerReg.close(); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackFacade(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java new file mode 100644 index 0000000..9ed418e --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CallbackNotificationApiExample.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getChangeInterfaceState; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getDisableInterfaceNotificationsReq; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getEnableInterfaceNotificationsReq; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.printNotification; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.SwInterfaceEventCallback; +import io.fd.vpp.jvpp.core.callback.WantInterfaceEventsReplyCallback; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; +import io.fd.vpp.jvpp.core.dto.SwInterfaceSetFlagsReply; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEventsReply; + +public class CallbackNotificationApiExample { + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for notifications"); + try (final JVppRegistry registry = new JVppRegistryImpl("CallbackNotificationApiExample"); + final JVpp jvpp = new JVppCoreImpl()) { + registry.register(jvpp, new TestCallback()); + System.out.println("Successfully connected to VPP"); + + getEnableInterfaceNotificationsReq().send(jvpp); + System.out.println("Interface notifications started"); + // TODO test ifc dump which also triggers interface flags send + + System.out.println("Changing interface configuration"); + getChangeInterfaceState().send(jvpp); + + // Notifications are received + Thread.sleep(500); + + getDisableInterfaceNotificationsReq().send(jvpp); + System.out.println("Interface events stopped"); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static class TestCallback implements SwInterfaceEventCallback, + WantInterfaceEventsReplyCallback { + + @Override + public void onSwInterfaceEvent( + final SwInterfaceEvent msg) { + printNotification(msg); + } + + @Override + public void onWantInterfaceEventsReply(final WantInterfaceEventsReply wantInterfaceEventsReply) { + System.out.println("Interface notification stream updated"); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception in getNodeIndexCallback: call=%s, reply=%d, context=%d%n", + ex.getMethodName(), ex.getErrorCode(), ex.getCtxId()); + + } + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java new file mode 100644 index 0000000..3db6d30 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/CreateSubInterfaceExample.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import static java.util.Objects.requireNonNull; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.CreateSubif; +import io.fd.vpp.jvpp.core.dto.CreateSubifReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; + +/** + * <p>Tests sub-interface creation.<br> Equivalent to:<br> + * + * <pre>{@code + * vppctl create sub GigabitEthernet0/9/0 1 dot1q 100 inner-dot1q any + * } + * </pre> + * + * To verify invoke:<br> + * <pre>{@code + * vpp_api_test json + * vat# sw_interface_dump + * } + */ +public class CreateSubInterfaceExample { + + private static SwInterfaceDump createSwInterfaceDumpRequest(final String ifaceName) { + SwInterfaceDump request = new SwInterfaceDump(); + request.nameFilter = ifaceName.getBytes(StandardCharsets.UTF_8); + request.nameFilterValid = 1; + return request; + } + + private static void requireSingleIface(final SwInterfaceDetailsReplyDump response, final String ifaceName) { + if (response.swInterfaceDetails.size() != 1) { + throw new IllegalStateException( + String.format("Expected one interface matching filter %s but was %d", ifaceName, + response.swInterfaceDetails.size())); + } + } + + private static CreateSubif createSubifRequest(final int swIfIndex, final int subId) { + CreateSubif request = new CreateSubif(); + request.swIfIndex = swIfIndex; // super interface id + request.subId = subId; + request.noTags = 0; + request.oneTag = 0; + request.twoTags = 1; + request.dot1Ad = 0; + request.exactMatch = 1; + request.defaultSub = 0; + request.outerVlanIdAny = 0; + request.innerVlanIdAny = 1; + request.outerVlanId = 100; + request.innerVlanId = 0; + return request; + } + + private static void print(CreateSubifReply reply) { + System.out.printf("CreateSubifReply: %s%n", reply); + } + + private static void testCreateSubInterface() throws Exception { + System.out.println("Testing sub-interface creation using Java callback API"); + try (final JVppRegistry registry = new JVppRegistryImpl("CreateSubInterfaceExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + final String ifaceName = "Gigabitethernet0/8/0"; + + final SwInterfaceDetailsReplyDump swInterfaceDetails = + jvppFacade.swInterfaceDump(createSwInterfaceDumpRequest(ifaceName)).toCompletableFuture().get(); + + requireNonNull(swInterfaceDetails, "swInterfaceDump returned null"); + requireNonNull(swInterfaceDetails.swInterfaceDetails, "swInterfaceDetails is null"); + requireSingleIface(swInterfaceDetails, ifaceName); + + final int swIfIndex = swInterfaceDetails.swInterfaceDetails.get(0).swIfIndex; + final int subId = 1; + + final CreateSubifReply createSubifReply = + jvppFacade.createSubif(createSubifRequest(swIfIndex, subId)).toCompletableFuture().get(); + print(createSubifReply); + + final String subIfaceName = "Gigabitethernet0/8/0." + subId; + final SwInterfaceDetailsReplyDump subIface = + jvppFacade.swInterfaceDump(createSwInterfaceDumpRequest(subIfaceName)).toCompletableFuture().get(); + requireNonNull(swInterfaceDetails, "swInterfaceDump returned null"); + requireNonNull(subIface.swInterfaceDetails, "swInterfaceDump returned null"); + requireSingleIface(swInterfaceDetails, ifaceName); + + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testCreateSubInterface(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java new file mode 100644 index 0000000..030e689 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiExample.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDump; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersion; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetails; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FutureApiExample { + + private static final Logger LOG = Logger.getLogger(FutureApiExample.class.getName()); + + private static void testShowVersion(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending ShowVersion request..."); + final Future<ShowVersionReply> replyFuture = jvpp.showVersion(new ShowVersion()).toCompletableFuture(); + final ShowVersionReply reply = replyFuture.get(); + LOG.info( + String.format( + "Received ShowVersionReply: context=%d, program=%s, version=%s, buildDate=%s, buildDirectory=%s%n", + reply.context, reply.program, + reply.version, + reply.buildDate, + reply.buildDirectory)); + } + + private static void testEmptyBridgeDomainDump(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending ShowVersion request..."); + final BridgeDomainDump request = new BridgeDomainDump(); + request.bdId = -1; // dump call + + final CompletableFuture<BridgeDomainDetailsReplyDump> + replyFuture = jvpp.bridgeDomainDump(request).toCompletableFuture(); + final BridgeDomainDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.bridgeDomainDetails == null) { + LOG.severe("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received bridge-domain dump reply with list of bridge-domains: %s", + reply.bridgeDomainDetails)); + } + } + + private static void testGetNodeIndex(final FutureJVppCoreFacade jvpp) { + LOG.info("Sending GetNodeIndex request..."); + final GetNodeIndex request = new GetNodeIndex(); + request.nodeName = "non-existing-node".getBytes(StandardCharsets.UTF_8); + final Future<GetNodeIndexReply> replyFuture = jvpp.getNodeIndex(request).toCompletableFuture(); + try { + final GetNodeIndexReply reply = replyFuture.get(); + LOG.info( + String.format( + "Received GetNodeIndexReply: context=%d, nodeIndex=%d%n", reply.context, reply.nodeIndex)); + } catch (Exception e) { + LOG.log(Level.SEVERE, "GetNodeIndex request failed", e); + } + } + + private static void testSwInterfaceDump(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending SwInterfaceDump request..."); + final SwInterfaceDump request = new SwInterfaceDump(); + request.nameFilterValid = 0; + request.nameFilter = "".getBytes(StandardCharsets.UTF_8); + + final Future<SwInterfaceDetailsReplyDump> replyFuture = jvpp.swInterfaceDump(request).toCompletableFuture(); + final SwInterfaceDetailsReplyDump reply = replyFuture.get(); + for (SwInterfaceDetails details : reply.swInterfaceDetails) { + Objects.requireNonNull(details, "reply.swInterfaceDetails contains null element!"); + LOG.info( + String.format("Received SwInterfaceDetails: interfaceName=%s, l2AddressLength=%d, adminUpDown=%d, " + + "linkUpDown=%d, linkSpeed=%d, linkMtu=%d%n", + details.interfaceName, + details.l2AddressLength, details.adminUpDown, + details.linkUpDown, details.linkSpeed, (int) details.linkMtu)); + } + } + + private static void testFutureApi() throws Exception { + LOG.info("Testing Java future API"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + LOG.info("Successfully connected to VPP"); + + testEmptyBridgeDomainDump(jvppFacade); + testShowVersion(jvppFacade); + testGetNodeIndex(jvppFacade); + testSwInterfaceDump(jvppFacade); + + LOG.info("Disconnecting..."); + } + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java new file mode 100644 index 0000000..3c84fd7 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiNotificationExample.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getChangeInterfaceState; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getDisableInterfaceNotificationsReq; +import static io.fd.vpp.jvpp.core.examples.NotificationUtils.getEnableInterfaceNotificationsReq; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import io.fd.vpp.jvpp.core.callback.SwInterfaceEventCallback; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; +import io.fd.vpp.jvpp.VppCallbackException; + +public class FutureApiNotificationExample { + + private static void testFutureApi() throws Exception { + System.out.println("Testing Java future API for notifications"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiNotificationExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl()); + final AutoCloseable notificationListenerReg = + jvppFacade.getEventRegistry() + .registerSwInterfaceEventCallback(new SwInterfaceEventCallback() { + public void onSwInterfaceEvent(SwInterfaceEvent reply) { + System.out.printf("Received interface notification: ifc: %s%n", reply); + } + + public void onError (VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", + ex.getMethodName(), ex.getCtxId(), ex.getErrorCode()); + } + })) { + System.out.println("Successfully connected to VPP"); + jvppFacade.wantInterfaceEvents(getEnableInterfaceNotificationsReq()).toCompletableFuture().get(); + System.out.println("Interface events started"); + + System.out.println("Changing interface configuration"); + jvppFacade.swInterfaceSetFlags(getChangeInterfaceState()).toCompletableFuture().get(); + + Thread.sleep(1000); + + jvppFacade.wantInterfaceEvents(getDisableInterfaceNotificationsReq()).toCompletableFuture().get(); + System.out.println("Interface events stopped"); + System.out.println("Disconnecting..."); + } + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiReadPerfTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiReadPerfTest.java new file mode 100644 index 0000000..f335b28 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/FutureApiReadPerfTest.java @@ -0,0 +1,137 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.callback.ShowVersionReplyCallback; +import io.fd.vpp.jvpp.core.dto.*; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; + +import java.util.concurrent.CompletableFuture; +import java.util.logging.Logger; + +public class FutureApiReadPerfTest { + + private static final Logger LOG = Logger.getLogger(FutureApiReadPerfTest.class.getName()); + private static final ShowVersion REQUEST = new ShowVersion(); + private static volatile int currentCount = 0; + private static int desiredCount = 0; + private static long timeAfter = 0; + private static volatile boolean stop = false; + /** + * Run after reply message is received + * in case of running for 1 sec check if time passed (stop variable) and if it does skip processing + * in case of running for set amount of REQUEST, record time in which was last reply received + * not thread save but since reading part process only one message at a time it's ok + */ + private static Runnable replyFc = () -> { + if (stop) { + return; + } + currentCount++; + if(currentCount == desiredCount) { + timeAfter = System.nanoTime(); + } + }; + + /** + * Used to reset counters and flags between runs + */ + private static void reset() { + currentCount = 0; + timeAfter = 0; + stop = false; + } + + public static boolean stop() { + stop = true; + return false; + } + + /** + * + * @return time of last reply received + * @throws Exception during thread sleep + */ + private static long getTime() throws Exception { + while(timeAfter == 0) { + LOG.info(String.format("Received %d replies", currentCount)); + Thread.sleep(1000); + } + return timeAfter; + } + + /** + * + * @param args - for running for one sec requires no parameter + * - for running for set amount of requests requires one parameters, desired REQUEST amount + * @throws Exception if arguments aren't String representations of numbers + */ + public static void main(String[] args) throws Exception { + if (args.length == 1) { + desiredCount = Integer.parseUnsignedInt(args[0]); + testInvokeCounter(true); + } else { + testInvokeCounter(false); + } + } + + /** + * + * @param setCount true = run with set amount of requests, false = run for 1 sec + * @throws Exception + */ + private static void testInvokeCounter(boolean setCount) throws Exception { + LOG.info("Testing callback API Invocation Counter"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiReadPerfTest"); + final FutureJVppCoreFacade jvpp = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + if (!setCount) { + for(int i = 0; i < 5; i++) { + reset(); + LOG.info("Starting invocation for 1sec"); + long time = System.nanoTime(); + do { + CompletableFuture<ShowVersionReply> replyFuture = jvpp.showVersion(REQUEST).toCompletableFuture(); + replyFuture.thenRun(replyFc); + } while (System.nanoTime() - time < 1000000000 || stop()); + LOG.info(String.format("Invocation count within 1 second: %d", currentCount)); + } + } else { + for (int i = 0; i < 5; i++) { + LOG.info("Starting invocations"); + reset(); + long time = System.nanoTime(); + for (int x = 0; x < desiredCount; x++) { + CompletableFuture<ShowVersionReply> replyFuture = jvpp.showVersion(REQUEST).toCompletableFuture(); + replyFuture.thenRun(replyFc); + } + LOG.info("Invocations send"); + long timeAfter = getTime(); + LOG.info(String.format("Invocations took %d ns (%f invocations/s)", timeAfter - time, + desiredCount * (1000000000.0/(timeAfter - time)))); + } + } + + + Thread.sleep(1000); + LOG.info("Disconnecting..."); + } + Thread.sleep(1000); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java new file mode 100644 index 0000000..0be85d4 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/L2AclExample.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelSession; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelSessionReply; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelTable; +import io.fd.vpp.jvpp.core.dto.ClassifyAddDelTableReply; +import io.fd.vpp.jvpp.core.dto.ClassifySessionDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.ClassifySessionDump; +import io.fd.vpp.jvpp.core.dto.ClassifyTableByInterface; +import io.fd.vpp.jvpp.core.dto.ClassifyTableByInterfaceReply; +import io.fd.vpp.jvpp.core.dto.ClassifyTableIds; +import io.fd.vpp.jvpp.core.dto.ClassifyTableIdsReply; +import io.fd.vpp.jvpp.core.dto.ClassifyTableInfo; +import io.fd.vpp.jvpp.core.dto.ClassifyTableInfoReply; +import io.fd.vpp.jvpp.core.dto.InputAclSetInterface; +import io.fd.vpp.jvpp.core.dto.InputAclSetInterfaceReply; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; + +/** + * <p>Tests L2 ACL creation and read.<br> Equivalent to the following vppctl commands:<br> + * + * <pre>{@code + * vppctl classify table mask l2 src + * vppctl classify session acl-hit-next deny opaque-index 0 table-index 0 match l2 src 01:02:03:04:05:06 + * vppctl set int input acl intfc local0 l2-table 0 + * vppctl sh class table verbose + * } + * </pre> + */ +public class L2AclExample { + + private static final int LOCAL0_IFACE_ID = 0; + private static final char[] hexArray = "0123456789ABCDEF".toCharArray(); + + + private static ClassifyAddDelTable createClassifyTable() { + ClassifyAddDelTable request = new ClassifyAddDelTable(); + request.isAdd = 1; + request.tableIndex = ~0; // default + request.nbuckets = 2; + request.memorySize = 2 << 20; + request.nextTableIndex = ~0; // default + request.missNextIndex = ~0; // default + request.skipNVectors = 0; + request.matchNVectors = 1; + request.mask = + new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, + (byte) 0xff, (byte) 0xff, 0x00, 0x00, 0x00, 0x00}; + return request; + } + + private static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for ( int j = 0; j < bytes.length; j++ ) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new java.lang.String(hexChars); + } + + private static ClassifyTableInfo createClassifyTableInfoRequest(final int tableId) { + ClassifyTableInfo request = new ClassifyTableInfo(); + request.tableId = tableId; + return request; + } + + private static ClassifyAddDelSession createClassifySession(final int tableIndex) { + ClassifyAddDelSession request = new ClassifyAddDelSession(); + request.isAdd = 1; + request.tableIndex = tableIndex; + request.hitNextIndex = 0; // deny + request.opaqueIndex = 0; + request.advance = 0; // default + // match 01:02:03:04:05:06 mac address + request.match = + new byte[] {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04, + (byte) 0x05, (byte) 0x06, 0x00, 0x00, 0x00, 0x00}; + return request; + } + + private static ClassifySessionDump createClassifySessionDumpRequest(final int newTableIndex) { + ClassifySessionDump request = new ClassifySessionDump(); + request.tableId = newTableIndex; + return request; + } + + private static InputAclSetInterface aclSetInterface() { + InputAclSetInterface request = new InputAclSetInterface(); + request.isAdd = 1; + request.swIfIndex = LOCAL0_IFACE_ID; + request.ip4TableIndex = ~0; // skip + request.ip6TableIndex = ~0; // skip + request.l2TableIndex = 0; + return request; + } + + private static ClassifyTableByInterface createClassifyTableByInterfaceRequest() { + ClassifyTableByInterface request = new ClassifyTableByInterface(); + request.swIfIndex = LOCAL0_IFACE_ID; + return request; + } + + private static void print(ClassifyAddDelTableReply reply) { + System.out.printf("ClassifyAddDelTableReply: %s%n", reply); + } + + private static void print(ClassifyTableIdsReply reply) { + System.out.printf("ClassifyTableIdsReply: %s%n", reply); + } + + private static void print(final ClassifyTableInfoReply reply) { + System.out.println(reply); + if (reply != null) { + System.out.println("Mask hex: " + bytesToHex(reply.mask)); + } + } + + private static void print(ClassifyAddDelSessionReply reply) { + System.out.printf("ClassifyAddDelSessionReply: context=%s%n", reply); + } + + private static void print(final ClassifySessionDetailsReplyDump reply) { + System.out.println(reply); + reply.classifySessionDetails.forEach(detail -> { + System.out.println(detail); + System.out.println("Match hex: " + bytesToHex(detail.match)); + }); + } + + private static void print(final InputAclSetInterfaceReply reply) { + System.out.printf("InputAclSetInterfaceReply: context=%s%n", reply); + } + + private static void print(final ClassifyTableByInterfaceReply reply) { + System.out.printf("ClassifyAddDelTableReply: %s%n", reply); + } + + private static void testL2Acl() throws Exception { + System.out.println("Testing L2 ACLs using Java callback API"); + try (final JVppRegistry registry = new JVppRegistryImpl("L2AclExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + final ClassifyAddDelTableReply classifyAddDelTableReply = + jvppFacade.classifyAddDelTable(createClassifyTable()).toCompletableFuture().get(); + print(classifyAddDelTableReply); + + final ClassifyTableIdsReply classifyTableIdsReply = + jvppFacade.classifyTableIds(new ClassifyTableIds()).toCompletableFuture().get(); + print(classifyTableIdsReply); + + final ClassifyTableInfoReply classifyTableInfoReply = + jvppFacade.classifyTableInfo(createClassifyTableInfoRequest(classifyAddDelTableReply.newTableIndex)) + .toCompletableFuture().get(); + print(classifyTableInfoReply); + + final ClassifyAddDelSessionReply classifyAddDelSessionReply = + jvppFacade.classifyAddDelSession(createClassifySession(classifyAddDelTableReply.newTableIndex)) + .toCompletableFuture().get(); + print(classifyAddDelSessionReply); + + final ClassifySessionDetailsReplyDump classifySessionDetailsReplyDump = + jvppFacade.classifySessionDump(createClassifySessionDumpRequest(classifyAddDelTableReply.newTableIndex)) + .toCompletableFuture().get(); + print(classifySessionDetailsReplyDump); + + final InputAclSetInterfaceReply inputAclSetInterfaceReply = + jvppFacade.inputAclSetInterface(aclSetInterface()).toCompletableFuture().get(); + print(inputAclSetInterfaceReply); + + final ClassifyTableByInterfaceReply classifyTableByInterfaceReply = + jvppFacade.classifyTableByInterface(createClassifyTableByInterfaceRequest()).toCompletableFuture() + .get(); + print(classifyTableByInterfaceReply); + + System.out.println("Disconnecting..."); + } + Thread.sleep(1000); + } + + public static void main(String[] args) throws Exception { + testL2Acl(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java new file mode 100644 index 0000000..f637669 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/LispAdjacencyExample.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.LispAddDelAdjacency; +import io.fd.vpp.jvpp.core.dto.LispAddDelLocalEid; +import io.fd.vpp.jvpp.core.dto.LispAddDelLocatorSet; +import io.fd.vpp.jvpp.core.dto.LispAddDelRemoteMapping; +import io.fd.vpp.jvpp.core.dto.LispAdjacenciesGet; +import io.fd.vpp.jvpp.core.dto.LispAdjacenciesGetReply; +import io.fd.vpp.jvpp.core.dto.LispEnableDisable; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutionException; +import java.util.logging.Logger; + +/** + * Tests lisp adjacency creation and read (custom vpe.api type support showcase). + */ +public class LispAdjacencyExample { + + private static final Logger LOG = Logger.getLogger(LispAdjacencyExample.class.getName()); + + private static void enableLisp(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispEnableDisable request = new LispEnableDisable(); + request.isEn = 1; + jvpp.lispEnableDisable(request).toCompletableFuture().get(); + LOG.info("Lisp enabled successfully"); + } + + private static void addLocatorSet(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispAddDelLocatorSet request = new LispAddDelLocatorSet(); + request.isAdd = 1; + request.locatorSetName = "ls1".getBytes(StandardCharsets.UTF_8); + jvpp.lispAddDelLocatorSet(request).toCompletableFuture().get(); + LOG.info("Locator set created successfully:" + request.toString()); + } + + private static void addLocalEid(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispAddDelLocalEid request = new LispAddDelLocalEid(); + request.isAdd = 1; + request.locatorSetName = "ls1".getBytes(StandardCharsets.UTF_8); + request.eid = new byte[] {1, 2, 1, 10}; + request.eidType = 0; // ip4 + request.vni = 0; + request.prefixLen = 32; + jvpp.lispAddDelLocalEid(request).toCompletableFuture().get(); + LOG.info("Local EID created successfully:" + request.toString()); + } + + private static void addRemoteMapping(final FutureJVppCoreFacade jvpp) + throws ExecutionException, InterruptedException { + final LispAddDelRemoteMapping request = new LispAddDelRemoteMapping(); + request.isAdd = 1; + request.vni = 0; + request.eid = new byte[] {1, 2, 1, 20}; + request.eidLen = 32; + request.rlocNum = 1; + // FIXME!!!! + //request.rlocs = new byte[] {1, 1, 1, 1, 2, 1, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + jvpp.lispAddDelRemoteMapping(request).toCompletableFuture().get(); + LOG.info("Remote mapping created successfully:" + request.toString()); + } + + private static void addAdjacency(final FutureJVppCoreFacade jvpp) throws ExecutionException, InterruptedException { + final LispAddDelAdjacency request = new LispAddDelAdjacency(); + request.isAdd = 1; + request.leid = new byte[] {1, 2, 1, 10}; + request.leidLen = 32; + request.reid = new byte[] {1, 2, 1, 20}; + request.reidLen = 32; + request.eidType = 0; // ip4 + request.vni = 0; + jvpp.lispAddDelAdjacency(request).toCompletableFuture().get(); + LOG.info("Lisp adjacency created successfully:" + request.toString()); + } + + private static void showAdjacencies(final FutureJVppCoreFacade jvpp) + throws ExecutionException, InterruptedException { + final LispAdjacenciesGetReply reply = + jvpp.lispAdjacenciesGet(new LispAdjacenciesGet()).toCompletableFuture().get(); + LOG.info("Lisp adjacency received successfully:" + reply.toString()); + } + + private static void testAdjacency(final FutureJVppCoreFacade jvpp) throws Exception { + enableLisp(jvpp); + addLocatorSet(jvpp); + addLocalEid(jvpp); + addRemoteMapping(jvpp); + addAdjacency(jvpp); + showAdjacencies(jvpp); + } + + private static void testFutureApi() throws Exception { + LOG.info("Create lisp adjacency test"); + try (final JVppRegistry registry = new JVppRegistryImpl("LispAdjacencyExample"); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + LOG.info("Successfully connected to VPP"); + + testAdjacency(jvppFacade); + LOG.info("Disconnecting..."); + } + } + + public static void main(String[] args) throws Exception { + testFutureApi(); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java new file mode 100644 index 0000000..e963d63 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/NotificationUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.examples; + +import java.io.PrintStream; +import io.fd.vpp.jvpp.core.dto.SwInterfaceSetFlags; +import io.fd.vpp.jvpp.core.dto.SwInterfaceEvent; +import io.fd.vpp.jvpp.core.dto.WantInterfaceEvents; + +final class NotificationUtils { + + private NotificationUtils() {} + + static PrintStream printNotification(final SwInterfaceEvent msg) { + return System.out.printf("Received interface notification: ifc: %s%n", msg); + } + + static SwInterfaceSetFlags getChangeInterfaceState() { + final SwInterfaceSetFlags swInterfaceSetFlags = new SwInterfaceSetFlags(); + swInterfaceSetFlags.swIfIndex = 0; + swInterfaceSetFlags.adminUpDown = 1; + return swInterfaceSetFlags; + } + + static WantInterfaceEvents getEnableInterfaceNotificationsReq() { + WantInterfaceEvents wantInterfaceEvents = new WantInterfaceEvents(); + wantInterfaceEvents.pid = 1; + wantInterfaceEvents.enableDisable = 1; + return wantInterfaceEvents; + } + + static WantInterfaceEvents getDisableInterfaceNotificationsReq() { + WantInterfaceEvents wantInterfaceEvents = new WantInterfaceEvents(); + wantInterfaceEvents.pid = 1; + wantInterfaceEvents.enableDisable = 0; + return wantInterfaceEvents; + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt new file mode 100644 index 0000000..9fe3c7a --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/examples/Readme.txt @@ -0,0 +1,24 @@ +This package contains basic examples for jvpp. To run the examples: + +- Make sure VPP is running +- From VPP's build-root/ folder execute: + - release version: sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] + - debug version: sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] + +Available examples: +CallbackApiExample - Similar to ControlPingTest, invokes more complex calls (e.g. interface dump) using low level JVpp APIs +CallbackJVppFacadeNotificationExample - Example of interface notifications using Callback based JVpp facade +CallbackJVppFacadeExample - Execution of more complex calls using Callback based JVpp facade +CallbackNotificationApiExample - Example of interface notifications using low level JVpp APIs +CreateSubInterfaceExample - Example of sub-interface creation +FutureApiNotificationExample - Example of interface notifications using Future based JVpp facade +FutureApiExample - Execution of more complex calls using Future based JVpp facade +L2AclExample - Example of L2 ACL creation +LispAdjacencyExample - Example of lisp adjacency creation and read (custom vpe.api type support showcase) + +CallbackApiReadPerfTest, FutureApiReadPerfTest, CallbackApiWritePerfTest - test provide two ways to count invocations: +1) maximum number of invocations and received replyies within 1 sec +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] +2) measure time in ns from first request to receiving last reply over set amount of requests +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-18.01.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-18.01.jar io.fd.vpp.jvpp.core.examples.[test name] [number of request to send] + diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java new file mode 100644 index 0000000..493116c --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.core.JVppCoreImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for core plugin"); + testControlPing(args[0], new JVppCoreImpl()); + } +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java b/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java new file mode 100644 index 0000000..d3acecc --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/test/FutureApiTest.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.core.test; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.core.JVppCoreImpl; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.BridgeDomainDump; +import io.fd.vpp.jvpp.core.dto.GetNodeIndex; +import io.fd.vpp.jvpp.core.dto.GetNodeIndexReply; +import io.fd.vpp.jvpp.core.dto.ShowVersion; +import io.fd.vpp.jvpp.core.dto.ShowVersionReply; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetails; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDetailsReplyDump; +import io.fd.vpp.jvpp.core.dto.SwInterfaceDump; +import io.fd.vpp.jvpp.core.future.FutureJVppCoreFacade; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Future; +import java.util.logging.Level; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testFutureApi(args); + } + + private static void testFutureApi(String[] args) throws Exception { + LOG.info("Testing Java future API for core plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppCoreFacade jvppFacade = new FutureJVppCoreFacade(registry, new JVppCoreImpl())) { + LOG.info("Successfully connected to VPP"); + + testEmptyBridgeDomainDump(jvppFacade); + + LOG.info("Disconnecting..."); + } + } + + private static void testEmptyBridgeDomainDump(final FutureJVppCoreFacade jvpp) throws Exception { + LOG.info("Sending BridgeDomainDump request..."); + final BridgeDomainDump request = new BridgeDomainDump(); + request.bdId = -1; // dump call + + final CompletableFuture<BridgeDomainDetailsReplyDump> + replyFuture = jvpp.bridgeDomainDump(request).toCompletableFuture(); + final BridgeDomainDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.bridgeDomainDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received bridge-domain dump reply with list of bridge-domains: %s", + reply.bridgeDomainDetails)); + } + } + + +} diff --git a/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt b/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt new file mode 100644 index 0000000..b74cf60 --- /dev/null +++ b/java/jvpp-core/io/fd/vpp/jvpp/core/test/Readme.txt @@ -0,0 +1,18 @@ +This package contains basic tests for jvpp. To run the tests: + +- Make sure VPP is running +- From VPP's build-root/ folder execute: + - release version: sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-core-17.10.jar io.fd.vpp.jvpp.core.test.[test name] + - debug version: sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-core-17.10.jar io.fd.vpp.jvpp.core.test.[test name] + +Available tests: +CallbackApiTest - Similar to ControlPingTest, invokes more complex calls (e.g. interface dump) using low level JVpp APIs +CallbackJVppFacadeNotificationTest - Tests interface notifications using Callback based JVpp facade +CallbackJVppFacadeTest - Execution of more complex calls using Callback based JVpp facade +CallbackNotificationApiTest - Tests interface notifications using low level JVpp APIs +ControlPingTest - Simple test executing a single control ping using low level JVpp APIs +CreateSubInterfaceTest - Tests sub-interface creation +FutureApiNotificationTest - Tests interface notifications using Future based JVpp facade +FutureApiTest - Execution of more complex calls using Future based JVpp facade +L2AclTest - Tests L2 ACL creation +LispAdjacencyTest - Tests lisp adjacency creation and read (custom vpe.api type support showcase) diff --git a/java/jvpp-core/jvpp_core.c b/java/jvpp-core/jvpp_core.c new file mode 100644 index 0000000..2e62426 --- /dev/null +++ b/java/jvpp-core/jvpp_core.c @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> +#include <jvpp-common/jvpp_common.h> +#include <vpp/api/vpe_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> +#include <jvpp_core.h> + + +// TODO: generate jvpp_plugin_name.c files (or at least reuse plugin's main structure) +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} core_main_t; + +core_main_t core_main __attribute__((aligned (64))); + +#include "io_fd_vpp_jvpp_core_JVppCoreImpl.h" +#include "jvpp_core_gen.h" + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_core_JVppCoreImpl_init0 +(JNIEnv * env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + core_main_t * plugin_main = &core_main; + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation (exit on mismatch) + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_core_JVppCoreImpl_close0 +(JNIEnv *env, jclass clazz) { + core_main_t * plugin_main = &core_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} + + +static void _host_to_net_string(JNIEnv * env, jstring javaString, vl_api_string_t * vl_api_string) +{ + const char *nativeString; + // prevent null, which causes jni to crash + if (NULL != javaString) { + nativeString = (*env)->GetStringUTFChars(env, javaString, 0); + } else{ + nativeString = ""; + } + + vl_api_to_api_string(jstr_length(env, javaString) + 1, nativeString, vl_api_string); + + (*env)->ReleaseStringUTFChars(env, javaString, nativeString); +} + + +static jstring _net_to_host_string(JNIEnv * env, const vl_api_string_t * _net) +{ + return (*env)->NewStringUTF(env, (char *)_net->buf); +} + + +static size_t jstr_length(JNIEnv *env, jstring string) +{ + return ((int) (*env)->GetStringUTFLength(env, string)); +} diff --git a/java/jvpp-core/jvpp_core.h b/java/jvpp-core/jvpp_core.h new file mode 100644 index 0000000..032dd33 --- /dev/null +++ b/java/jvpp-core/jvpp_core.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2018 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_JVPP_CORE_H +#define VPP_JVPP_CORE_H + +#include <vlibapi/api_types.h> + +#endif //VPP_JVPP_CORE_H + +// /** +// * Host to network byte order conversion for string type. Converts String in Java to VPP string type. +// * typedef struct +// * { +// * u32 length; +// * u8 buf[0]; +// * } __attribute__ ((packed)) vl_api_string_t; +// */ +static void _host_to_net_string(JNIEnv * env, jstring javaString, vl_api_string_t * vl_api_string); + + +// +// /** +// * Network to host byte order conversion for string type. Converts VPP string type to String in Java +// * typedef struct +// * { +// * u32 length; +// * u8 buf[0]; +// * } __attribute__ ((packed)) vl_api_string_t; +// */ +static jstring _net_to_host_string(JNIEnv * env, const vl_api_string_t * _net); + + +// +// /** +// * Returns the length of jstring as size_t +// */ +static size_t jstr_length(JNIEnv *env, jstring string); diff --git a/java/jvpp-gtpu/jvpp_gtpu.c b/java/jvpp-gtpu/jvpp_gtpu.c new file mode 100644 index 0000000..8e523ca --- /dev/null +++ b/java/jvpp-gtpu/jvpp_gtpu.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <gtpu/gtpu_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <gtpu/gtpu_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-gtpu/io_fd_vpp_jvpp_gtpu_JVppGtpuImpl.h" +#include "jvpp_gtpu.h" +#include "jvpp-gtpu/jvpp_gtpu_gen.h" + +/* + * Class: io_fd_vpp_jvpp_gtpu_JVppgtpuImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_gtpu_JVppGtpuImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + gtpu_main_t * plugin_main = >pu_main; + clib_warning ("Java_io_fd_vpp_jvpp_gtpu_JVppGtpuImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (svm_queue_t *)queue_address; + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_gtpu_JVppGtpuImpl_close0 +(JNIEnv *env, jclass clazz) { + gtpu_main_t * plugin_main = >pu_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-gtpu/jvpp_gtpu.h b/java/jvpp-gtpu/jvpp_gtpu.h new file mode 100644 index 0000000..fa7c8b7 --- /dev/null +++ b/java/jvpp-gtpu/jvpp_gtpu.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_gtpu_h__ +#define __included_jvpp_gtpu_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-gtpu */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} gtpu_main_t; + +gtpu_main_t gtpu_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_gtpu_h__ */ diff --git a/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java new file mode 100644 index 0000000..2f5b7db --- /dev/null +++ b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/IoamExportApiExample.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamexport.examples; + +import java.net.InetAddress; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.ioamexport.JVppIoamexportImpl; +import io.fd.vpp.jvpp.ioamexport.future.FutureJVppIoamexportFacade; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisable; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisableReply; + +public class IoamExportApiExample { + + public static void main(String[] args) throws Exception { + ioamExportTestApi(); + } + + private static void ioamExportTestApi() throws Exception { + System.out.println("Testing Java API for ioam export plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("ioamExportApiExample"); + final JVpp jvpp = new JVppIoamexportImpl()) { + FutureJVppIoamexportFacade ioamexportJvpp = new FutureJVppIoamexportFacade(registry,jvpp); + System.out.println("Sending ioam export request..."); + IoamExportIp6EnableDisable request = new IoamExportIp6EnableDisable(); + request.isDisable = 0; + InetAddress collectorAddress = InetAddress.getByName("2001:0DB8:AC10:FE01:0000:0000:0000:0000"); + InetAddress srcAddress = InetAddress.getByName("2001:0DB8:AC10:FE01:0000:0000:0000:0001"); + request.collectorAddress = collectorAddress.getAddress(); + request.srcAddress = srcAddress.getAddress(); + IoamExportIp6EnableDisableReply reply = ioamexportJvpp.ioamExportIp6EnableDisable(request).toCompletableFuture().get(); + System.out.printf("IoamExportIp6EnableDisableReply = "+reply.toString()+"%n"); + + Thread.sleep(1000); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt new file mode 100644 index 0000000..f2dfe91 --- /dev/null +++ b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.examples.IoamExportApiExample +debug vresion: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.examples.IoamExportApiExample diff --git a/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java new file mode 100644 index 0000000..ba49d77 --- /dev/null +++ b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamexport.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.ioamexport.JVppIoamexportImpl; + +import java.util.logging.Logger; + + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for ioamexport plugin"); + testControlPing(args[0], new JVppIoamexportImpl()); + } +} diff --git a/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java new file mode 100644 index 0000000..048d244 --- /dev/null +++ b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/FutureApiTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamexport.test; + + +import io.fd.vpp.jvpp.Assertions; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.ioamexport.JVppIoamexportImpl; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisable; +import io.fd.vpp.jvpp.ioamexport.dto.IoamExportIp6EnableDisableReply; +import io.fd.vpp.jvpp.ioamexport.future.FutureJVppIoamexportFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for ioamexport plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppIoamexportFacade jvpp = new FutureJVppIoamexportFacade(registry, new JVppIoamexportImpl())) { + LOG.info("Successfully connected to VPP"); + + testIoamExportIp6EnableDisable(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testIoamExportIp6EnableDisable(FutureJVppIoamexportFacade jvpp) throws Exception { + LOG.info("Sending IoamExportIp6EnableDisable request..."); + final IoamExportIp6EnableDisable request = new IoamExportIp6EnableDisable(); + + final Future<IoamExportIp6EnableDisableReply> replyFuture = jvpp.ioamExportIp6EnableDisable(request).toCompletableFuture(); + final IoamExportIp6EnableDisableReply reply = replyFuture.get(); + + Assertions.assertNotNull(reply); + } +} diff --git a/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt new file mode 100644 index 0000000..820071a --- /dev/null +++ b/java/jvpp-ioamexport/io/fd/vpp/jvpp/ioamexport/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamexport-17.10.jar io.fd.vpp.jvpp.ioamexport.test.[test-name] diff --git a/java/jvpp-ioamexport/jvpp_ioam_export.c b/java/jvpp-ioamexport/jvpp_ioam_export.c new file mode 100644 index 0000000..74ae438 --- /dev/null +++ b/java/jvpp-ioamexport/jvpp_ioam_export.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <ioam/export/ioam_export_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <ioam/export/ioam_export_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-ioamexport/io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl.h" +#include "jvpp_ioam_export.h" +#include "jvpp-ioamexport/jvpp_ioamexport_gen.h" + +/* + * Class: io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + ioamexport_main_t * plugin_main = &ioamexport_main; + clib_warning ("Java_io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamexport_JVppIoamexportImpl_close0 +(JNIEnv *env, jclass clazz) { + ioamexport_main_t * plugin_main = &ioamexport_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP iOAM EXPORT */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP iOAM EXPORT */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-ioamexport/jvpp_ioam_export.h b/java/jvpp-ioamexport/jvpp_ioam_export.h new file mode 100644 index 0000000..596a054 --- /dev/null +++ b/java/jvpp-ioamexport/jvpp_ioam_export.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_ioam_export_h__ +#define __included_jvpp_ioam_export_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-IOAM-EXPORT */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} ioamexport_main_t; + +ioamexport_main_t ioamexport_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_ioam_export_h__ */ diff --git a/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java new file mode 100644 index 0000000..b9ed7d0 --- /dev/null +++ b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/IoamPotApiExample.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioampot.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.ioampot.JVppIoampotImpl; +import io.fd.vpp.jvpp.ioampot.callback.PotProfileAddReplyCallback; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileAdd; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileAddReply; +import java.nio.charset.StandardCharsets; + +public class IoamPotApiExample { + + static class IoamPotTestCallback implements PotProfileAddReplyCallback { + + @Override + public void onPotProfileAddReply(final PotProfileAddReply reply) { + System.out.printf("Received PotProfileAddReply reply: context=%d%n", + reply.context); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } + + public static void main(String[] args) throws Exception { + ioamPotTestApi(); + } + + private static void ioamPotTestApi() throws Exception { + System.out.println("Testing Java API for ioam pot plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("ioamPotApiExample"); + final JVpp jvpp = new JVppIoampotImpl()) { + registry.register(jvpp, new IoamPotTestCallback()); + + System.out.println("Sending ioam pot profile add request..."); + PotProfileAdd request = new PotProfileAdd(); + request.id = 0; + request.validator = 4; + request.secretKey = 1; + request.secretShare = 2; + request.prime = 1234; + request.maxBits = 53; + request.lpc = 1234; + request.polynomialPublic = 1234; + request.listNameLen = (byte)"test pot profile".getBytes(StandardCharsets.UTF_8).length; + request.listName = "test pot profile".getBytes(StandardCharsets.UTF_8); + final int result = jvpp.send(request); + System.out.printf("PotProfileAdd send result = %d%n", result); + + Thread.sleep(1000); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt new file mode 100644 index 0000000..e91550b --- /dev/null +++ b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.examples.IoamPotApiExample +debug vresion: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.examples.IoamPotApiExample diff --git a/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java new file mode 100644 index 0000000..20b85d8 --- /dev/null +++ b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioampot.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.ioampot.JVppIoampotImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for ioampot plugin"); + testControlPing(args[0], new JVppIoampotImpl()); + } +} diff --git a/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java new file mode 100644 index 0000000..6401c67 --- /dev/null +++ b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/FutureApiTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioampot.test; + + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.ioampot.JVppIoampotImpl; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileShowConfigDetailsReplyDump; +import io.fd.vpp.jvpp.ioampot.dto.PotProfileShowConfigDump; +import io.fd.vpp.jvpp.ioampot.future.FutureJVppIoampotFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(io.fd.vpp.jvpp.ioampot.test.FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for ioampot plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppIoampotFacade jvpp = new FutureJVppIoampotFacade(registry, new JVppIoampotImpl())) { + LOG.info("Successfully connected to VPP"); + + testPotProfileShowConfigDump(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testPotProfileShowConfigDump(FutureJVppIoampotFacade jvpp) throws Exception { + LOG.info("Sending PotProfileShowConfigDump request..."); + final PotProfileShowConfigDump request = new PotProfileShowConfigDump(); + + final Future<PotProfileShowConfigDetailsReplyDump> replyFuture = jvpp.potProfileShowConfigDump(request).toCompletableFuture(); + final PotProfileShowConfigDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.potProfileShowConfigDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received pot profile show config dump reply: %s", + reply.potProfileShowConfigDetails)); + } + } +} diff --git a/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt new file mode 100644 index 0000000..f3cae26 --- /dev/null +++ b/java/jvpp-ioampot/io/fd/vpp/jvpp/ioampot/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioampot-17.10.jar io.fd.vpp.jvpp.ioampot.test.[test-name] diff --git a/java/jvpp-ioampot/jvpp_ioam_pot.c b/java/jvpp-ioampot/jvpp_ioam_pot.c new file mode 100644 index 0000000..ce1da69 --- /dev/null +++ b/java/jvpp-ioampot/jvpp_ioam_pot.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <ioam/lib-pot/pot_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <ioam/lib-pot/pot_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-ioampot/io_fd_vpp_jvpp_ioampot_JVppIoampotImpl.h" +#include "jvpp_ioam_pot.h" +#include "jvpp-ioampot/jvpp_ioampot_gen.h" + +/* + * Class: io_fd_vpp_jvpp_ioampot_JVppIoampotImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioampot_JVppIoampotImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + ioampot_main_t * plugin_main = &ioampot_main; + clib_warning ("Java_io_fd_vpp_jvpp_ioampot_JVppIoampotImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioampot_JVppIoampotImpl_close0 +(JNIEnv *env, jclass clazz) { + ioampot_main_t * plugin_main = &ioampot_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP iOAM POT */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP iOAM POT */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-ioampot/jvpp_ioam_pot.h b/java/jvpp-ioampot/jvpp_ioam_pot.h new file mode 100644 index 0000000..51db3da --- /dev/null +++ b/java/jvpp-ioampot/jvpp_ioam_pot.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_ioam_pot_h__ +#define __included_jvpp_ioam_pot_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-IOAM-POT */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} ioampot_main_t; + +ioampot_main_t ioampot_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_ioam_pot_h__ */ diff --git a/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java new file mode 100644 index 0000000..d63d137 --- /dev/null +++ b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/IoamTraceApiExample.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamtrace.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.ioamtrace.future.FutureJVppIoamtraceFacade; +import io.fd.vpp.jvpp.ioamtrace.JVppIoamtraceImpl; +import io.fd.vpp.jvpp.ioamtrace.callback.TraceProfileAddReplyCallback; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileAdd; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileAddReply; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfig; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfigReply; + +public class IoamTraceApiExample { + + static class IoamTraceTestCallback implements TraceProfileAddReplyCallback { + + @Override + public void onTraceProfileAddReply(final TraceProfileAddReply reply) { + System.out.printf("Received TraceProfileAddReply reply: context=%d%n", + reply.context); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } + + public static void main(String[] args) throws Exception { + ioamTraceTestApi(); + } + + private static void ioamTraceTestApi() throws Exception { + System.out.println("Testing Java API for ioam trace plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("ioamTraceApiTest"); + final JVpp jvpp = new JVppIoamtraceImpl()) { + FutureJVppIoamtraceFacade ioamtraceJvpp = new FutureJVppIoamtraceFacade(registry,jvpp); + + System.out.println("Sending ioam trace profile add request..."); + TraceProfileAdd request = new TraceProfileAdd(); + request.traceType = 0x1f; + request.numElts = 4; + request.nodeId = 1; + request.traceTsp = 2; + request.appData = 1234; + final int result = jvpp.send(request); + System.out.printf("TraceProfileAdd send result = %d%n", result); + + Thread.sleep(1000); + + TraceProfileShowConfig showRequest = new TraceProfileShowConfig(); + TraceProfileShowConfigReply reply = ioamtraceJvpp.traceProfileShowConfig(showRequest).toCompletableFuture().get(); + System.out.printf("TraceProfileShowConfig result = "+ reply.toString()); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt new file mode 100644 index 0000000..e8c3907 --- /dev/null +++ b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/examples/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.examples.IoamTraceApiExample +debug vresion: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.examples.IoamTraceApiExample diff --git a/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java new file mode 100644 index 0000000..4a71db5 --- /dev/null +++ b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamtrace.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.ioamtrace.JVppIoamtraceImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for ioamtrace plugin"); + testControlPing(args[0], new JVppIoamtraceImpl()); + } +} diff --git a/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java new file mode 100644 index 0000000..4e13ed1 --- /dev/null +++ b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/FutureApiTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.ioamtrace.test; + + +import io.fd.vpp.jvpp.Assertions; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.ioamtrace.JVppIoamtraceImpl; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfig; +import io.fd.vpp.jvpp.ioamtrace.dto.TraceProfileShowConfigReply; +import io.fd.vpp.jvpp.ioamtrace.future.FutureJVppIoamtraceFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(io.fd.vpp.jvpp.ioamtrace.test.FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for ioamtrace plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppIoamtraceFacade jvpp = new FutureJVppIoamtraceFacade(registry, new JVppIoamtraceImpl())) { + LOG.info("Successfully connected to VPP"); + + testTraceProfileShowConfig(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testTraceProfileShowConfig(FutureJVppIoamtraceFacade jvpp) throws Exception { + LOG.info("Sending TraceProfileShowConfig request..."); + final TraceProfileShowConfig request = new TraceProfileShowConfig(); + + final Future<TraceProfileShowConfigReply> replyFuture = jvpp.traceProfileShowConfig(request).toCompletableFuture(); + final TraceProfileShowConfigReply reply = replyFuture.get(); + + Assertions.assertNotNull(reply); + } +} diff --git a/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt new file mode 100644 index 0000000..9a1ba82 --- /dev/null +++ b/java/jvpp-ioamtrace/io/fd/vpp/jvpp/ioamtrace/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-ioamtrace-17.10.jar io.fd.vpp.jvpp.ioamtrace.test.[test-name] diff --git a/java/jvpp-ioamtrace/jvpp_ioam_trace.c b/java/jvpp-ioamtrace/jvpp_ioam_trace.c new file mode 100644 index 0000000..3532d91 --- /dev/null +++ b/java/jvpp-ioamtrace/jvpp_ioam_trace.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <ioam/lib-trace/trace_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <ioam/lib-trace/trace_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-ioamtrace/io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl.h" +#include "jvpp_ioam_trace.h" +#include "jvpp-ioamtrace/jvpp_ioamtrace_gen.h" + +/* + * Class: io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + ioamtrace_main_t * plugin_main = &ioamtrace_main; + clib_warning ("Java_io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_ioamtrace_JVppIoamtraceImpl_close0 +(JNIEnv *env, jclass clazz) { + ioamtrace_main_t * plugin_main = &ioamtrace_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP iOAM Trace */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP iOAM Trace */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-ioamtrace/jvpp_ioam_trace.h b/java/jvpp-ioamtrace/jvpp_ioam_trace.h new file mode 100644 index 0000000..752b599 --- /dev/null +++ b/java/jvpp-ioamtrace/jvpp_ioam_trace.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_ioam_trace_h__ +#define __included_jvpp_ioam_trace_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-IOAM-TRACE */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} ioamtrace_main_t; + +ioamtrace_main_t ioamtrace_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_ioam_trace_h__ */ diff --git a/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java b/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java new file mode 100644 index 0000000..e0d93ff --- /dev/null +++ b/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/CallbackApiExample.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.nat.examples; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.VppCallbackException; +import io.fd.vpp.jvpp.nat.JVppNatImpl; +import io.fd.vpp.jvpp.nat.callback.Nat44InterfaceAddDelFeatureReplyCallback; +import io.fd.vpp.jvpp.nat.dto.Nat44InterfaceAddDelFeature; +import io.fd.vpp.jvpp.nat.dto.Nat44InterfaceAddDelFeatureReply; + +public class CallbackApiExample { + + static class TestCallback implements Nat44InterfaceAddDelFeatureReplyCallback { + + @Override + public void onNat44InterfaceAddDelFeatureReply(final Nat44InterfaceAddDelFeatureReply msg) { + System.out.printf("Received Nat44InterfaceAddDelFeatureReply: context=%d%n", + msg.context); + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()); + } + } + + public static void main(String[] args) throws Exception { + testCallbackApi(); + } + + private static void testCallbackApi() throws Exception { + System.out.println("Testing Java callback API for nat plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("NatCallbackApiTest"); + final JVpp jvpp = new JVppNatImpl()) { + registry.register(jvpp, new TestCallback()); + + System.out.println("Sending Nat44InterfaceAddDelFeature request..."); + Nat44InterfaceAddDelFeature request = new Nat44InterfaceAddDelFeature(); + request.isAdd = 1; + request.isInside = 1; + request.swIfIndex = 1; + final int result = jvpp.send(request); + System.out.printf("Nat44InterfaceAddDelFeature send result = %d%n", result); + + Thread.sleep(1000); + + System.out.println("Disconnecting..."); + } + } +} diff --git a/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt b/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt new file mode 100644 index 0000000..ac75e04 --- /dev/null +++ b/java/jvpp-nat/io/fd/vpp/jvpp/nat/examples/Readme.txt @@ -0,0 +1 @@ +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp-native/vpp/vpp-api/java/jvpp-nat-17.10.jar io.fd.vpp.jvpp.nat.examples.CallbackApiExample diff --git a/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java new file mode 100644 index 0000000..a6f8214 --- /dev/null +++ b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/CallbackApiTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.nat.test; + +import io.fd.vpp.jvpp.AbstractCallbackApiTest; +import io.fd.vpp.jvpp.nat.JVppNatImpl; + +import java.util.logging.Logger; + +public class CallbackApiTest extends AbstractCallbackApiTest { + + private static Logger LOG = Logger.getLogger(CallbackApiTest.class.getName()); + + + public static void main(String[] args) throws Exception { + LOG.info("Testing ControlPing using Java callback API for core plugin"); + testControlPing(args[0], new JVppNatImpl()); + } +} diff --git a/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java new file mode 100644 index 0000000..26d6b98 --- /dev/null +++ b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/FutureApiTest.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.nat.test; + + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; +import io.fd.vpp.jvpp.nat.JVppNatImpl; +import io.fd.vpp.jvpp.nat.dto.Nat44AddressDetailsReplyDump; +import io.fd.vpp.jvpp.nat.dto.Nat44AddressDump; +import io.fd.vpp.jvpp.nat.future.FutureJVppNatFacade; + +import java.util.concurrent.Future; +import java.util.logging.Logger; + +public class FutureApiTest { + + private static final Logger LOG = Logger.getLogger(io.fd.vpp.jvpp.nat.test.FutureApiTest.class.getName()); + + public static void main(String[] args) throws Exception { + testCallbackApi(args); + } + + private static void testCallbackApi(String[] args) throws Exception { + LOG.info("Testing Java callback API for nat plugin"); + try (final JVppRegistry registry = new JVppRegistryImpl("FutureApiTest", args[0]); + final FutureJVppNatFacade jvpp = new FutureJVppNatFacade(registry, new JVppNatImpl())) { + LOG.info("Successfully connected to VPP"); + + testAclDump(jvpp); + + LOG.info("Disconnecting..."); + } + } + + private static void testAclDump(FutureJVppNatFacade jvpp) throws Exception { + LOG.info("Sending Nat44AddressDump request..."); + final Nat44AddressDump request = new Nat44AddressDump(); + + final Future<Nat44AddressDetailsReplyDump> replyFuture = jvpp.nat44AddressDump(request).toCompletableFuture(); + final Nat44AddressDetailsReplyDump reply = replyFuture.get(); + + if (reply == null || reply.nat44AddressDetails == null) { + throw new IllegalStateException("Received null response for empty dump: " + reply); + } else { + LOG.info( + String.format( + "Received nat address dump reply with list of nat address: %s", + reply.nat44AddressDetails)); + } + } +} diff --git a/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt new file mode 100644 index 0000000..6f75808 --- /dev/null +++ b/java/jvpp-nat/io/fd/vpp/jvpp/nat/test/Readme.txt @@ -0,0 +1,4 @@ +release version: +sudo java -cp build-vpp-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-nat-17.10.jar io.fd.vpp.jvpp.nat.test.[test-name] +debug version: +sudo java -cp build-vpp_debug-native/vpp/vpp-api/java/jvpp-registry-17.10.jar:build-vpp_debug-native/vpp/vpp-api/java/jvpp-nat-17.10.jar io.fd.vpp.jvpp.nat.test.[test-name] diff --git a/java/jvpp-nat/jvpp_nat.c b/java/jvpp-nat/jvpp_nat.c new file mode 100644 index 0000000..bdae1e6 --- /dev/null +++ b/java/jvpp-nat/jvpp_nat.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <nat/nat_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <nat/nat_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-nat/io_fd_vpp_jvpp_nat_JVppNatImpl.h" +#include "jvpp_nat.h" +#include "jvpp-nat/jvpp_nat_gen.h" + +/* + * Class: io_fd_vpp_jvpp_nat_JVppNatImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nat_JVppNatImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + nat_main_t * plugin_main = &nat_main; + clib_warning ("Java_io_fd_vpp_jvpp_nat_JVppNatImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = uword_to_pointer (queue_address, svm_queue_t *); + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nat_JVppNatImpl_close0 +(JNIEnv *env, jclass clazz) { + nat_main_t * plugin_main = &nat_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP SNAT */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP SNAT */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-nat/jvpp_nat.h b/java/jvpp-nat/jvpp_nat.h new file mode 100644 index 0000000..9bff974 --- /dev/null +++ b/java/jvpp-nat/jvpp_nat.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_nat_h__ +#define __included_jvpp_nat_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-NAT */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} nat_main_t; + +nat_main_t nat_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_nat_h__ */ diff --git a/java/jvpp-nsh/jvpp_nsh.c b/java/jvpp-nsh/jvpp_nsh.c new file mode 100644 index 0000000..1736ce7 --- /dev/null +++ b/java/jvpp-nsh/jvpp_nsh.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#define vl_typedefs /* define message structures */ +#include <nsh/nsh.api.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-nsh/io_fd_vpp_jvpp_nsh_JVppNshImpl.h" +#include "jvpp_nsh.h" +#include "jvpp-nsh/jvpp_nsh_gen.h" + +/* + * Class: io_fd_vpp_jvpp_nsh_JVppnshImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nsh_JVppNshImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + nsh_main_t * plugin_main = &nsh_main; + clib_warning ("Java_io_fd_vpp_jvpp_nsh_JVppNshImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (svm_queue_t *)queue_address; + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_nsh_JVppNshImpl_close0 +(JNIEnv *env, jclass clazz) { + nsh_main_t * plugin_main = &nsh_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-nsh/jvpp_nsh.h b/java/jvpp-nsh/jvpp_nsh.h new file mode 100644 index 0000000..5f62af6 --- /dev/null +++ b/java/jvpp-nsh/jvpp_nsh.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_nsh_h__ +#define __included_jvpp_nsh_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-nsh */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} nsh_main_t; + +nsh_main_t nsh_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_nsh_h__ */ diff --git a/java/jvpp-pppoe/jvpp_pppoe.c b/java/jvpp-pppoe/jvpp_pppoe.c new file mode 100644 index 0000000..02bd20e --- /dev/null +++ b/java/jvpp-pppoe/jvpp_pppoe.c @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2016 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 <vnet/vnet.h> + +#include <pppoe/pppoe_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <pppoe/pppoe_all_api_h.h> +#undef vl_typedefs + +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> + +#if VPPJNI_DEBUG == 1 + #define DEBUG_LOG(...) clib_warning(__VA_ARGS__) +#else + #define DEBUG_LOG(...) +#endif + +#include <jvpp-common/jvpp_common.h> + +#include "jvpp-pppoe/io_fd_vpp_jvpp_pppoe_JVppPppoeImpl.h" +#include "jvpp_pppoe.h" +#include "jvpp-pppoe/jvpp_pppoe_gen.h" + +/* + * Class: io_fd_vpp_jvpp_pppoe_JVpppppoeImpl + * Method: init0 + * Signature: (JI)V + */ +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_pppoe_JVppPppoeImpl_init0 + (JNIEnv *env, jclass clazz, jobject callback, jlong queue_address, jint my_client_index) { + pppoe_main_t * plugin_main = &pppoe_main; + clib_warning ("Java_io_fd_vpp_jvpp_pppoe_JVppPppoeImpl_init0"); + + plugin_main->my_client_index = my_client_index; + plugin_main->vl_input_queue = (svm_queue_t *)queue_address; + + plugin_main->callbackObject = (*env)->NewGlobalRef(env, callback); + plugin_main->callbackClass = (jclass)(*env)->NewGlobalRef(env, (*env)->GetObjectClass(env, callback)); + + // verify API has not changed since jar generation + #define _(N) \ + if (get_message_id(env, #N) == 0) return; + foreach_supported_api_message; + #undef _ + + #define _(N,n) \ + vl_msg_api_set_handlers(get_message_id(env, #N), #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + vl_noop_handler, \ + sizeof(vl_api_##n##_t), 1); + foreach_api_reply_handler; + #undef _ +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_pppoe_JVppPppoeImpl_close0 +(JNIEnv *env, jclass clazz) { + pppoe_main_t * plugin_main = &pppoe_main; + + // cleanup: + (*env)->DeleteGlobalRef(env, plugin_main->callbackClass); + (*env)->DeleteGlobalRef(env, plugin_main->callbackObject); + + plugin_main->callbackClass = NULL; + plugin_main->callbackObject = NULL; +} + +/* Attach thread to JVM and cache class references when initiating JVPP ACL */ +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + if (cache_class_references(env) != 0) { + clib_warning ("Failed to cache class references\n"); + return JNI_ERR; + } + + return JNI_VERSION_1_8; +} + +/* Clean up cached references when disposing JVPP ACL */ +void JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + delete_class_references(env); +} diff --git a/java/jvpp-pppoe/jvpp_pppoe.h b/java/jvpp-pppoe/jvpp_pppoe.h new file mode 100644 index 0000000..7606a7e --- /dev/null +++ b/java/jvpp-pppoe/jvpp_pppoe.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2016 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_jvpp_pppoe_h__ +#define __included_jvpp_pppoe_h__ + +#include <vnet/vnet.h> +#include <vnet/ip/ip.h> +#include <vnet/api_errno.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <jni.h> + +/* Global state for JVPP-pppoe */ +typedef struct { + /* Pointer to shared memory queue */ + svm_queue_t * vl_input_queue; + + /* VPP api client index */ + u32 my_client_index; + + /* Callback object and class references enabling asynchronous Java calls */ + jobject callbackObject; + jclass callbackClass; + +} pppoe_main_t; + +pppoe_main_t pppoe_main __attribute__((aligned (64))); + + +#endif /* __included_jvpp_pppoe_h__ */ diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java b/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java new file mode 100644 index 0000000..d221d1e --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/AbstractCallbackApiTest.java @@ -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. + */ + +package io.fd.vpp.jvpp; + +import io.fd.vpp.jvpp.callback.ControlPingCallback; +import io.fd.vpp.jvpp.dto.ControlPing; +import io.fd.vpp.jvpp.dto.ControlPingReply; + +public abstract class AbstractCallbackApiTest { + + private static int receivedPingCount = 0; + private static int errorPingCount = 0; + + public static void testControlPing(String shm_prefix, JVpp jvpp) throws Exception { + try (JVppRegistry registry = new JVppRegistryImpl("CallbackApiTest", shm_prefix)) { + + registry.register(jvpp, new ControlPingCallback() { + @Override + public void onControlPingReply(final ControlPingReply reply) { + System.out.printf("Received ControlPingReply: %s%n", reply); + receivedPingCount++; + } + + @Override + public void onError(VppCallbackException ex) { + System.out.printf("Received onError exception: call=%s, reply=%d, context=%d ", ex.getMethodName(), + ex.getErrorCode(), ex.getCtxId()); + errorPingCount++; + } + + }); + System.out.println("Successfully connected to VPP"); + Thread.sleep(1000); + + System.out.println("Sending control ping using JVppRegistry"); + registry.controlPing(jvpp.getClass()); + + Thread.sleep(2000); + + System.out.println("Sending control ping using JVpp plugin"); + jvpp.send(new ControlPing()); + + Thread.sleep(2000); + System.out.println("Disconnecting..."); + Assertions.assertEquals(2, receivedPingCount); + Assertions.assertEquals(0, errorPingCount); + } + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java b/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java new file mode 100644 index 0000000..f8b591f --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/Assertions.java @@ -0,0 +1,32 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp; + +public class Assertions { + + public static void assertEquals(final int expected, final int actual) { + if (expected != actual) { + throw new IllegalArgumentException(String.format("Expected[%s]/Actual[%s]", expected, actual)); + } + } + + public static void assertNotNull(final Object value) { + if (value == null) { + throw new IllegalArgumentException("Variable is null"); + } + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java b/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java new file mode 100644 index 0000000..55f25a7 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/JVpp.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.dto.ControlPing; +import io.fd.vpp.jvpp.dto.JVppRequest; + +/** + * Base interface for plugin's Java API. + */ +public interface JVpp extends AutoCloseable { + + /** + * Sends request to vpp. + * + * @param request request to be sent + * @return unique identifer of message in message queue + * @throws VppInvocationException when message could not be sent + */ + int send(final JVppRequest request) throws VppInvocationException; + + /** + * Initializes plugin's Java API. + * + * @param registry plugin registry + * @param callback called by vpe.api message handlers + * @param queueAddress address of vpp shared memory queue + * @param clientIndex vpp client identifier + */ + void init(final JVppRegistry registry, final JVppCallback callback, final long queueAddress, + final int clientIndex); + + /** + * Sends control_ping message. + * + * @param controlPing request DTO + * @return unique identifer of message in message queue + * @throws VppInvocationException when message could not be sent + */ + int controlPing(final ControlPing controlPing) throws VppInvocationException; +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java new file mode 100644 index 0000000..6535db0 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistry.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import io.fd.vpp.jvpp.callback.JVppCallback; + +/** + * Manages VPP connection and stores plugin callbacks. + */ +public interface JVppRegistry extends AutoCloseable { + + /** + * Vpp connection managed by the registry. + * + * @return representation of vpp connection + */ + VppConnection getConnection(); + + /** + * Registers callback and initializes Java API for given plugin. + * + * @param jvpp plugin name + * @param callback callback provided by the plugin + * @throws NullPointerException if name or callback is null + * @throws IllegalArgumentException if plugin was already registered + */ + void register(final JVpp jvpp, final JVppCallback callback); + + /** + * Unregisters callback for the given plugin. + * + * @param name plugin name + * @throws NullPointerException if name is null + * @throws IllegalArgumentException if plugin was not registered + */ + void unregister(final String name); + + /** + * Returns callback registered for the plugin. + * + * @param name plugin name + * @return callback provided by the plugin + * @throws NullPointerException if name is null + * @throws IllegalArgumentException if plugin was not registered + */ + JVppCallback get(final String name); + + /** + * Sends control ping. Reply handler calls callback registered for give plugin. + * + * Control ping is used for initial RX thread to Java thread attachment + * that takes place in the plugin's JNI lib + * and to wrap dump message replies in one list. + * + * VPP plugins don't have to provide special control ping, therefore + * it is necessary to providing control ping support in JVppRegistry. + + * @param clazz identifies plugin that should receive ping callback + * @return unique identifier of message in message queue + */ + int controlPing(final Class<? extends JVpp> clazz) throws VppInvocationException; +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java new file mode 100644 index 0000000..baef14c --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/JVppRegistryImpl.java @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import static java.util.Objects.requireNonNull; + +import io.fd.vpp.jvpp.callback.ControlPingCallback; +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.dto.ControlPingReply; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Default implementation of JVppRegistry. + */ +public final class JVppRegistryImpl implements JVppRegistry, ControlPingCallback { + + private static final Logger LOG = Logger.getLogger(JVppRegistryImpl.class.getName()); + + private final VppJNIConnection connection; + // Unguarded concurrent map, no race conditions expected on top of that + private final Map<String, JVppCallback> pluginRegistry; + // Guarded by self + private final Map<Integer, ControlPingCallback> pingCalls; + + public JVppRegistryImpl(final String clientName) throws IOException { + connection = new VppJNIConnection(clientName); + connection.connect(); + pluginRegistry = new ConcurrentHashMap<>(); + pingCalls = new HashMap<>(); + } + + public JVppRegistryImpl(final String clientName, final String shmPrefix) throws IOException { + connection = new VppJNIConnection(clientName, shmPrefix); + connection.connect(); + pluginRegistry = new ConcurrentHashMap<>(); + pingCalls = new HashMap<>(); + } + + @Override + public VppConnection getConnection() { + return connection; + } + + @Override + public void register(final JVpp jvpp, final JVppCallback callback) { + requireNonNull(jvpp, "jvpp should not be null"); + requireNonNull(callback, "Callback should not be null"); + final String name = jvpp.getClass().getName(); + if (pluginRegistry.containsKey(name)) { + throw new IllegalArgumentException( + String.format("Callback for plugin %s was already registered", name)); + } + jvpp.init(this, callback, connection.getConnectionInfo().queueAddress, + connection.getConnectionInfo().clientIndex); + pluginRegistry.put(name, callback); + } + + @Override + public void unregister(final String name) { + requireNonNull(name, "Plugin name should not be null"); + final JVppCallback previous = pluginRegistry.remove(name); + assertPluginWasRegistered(name, previous); + } + + @Override + public JVppCallback get(final String name) { + requireNonNull(name, "Plugin name should not be null"); + JVppCallback value = pluginRegistry.get(name); + assertPluginWasRegistered(name, value); + return value; + } + + private native int controlPing0() throws VppInvocationException; + + @Override + public int controlPing(final Class<? extends JVpp> clazz) throws VppInvocationException { + connection.checkActive(); + final String name = clazz.getName(); + + final ControlPingCallback callback = (ControlPingCallback) pluginRegistry.get(clazz.getName()); + assertPluginWasRegistered(name, callback); + + // controlPing0 is sending function and can go to waiting in case of e. g. full queue + // because of that it cant be in same synchronization block as used by reply handler function + int context = controlPing0(); + if (context < 0) { + throw new VppInvocationException("controlPing", context); + } + + synchronized (pingCalls) { + // if callback is in map it's because reply was already received + EarlyControlPingReply earlyReplyCallback = (EarlyControlPingReply) pingCalls.remove(context); + if(earlyReplyCallback == null) { + pingCalls.put(context, callback); + } else { + callback.onControlPingReply(earlyReplyCallback.getReply()); + } + } + + return context; + } + + @Override + public void onControlPingReply(final ControlPingReply reply) { + final ControlPingCallback callback; + synchronized (pingCalls) { + callback = pingCalls.remove(reply.context); + if (callback == null) { + // reply received early, because we don't know callback to call + // we wrap the reply and let the sender to call it + pingCalls.put(reply.context, new EarlyControlPingReply(reply)); + return; + } + } + // pass the reply to the callback registered by the ping caller + callback.onControlPingReply(reply); + } + + @Override + public void onError(final VppCallbackException ex) { + final int ctxId = ex.getCtxId(); + final ControlPingCallback callback; + + synchronized (pingCalls) { + callback = pingCalls.get(ctxId); + } + if (callback == null) { + LOG.log(Level.WARNING, "No callback was registered for reply id={0} ", ctxId); + return; + } + // pass the error to the callback registered by the ping caller + callback.onError(ex); + } + + private static void assertPluginWasRegistered(final String name, final JVppCallback value) { + if (value == null) { + throw new IllegalArgumentException(String.format("Callback for plugin %s is not registered", name)); + } + } + + @Override + public void close() throws Exception { + connection.close(); + } + + private static class EarlyControlPingReply implements ControlPingCallback { + + private final ControlPingReply reply; + + public EarlyControlPingReply(final ControlPingReply reply) { + this.reply = reply; + } + + public ControlPingReply getReply() { + return reply; + } + + @Override + public void onError(VppCallbackException ex) { + throw new IllegalStateException("Calling onError in EarlyControlPingReply"); + } + + @Override + public void onControlPingReply(ControlPingReply reply) { + throw new IllegalStateException("Calling onControlPingReply in EarlyControlPingReply"); + } + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java b/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java new file mode 100644 index 0000000..ce6d1bf --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/NativeLibraryLoader.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class for loading JNI libraries. + */ +public final class NativeLibraryLoader { + + private static final Logger LOG = Logger.getLogger(NativeLibraryLoader.class.getName()); + + private NativeLibraryLoader() { + throw new UnsupportedOperationException("This utility class cannot be instantiated."); + } + + /** + * Loads JNI library using class loader of the given class. + * + * @param libName name of the library to be loaded + */ + public static void loadLibrary(final String libName, final Class clazz) throws IOException { + java.util.Objects.requireNonNull(libName, "libName should not be null"); + java.util.Objects.requireNonNull(clazz, "clazz should not be null"); + try (final InputStream is = clazz.getResourceAsStream('/' + libName)) { + if (is == null) { + throw new IOException("Failed to open library resource " + libName); + } + loadStream(libName, is); + } + } + + private static void loadStream(final String libName, final InputStream is) throws IOException { + final Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(libName, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + Runtime.getRuntime().load(p.toString()); + } catch (Exception e) { + throw new IOException("Failed to load library " + p, e); + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + LOG.log(Level.WARNING, String.format("Failed to delete temporary file %s.", p), e); + } + } + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java new file mode 100644 index 0000000..7fc1682 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/VppBaseCallException.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + + +/** + * Base exception representing failed operation of JVpp request call + */ +public abstract class VppBaseCallException extends Exception { + private final String methodName; + private final int errorCode; + + /** + * Constructs an VppCallbackException with the specified api method name and error code. + * + * @param methodName name of a method, which invocation or execution failed + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppBaseCallException(final String methodName, final int errorCode) { + super(String.format("vppApi.%s failed with error code: %d", methodName, errorCode)); + this.methodName = java.util.Objects.requireNonNull(methodName, "apiMethodName is null!"); + this.errorCode = errorCode; + if(errorCode >= 0) { + throw new IllegalArgumentException("Error code must be < 0. Was " + errorCode + + " for " + methodName ); + } + } + + /** + * Constructs an VppCallbackException with the specified api method name, error description and error code. + * + * @param methodName name of a method, which invocation or execution failed + * @param message description of error reason + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppBaseCallException(final String methodName, final String message, final int errorCode) { + super(String.format("vppApi.%s failed: %s (error code: %d)", methodName,message, errorCode)); + this.methodName = java.util.Objects.requireNonNull(methodName, "apiMethodName is null!"); + this.errorCode = errorCode; + if(errorCode >= 0) { + throw new IllegalArgumentException("Error code must be < 0. Was " + errorCode + + " for " + methodName ); + } + } + + /** + * Returns name of a method, which invocation failed. + * + * @return method name + */ + public String getMethodName() { + return methodName; + } + + /** + * Returns the error code associated with this failure. + * + * @return a negative integer error code + */ + public int getErrorCode() { + return errorCode; + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java new file mode 100644 index 0000000..adcc5d2 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/VppCallbackException.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +/** + * Callback Exception representing failed operation of JVpp request call + */ +public class VppCallbackException extends VppBaseCallException { + private final int ctxId; + + /** + * Constructs an VppCallbackException with the specified api method name and error code. + * + * @param methodName name of a method, which invocation failed. + * @param message description of error reason + * @param ctxId api request context identifier + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppCallbackException(final String methodName, final String message, final int ctxId, final int errorCode ){ + super(methodName, message, errorCode); + this.ctxId = ctxId; + } + + /** + * Returns api request context identifier. + * + * @return value of context identifier + */ + public int getCtxId() { + return ctxId; + } + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java new file mode 100644 index 0000000..e6fd3bd --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/VppConnection.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import java.io.IOException; + +/** + * Representation of a management connection to VPP. + */ +public interface VppConnection extends AutoCloseable { + + /** + * Opens VppConnection for communication with VPP. + * + * @throws IOException if connection is not established + */ + void connect() throws IOException; + + /** + * Checks if this instance connection is active. + * + * @throws IllegalStateException if this instance was disconnected. + */ + void checkActive(); + + /** + * Closes Vpp connection. + */ + @Override + void close(); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java new file mode 100644 index 0000000..a7ccb19 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/VppInvocationException.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +/** + * Exception thrown when Vpp jAPI method invocation failed. + */ +public class VppInvocationException extends VppBaseCallException { + /** + * Constructs an VppApiInvocationFailedException with the specified api method name and error code. + * + * @param methodName name of a method, which invocation failed. + * @param errorCode negative error code value associated with this failure + * @throws NullPointerException if apiMethodName is null + */ + public VppInvocationException(final String methodName, final int errorCode) { + super(methodName, errorCode); + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java b/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java new file mode 100644 index 0000000..6a414f3 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/VppJNIConnection.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp; + +import static io.fd.vpp.jvpp.NativeLibraryLoader.loadLibrary; +import static java.lang.String.format; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * JNI based representation of a management connection to VPP. + */ +public final class VppJNIConnection implements VppConnection { + private static final Logger LOG = Logger.getLogger(VppJNIConnection.class.getName()); + private static final String DEFAULT_SHM_PREFIX = "/vpe-api"; + + static { + final String libName = "libjvpp_registry.so"; + try { + loadLibrary(libName, VppJNIConnection.class); + } catch (IOException e) { + LOG.log(Level.SEVERE, format("Can't find vpp jni library: %s", libName), e); + throw new ExceptionInInitializerError(e); + } + } + + private ConnectionInfo connectionInfo; + + private final String clientName; + private final String shmPrefix; + private volatile boolean disconnected = false; + + /** + * Create VPPJNIConnection instance for client connecting to VPP. + * + * @param clientName client name instance to be used for communication. Single connection per clientName is + * allowed. + */ + public VppJNIConnection(final String clientName) { + this.clientName = Objects.requireNonNull(clientName, "Null clientName"); + this.shmPrefix = DEFAULT_SHM_PREFIX; + } + + public VppJNIConnection(final String clientName, final String shmPrefix) { + this.clientName = Objects.requireNonNull(clientName, "Null clientName"); + this.shmPrefix = Objects.requireNonNull(shmPrefix, "Null shmPrefix"); + } + + /** + * Guarded by VppJNIConnection.class + */ + private static final Map<String, VppJNIConnection> connections = new HashMap<>(); + + /** + * Initiate VPP connection for current instance + * + * Multiple instances are allowed since this class is not a singleton (VPP allows multiple management connections). + * + * However only a single connection per clientName is allowed. + * + * @throws IOException in case the connection could not be established + */ + + @Override + public void connect() throws IOException { + _connect(shmPrefix); + } + + private void _connect(final String shmPrefix) throws IOException { + Objects.requireNonNull(shmPrefix, "Shared memory prefix must be defined"); + + synchronized (VppJNIConnection.class) { + if (connections.containsKey(clientName)) { + throw new IOException("Client " + clientName + " already connected"); + } + + connectionInfo = clientConnect(shmPrefix, clientName); + if (connectionInfo.status != 0) { + throw new IOException("Connection returned error " + connectionInfo.status); + } + connections.put(clientName, this); + } + } + + @Override + public final void checkActive() { + if (disconnected) { + throw new IllegalStateException("Disconnected client " + clientName); + } + } + + @Override + public final synchronized void close() { + if (!disconnected) { + disconnected = true; + try { + clientDisconnect(); + } finally { + synchronized (VppJNIConnection.class) { + connections.remove(clientName); + } + } + } + } + + public ConnectionInfo getConnectionInfo() { + return connectionInfo; + } + + /** + * VPP connection information used by plugins to reuse the connection. + */ + public static final class ConnectionInfo { + public final long queueAddress; + public final int clientIndex; + public final int status; // FIXME throw exception instead + public final int pid; + + public ConnectionInfo(long queueAddress, int clientIndex, int status, int pid) { + this.queueAddress = queueAddress; + this.clientIndex = clientIndex; + this.status = status; + this.pid = pid; + } + } + + private static native ConnectionInfo clientConnect(String shmPrefix, String clientName); + + private static native void clientDisconnect(); + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java b/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java new file mode 100644 index 0000000..efddfdb --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/callback/ControlPingCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.callback; + +import io.fd.vpp.jvpp.dto.ControlPingReply; + +/** + * Represents callback for control_ping message. + */ +public interface ControlPingCallback extends JVppCallback { + + void onControlPingReply(ControlPingReply reply); + +} + diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java b/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java new file mode 100644 index 0000000..ae02063 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppCallback.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.callback; +import io.fd.vpp.jvpp.VppCallbackException; + +/** + * Base JVppCallback interface + */ +public interface JVppCallback { + /** + * onError callback handler used to report failing operation + * @param ex VppCallbackException object containing details about failing operation + */ + void onError(VppCallbackException ex); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java b/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java new file mode 100644 index 0000000..8ab0cb2 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/callback/JVppNotificationCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.callback; + +/** +* Notification callback +*/ +public interface JVppNotificationCallback { + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java b/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java new file mode 100644 index 0000000..1e780bb --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/coverity/SuppressFBWarnings.java @@ -0,0 +1,40 @@ +/* + * 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. + */ + +package io.fd.vpp.jvpp.coverity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Used to suppress FindBugs warnings found by Coverity. <br> + * We don't want extra dependency, so we define our own annotation version. + * + * @see <a href="https://sourceforge.net/p/findbugs/feature-requests/298/#5e88"/>Findbugs sourceforge</a> + */ +@Retention(RetentionPolicy.CLASS) +public @interface SuppressFBWarnings { + /** + * The set of FindBugs warnings that are to be suppressed in annotated element. The value can be a bug category, + * kind or pattern. + */ + String[] value() default {}; + + /** + * Optional documentation of the reason why the warning is suppressed + */ + String justification() default ""; +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java new file mode 100644 index 0000000..984e167 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPing.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.VppInvocationException; + +/** + * Represents request DTO for control_ping message. + */ +public final class ControlPing implements JVppRequest { + + @Override + public int send(final JVpp jvpp) throws VppInvocationException { + return jvpp.controlPing(this); + } + +} + + diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java new file mode 100644 index 0000000..61e4d0e --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/dto/ControlPingReply.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +import java.util.Objects; + +/** + * Represents reply DTO for control_ping message. + */ +public final class ControlPingReply implements JVppReply<ControlPing> { + + public int context; + public int clientIndex; + public int vpePid; + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final ControlPingReply that = (ControlPingReply) o; + return context == that.context && + clientIndex == that.clientIndex && + vpePid == that.vpePid; + } + + @Override + public int hashCode() { + return Objects.hash(context, clientIndex, vpePid); + } + + @Override + public String toString() { + return "ControlPingReply{" + + "context=" + context + + ", clientIndex=" + clientIndex + + ", vpePid=" + vpePid + + '}'; + } +} + diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java new file mode 100644 index 0000000..60b9898 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppDump.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all dump requests +*/ +public interface JVppDump extends JVppRequest { + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java new file mode 100644 index 0000000..73f512d --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReply.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all reply DTOs +*/ +public interface JVppReply<REQ extends JVppRequest> { + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java new file mode 100644 index 0000000..1511139 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppReplyDump.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +/** +* Base interface for all dump replies +*/ +public interface JVppReplyDump<REQ extends JVppRequest, RESP extends JVppReply<REQ>> + extends JVppReply<REQ> { + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java new file mode 100644 index 0000000..9b301da --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/dto/JVppRequest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.dto; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.VppInvocationException; + +/** +* Base interface for all request DTOs +*/ +public interface JVppRequest { + + /** + * Invoke current operation asynchronously on VPP + * + * @return context id of this request. Can be used to track incoming response + */ + int send(JVpp jvpp) throws VppInvocationException; + +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java b/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java new file mode 100644 index 0000000..ac85f53 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/future/AbstractFutureJVppInvoker.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.future; + + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionStage; + +import io.fd.vpp.jvpp.JVpp; +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.VppInvocationException; +import io.fd.vpp.jvpp.dto.JVppDump; +import io.fd.vpp.jvpp.dto.JVppReply; +import io.fd.vpp.jvpp.dto.JVppReplyDump; +import io.fd.vpp.jvpp.dto.JVppRequest; + +/** + * Future facade on top of JVpp + */ +public abstract class AbstractFutureJVppInvoker implements FutureJVppInvoker { + + private final JVpp jvpp; + private final JVppRegistry registry; + + /** + * Guarded by self + */ + private final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requests; + + protected AbstractFutureJVppInvoker(final JVpp jvpp, final JVppRegistry registry, + final Map<Integer, CompletableFuture<? extends JVppReply<?>>> requestMap) { + this.jvpp = Objects.requireNonNull(jvpp, "jvpp should not be null"); + this.registry = Objects.requireNonNull(registry, "registry should not be null"); + // Request map represents the shared state between this facade and it's callback + // where facade puts futures in and callback completes + removes them + this.requests = Objects.requireNonNull(requestMap, "Null requestMap"); + } + + protected final Map<Integer, CompletableFuture<? extends JVppReply<?>>> getRequests() { + synchronized (requests) { + return requests; + } + } + + // TODO use Optional in Future, java8 + + @Override + @SuppressWarnings("unchecked") + public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req) { + try { + // jvpp.send() can go to waiting state if sending queue is full, putting it into same + // synchronization block as used by receiving part (synchronized(requests)) can lead + // to deadlock between these two sides or at least slowing sending process by slow + // reader + final CompletableFuture<REPLY> replyCompletableFuture; + final int contextId = jvpp.send(req); + + if(req instanceof JVppDump) { + throw new IllegalArgumentException("Send with empty reply dump has to be used in case of dump calls"); + } + + synchronized(requests) { + CompletableFuture<? extends JVppReply<?>> replyFuture = requests.get(contextId); + if (replyFuture == null) { + // reply not yet received, put new future into map + replyCompletableFuture = new CompletableFuture<>(); + requests.put(contextId, replyCompletableFuture); + } else { + // reply already received (should be completed by reader), + // remove future from map and return it to caller + replyCompletableFuture = (CompletableFuture<REPLY>) replyFuture; + requests.remove(contextId); + } + } + + // TODO in case of timeouts/missing replies, requests from the map are not removed + // consider adding cancel method, that would remove requests from the map and cancel + // associated replyCompletableFuture + + return replyCompletableFuture; + } catch (VppInvocationException ex) { + final CompletableFuture<REPLY> replyCompletableFuture = new CompletableFuture<>(); + replyCompletableFuture.completeExceptionally(ex); + return replyCompletableFuture; + } + } + + @Override + @SuppressWarnings("unchecked") + public <REQ extends JVppRequest, REPLY extends JVppReply<REQ>, DUMP extends JVppReplyDump<REQ, REPLY>> CompletionStage<DUMP> send( + REQ req, DUMP emptyReplyDump) { + try { + // jvpp.send() and registry.controlPing() can go to waiting state if sending queue is full, + // putting it into same synchronization block as used by receiving part (synchronized(requests)) + // can lead to deadlock between these two sides or at least slowing sending process by slow reader + final CompletableDumpFuture<DUMP> replyDumpFuture; + final int contextId = jvpp.send(req); + + if(!(req instanceof JVppDump)) { + throw new IllegalArgumentException("Send without empty reply dump has to be used in case of regular calls"); + } + + synchronized(requests) { + CompletableFuture<? extends JVppReply<?>> replyFuture = requests.get(contextId); + if (replyFuture == null) { + // reply not received yet, put new future to map + replyDumpFuture = new CompletableDumpFuture<>(contextId, emptyReplyDump); + requests.put(contextId, replyDumpFuture); + } else { + // reply already received, save existing future + replyDumpFuture = (CompletableDumpFuture<DUMP>) replyFuture; + } + } + + final int pingId = registry.controlPing(jvpp.getClass()); + + synchronized(requests) { + if (requests.remove(pingId) == null) { + // reply not received yet, put future into map under pingId + requests.put(pingId, replyDumpFuture); + } else { + // reply already received, complete future + // ping reply couldn't complete the future because it is not in map under + // ping id + replyDumpFuture.complete(replyDumpFuture.getReplyDump()); + requests.remove(contextId); + } + } + + // TODO in case of timeouts/missing replies, requests from the map are not removed + // consider adding cancel method, that would remove requests from the map and cancel + // associated replyCompletableFuture + + return replyDumpFuture; + } catch (VppInvocationException ex) { + final CompletableFuture<DUMP> replyCompletableFuture = new CompletableFuture<>(); + replyCompletableFuture.completeExceptionally(ex); + return replyCompletableFuture; + } + } + + public static final class CompletableDumpFuture<T extends JVppReplyDump<?, ?>> extends CompletableFuture<T> { + private final T replyDump; + private final int contextId; + + public CompletableDumpFuture(final int contextId, final T emptyDump) { + this.contextId = contextId; + this.replyDump = emptyDump; + } + + public int getContextId() { + return contextId; + } + + public T getReplyDump() { + return replyDump; + } + } + + @Override + public void close() throws Exception { + jvpp.close(); + } +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java b/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java new file mode 100644 index 0000000..65250ed --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/future/FutureJVppInvoker.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.future; + + +import io.fd.vpp.jvpp.dto.JVppReply; +import io.fd.vpp.jvpp.dto.JVppReplyDump; +import io.fd.vpp.jvpp.dto.JVppRequest; + +import java.util.concurrent.CompletionStage; +import io.fd.vpp.jvpp.notification.EventRegistryProvider; + +/** +* Future facade on top of JVpp +*/ +public interface FutureJVppInvoker extends EventRegistryProvider, AutoCloseable { + + /** + * Invoke asynchronous operation on VPP + * + * @return CompletionStage with future result of an async VPP call + * @throws io.fd.vpp.jvpp.VppInvocationException when send request failed with details + */ + <REQ extends JVppRequest, REPLY extends JVppReply<REQ>> CompletionStage<REPLY> send(REQ req); + + + /** + * Invoke asynchronous dump operation on VPP + * + * @return CompletionStage with aggregated future result of an async VPP dump call + * @throws io.fd.vpp.jvpp.VppInvocationException when send request failed with details + */ + <REQ extends JVppRequest, REPLY extends JVppReply<REQ>, DUMP extends JVppReplyDump<REQ, REPLY>> CompletionStage<DUMP> send( + REQ req, DUMP emptyReplyDump); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistry.java b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistry.java new file mode 100644 index 0000000..12515a5 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistry.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.notification; + +/** + * Base registry for notification callbacks. + */ +public interface EventRegistry extends AutoCloseable { + + void close(); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistryProvider.java b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistryProvider.java new file mode 100644 index 0000000..1ac5d55 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/notification/EventRegistryProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.notification; + +/** + * Provides notification registry + */ +public interface EventRegistryProvider { + + /** + * Get current notification registry instance + */ + EventRegistry getEventRegistry(); +} diff --git a/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java b/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java new file mode 100644 index 0000000..27b4d29 --- /dev/null +++ b/java/jvpp-registry/io/fd/vpp/jvpp/test/ConnectionTest.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2016 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. + */ + +package io.fd.vpp.jvpp.test; + +import io.fd.vpp.jvpp.JVppRegistry; +import io.fd.vpp.jvpp.JVppRegistryImpl; + +/** + * Run using: + * sudo java -cp build-vpp-native/vpp-api/java/jvpp-registry-16.09.jar io.fd.vpp.jvpp.test.ConnectionTest + */ +public class ConnectionTest { + + private static void testConnect() throws Exception { + System.out.println("Testing JNI connection with JVppRegistry"); + final JVppRegistry registry = new JVppRegistryImpl("ConnectionTest"); + try { + System.out.println("Successfully connected to vpp"); + Thread.sleep(5000); + System.out.println("Disconnecting..."); + Thread.sleep(1000); + } finally { + registry.close(); + } + } + + public static void main(String[] args) throws Exception { + testConnect(); + } +} diff --git a/java/jvpp-registry/jvpp_registry.c b/java/jvpp-registry/jvpp_registry.c new file mode 100644 index 0000000..bbe9719 --- /dev/null +++ b/java/jvpp-registry/jvpp_registry.c @@ -0,0 +1,410 @@ +/* + * Copyright (c) 2016 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 _GNU_SOURCE /* for strcasestr(3) */ +#include <vnet/vnet.h> + +#define vl_api_version(n,v) static u32 vpe_api_version = (v); +#include <vpp/api/vpe.api.h> +#undef vl_api_version + + +#include <jni.h> +#include <jvpp-common/jvpp_common.h> +#include "io_fd_vpp_jvpp_VppJNIConnection.h" +#include "io_fd_vpp_jvpp_JVppRegistryImpl.h" + +#include <vpp/api/vpe_msg_enum.h> +#define vl_typedefs /* define message structures */ +#include <vpp/api/vpe_all_api_h.h> +#undef vl_typedefs + +#define vl_endianfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) +#define vl_printfun +#include <vpp/api/vpe_all_api_h.h> +#undef vl_printfun + +vlib_main_t vlib_global_main; +vlib_main_t **vlib_mains; + +/* + * The Java runtime isn't compile w/ -fstack-protector, + * so we have to supply missing external references for the + * regular vpp libraries. + */ +void __stack_chk_guard(void) __attribute__((weak)); +void __stack_chk_guard(void) { +} + +#define CONTROL_PING_MESSAGE "control_ping" +#define CONTROL_PING_REPLY_MESSAGE "control_ping_reply" + +typedef struct { + /* UThread attachment */ + volatile u32 control_ping_result_ready; + volatile i32 control_ping_retval; + + /* Control ping callback */ + jobject registryObject; + jclass registryClass; + jclass controlPingReplyClass; + jclass callbackExceptionClass; + int control_ping_msg_id; + int control_ping_reply_msg_id; + + /* Thread cleanup */ + pthread_key_t cleanup_rx_thread_key; + + /* Connected indication */ + volatile u8 is_connected; + u32 vpe_pid; +} jvpp_registry_main_t; + +jvpp_registry_main_t jvpp_registry_main __attribute__((aligned (64))); + +void vl_client_add_api_signatures(vl_api_memclnt_create_t *mp) { + /* + * Send the main API signature in slot 0. This bit of code must + * match the checks in ../vpe/api/api.c: vl_msg_api_version_check(). + */ + mp->api_versions[0] = clib_host_to_net_u32(vpe_api_version); +} + +/* cleanup handler for RX thread */ +static_always_inline void cleanup_rx_thread(void *arg) { + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + vppjni_lock(jm, 99); + + int getEnvStat = (*jm->jvm)->GetEnv(jm->jvm, (void **) &(jm->jenv), + JNI_VERSION_1_8); + if (getEnvStat == JNI_EVERSION) { + clib_warning("Unsupported JNI version\n"); + rm->control_ping_retval = VNET_API_ERROR_UNSUPPORTED_JNI_VERSION; + goto out; + } else if (getEnvStat != JNI_EDETACHED) { + (*jm->jvm)->DetachCurrentThread(jm->jvm); + } + out: vppjni_unlock(jm); +} + +static void vl_api_control_ping_reply_t_handler( + vl_api_control_ping_reply_t * mp) { + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + char was_thread_connected = 0; + + // attach to java thread if not attached + int getEnvStat = (*jm->jvm)->GetEnv(jm->jvm, (void **) &(jm->jenv), + JNI_VERSION_1_8); + if (getEnvStat == JNI_EDETACHED) { + if ((*jm->jvm)->AttachCurrentThread(jm->jvm, (void **) &(jm->jenv), + NULL) != 0) { + clib_warning("Failed to attach thread\n"); + rm->control_ping_retval = + VNET_API_ERROR_FAILED_TO_ATTACH_TO_JAVA_THREAD; + goto out; + } + + // workaround as we can't use pthread_cleanup_push + pthread_key_create(&rm->cleanup_rx_thread_key, cleanup_rx_thread); + // destructor is only called if the value of key is non null + pthread_setspecific(rm->cleanup_rx_thread_key, (void *) 1); + was_thread_connected = 1; + } else if (getEnvStat == JNI_EVERSION) { + clib_warning("Unsupported JNI version\n"); + rm->control_ping_retval = VNET_API_ERROR_UNSUPPORTED_JNI_VERSION; + goto out; + } + + if (was_thread_connected == 0) { + JNIEnv *env = jm->jenv; + if (mp->retval < 0) { + call_on_error("controlPing", mp->context, mp->retval, + rm->registryClass, rm->registryObject, + rm->callbackExceptionClass); + } else { + jmethodID constructor = (*env)->GetMethodID(env, + rm->controlPingReplyClass, "<init>", "()V"); + jmethodID callbackMethod = (*env)->GetMethodID(env, + rm->registryClass, "onControlPingReply", + "(Lio/fd/vpp/jvpp/dto/ControlPingReply;)V"); + + jobject dto = (*env)->NewObject(env, rm->controlPingReplyClass, + constructor); + + jfieldID contextFieldId = (*env)->GetFieldID(env, + rm->controlPingReplyClass, "context", "I"); + (*env)->SetIntField(env, dto, contextFieldId, + clib_net_to_host_u32(mp->context)); + + jfieldID clientIndexFieldId = (*env)->GetFieldID(env, + rm->controlPingReplyClass, "clientIndex", "I"); + (*env)->SetIntField(env, dto, clientIndexFieldId, + clib_net_to_host_u32(mp->client_index)); + + jfieldID vpePidFieldId = (*env)->GetFieldID(env, + rm->controlPingReplyClass, "vpePid", "I"); + (*env)->SetIntField(env, dto, vpePidFieldId, + clib_net_to_host_u32(mp->vpe_pid)); + + (*env)->CallVoidMethod(env, rm->registryObject, callbackMethod, + dto); + (*env)->DeleteLocalRef(env, dto); + } + } + + out: rm->vpe_pid = clib_net_to_host_u32(mp->vpe_pid); + rm->control_ping_result_ready = 1; +} + +static int find_ping_id() { + int rv = 0; + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + api_main_t *am = &api_main; + hash_pair_t *hp; + jm->messages_hash = am->msg_index_by_name_and_crc; + + rm->control_ping_msg_id = -1; + rm->control_ping_reply_msg_id = -1; + + hash_foreach_pair (hp, jm->messages_hash, + ({ + char *key = (char *)hp->key; // key format: name_crc + int msg_name_len = strlen(key) - 9; // ignore crc + if (strlen(CONTROL_PING_MESSAGE) == msg_name_len && + strncmp(CONTROL_PING_MESSAGE, (char *)hp->key, msg_name_len) == 0) { + rm->control_ping_msg_id = (u32)hp->value[0]; + } + if (strlen(CONTROL_PING_REPLY_MESSAGE) == msg_name_len && + strncmp(CONTROL_PING_REPLY_MESSAGE, (char *)hp->key, msg_name_len) == 0) { + rm->control_ping_reply_msg_id = (u32)hp->value[0]; + } + })); + if (rm->control_ping_msg_id == -1) { + clib_warning("failed to find id for %s", CONTROL_PING_MESSAGE); + rv = -1; + } + if (rm->control_ping_reply_msg_id == -1) { + clib_warning("failed to find id for %s", CONTROL_PING_REPLY_MESSAGE); + rv = -1; + } + return rv; +} + +static int send_initial_control_ping() { + f64 timeout; + clib_time_t clib_time; + vl_api_control_ping_t * mp; + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + clib_time_init(&clib_time); + + rm->control_ping_result_ready = 0; + mp = vl_msg_api_alloc(sizeof(*mp)); + memset(mp, 0, sizeof(*mp)); + mp->_vl_msg_id = ntohs(rm->control_ping_msg_id); + mp->client_index = jm->my_client_index; + + // send message: + vl_msg_api_send_shmem(jm->vl_input_queue, (u8 *) &mp); + + // wait for results: Current time + 10 seconds is the timeout + timeout = clib_time_now(&clib_time) + 10.0; + int rv = VNET_API_ERROR_RESPONSE_NOT_READY; + while (clib_time_now(&clib_time) < timeout) { + if (rm->control_ping_result_ready == 1) { + rv = rm->control_ping_retval; + break; + } + } + + if (rv != 0) { + vl_msg_api_clean_handlers(rm->control_ping_reply_msg_id); + clib_warning("first control ping failed: %d", rv); + } + return rv; +} + +#if USE_DLMALLOC == 1 +void * __jvpp_heap; +#endif + +static int connect_to_vpe(char *shm_prefix, char *name) { + jvpp_main_t * jm = &jvpp_main; + api_main_t * am = &api_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + +#if USE_DLMALLOC == 1 + __jvpp_heap = clib_mem_init (0, 1<<20); +#endif + + if (vl_client_connect_to_vlib(shm_prefix, name, 32) < 0) + return -1; + jm->my_client_index = am->my_client_index; + + jm->vl_input_queue = am->shmem_hdr->vl_input_queue; + + if (find_ping_id() < 0) + return -1; + + vl_msg_api_set_handlers(rm->control_ping_reply_msg_id, CONTROL_PING_REPLY_MESSAGE, + vl_api_control_ping_reply_t_handler, vl_noop_handler, + vl_api_control_ping_reply_t_endian, + vl_api_control_ping_reply_t_print, + sizeof(vl_api_control_ping_reply_t), 1); + + return send_initial_control_ping(); +} + +JNIEXPORT jobject JNICALL Java_io_fd_vpp_jvpp_VppJNIConnection_clientConnect( + JNIEnv *env, jclass obj, jstring shmPrefix, jstring clientName) { + /* + * TODO introducing memory prefix as variable can be used in hc2vpp + * to be able to run without root privileges + * https://jira.fd.io/browse/HC2VPP-176 + */ + int rv; + const char *client_name; + const char *shm_prefix; + void vl_msg_reply_handler_hookup(void); + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + + jclass connectionInfoClass = (*env)->FindClass(env, + "io/fd/vpp/jvpp/VppJNIConnection$ConnectionInfo"); + jmethodID connectionInfoConstructor = (*env)->GetMethodID(env, + connectionInfoClass, "<init>", "(JIII)V"); + + if (rm->is_connected) { + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, 0, 0, + VNET_API_ERROR_ALREADY_CONNECTED, 0); + } + + client_name = (*env)->GetStringUTFChars(env, clientName, 0); + shm_prefix = (*env)->GetStringUTFChars(env, shmPrefix, 0); + + if (!client_name) { + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, 0, 0, VNET_API_ERROR_INVALID_VALUE, 0, shmPrefix); + } + + if (!shm_prefix) { + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, 0, 0, VNET_API_ERROR_INVALID_VALUE, 0, shmPrefix); + } + + rv = connect_to_vpe((char *) shm_prefix, (char *) client_name); + + if (rv < 0) + clib_warning("connection failed, rv %d", rv); + + (*env)->ReleaseStringUTFChars(env, clientName, client_name); + (*env)->ReleaseStringUTFChars(env, shmPrefix, shm_prefix); + + return (*env)->NewObject(env, connectionInfoClass, + connectionInfoConstructor, (jlong) pointer_to_uword (jm->vl_input_queue), + (jint) jm->my_client_index, (jint) rv, (jint) rm->vpe_pid); +} + +JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_JVppRegistryImpl_controlPing0( + JNIEnv *env, jobject regstryObject) { + jvpp_main_t * jm = &jvpp_main; + vl_api_control_ping_t * mp; + u32 my_context_id = vppjni_get_context_id(&jvpp_main); + jvpp_registry_main_t * rm = &jvpp_registry_main; + + if (rm->registryObject == 0) { + rm->registryObject = (*env)->NewGlobalRef(env, regstryObject); + } + if (rm->registryClass == 0) { + rm->registryClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->GetObjectClass(env, regstryObject)); + } + + mp = vl_msg_api_alloc(sizeof(*mp)); + memset(mp, 0, sizeof(*mp)); + mp->_vl_msg_id = ntohs(rm->control_ping_msg_id); + mp->client_index = jm->my_client_index; + mp->context = clib_host_to_net_u32(my_context_id); + + // send message: + vl_msg_api_send_shmem(jm->vl_input_queue, (u8 *) &mp); + return my_context_id; +} + +JNIEXPORT void JNICALL Java_io_fd_vpp_jvpp_VppJNIConnection_clientDisconnect( + JNIEnv *env, jclass clazz) { + jvpp_registry_main_t * rm = &jvpp_registry_main; + rm->is_connected = 0; // TODO make thread safe + vl_client_disconnect_from_vlib(); + + // cleanup: + if (rm->registryObject) { + (*env)->DeleteGlobalRef(env, rm->registryObject); + rm->registryObject = 0; + } + if (rm->registryClass) { + (*env)->DeleteGlobalRef(env, rm->registryClass); + rm->registryClass = 0; + } +} + +jint JNI_OnLoad(JavaVM *vm, void *reserved) { + jvpp_main_t * jm = &jvpp_main; + jvpp_registry_main_t * rm = &jvpp_registry_main; + JNIEnv* env; + + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return JNI_EVERSION; + } + + rm->controlPingReplyClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->FindClass(env, "io/fd/vpp/jvpp/dto/ControlPingReply")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + clib_warning("Failed to cache class references\n"); + return JNI_ERR; + } + + rm->callbackExceptionClass = (jclass) (*env)->NewGlobalRef(env, + (*env)->FindClass(env, "io/fd/vpp/jvpp/VppCallbackException")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + } + + jm->jvm = vm; + return JNI_VERSION_1_8; +} + +void JNI_OnUnload(JavaVM *vm, void *reserved) { + jvpp_main_t * jm = &jvpp_main; + JNIEnv* env; + if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_8) != JNI_OK) { + return; + } + + jm->jenv = NULL; + jm->jvm = NULL; +} diff --git a/java/jvpp/gen/jvpp_gen.py b/java/jvpp/gen/jvpp_gen.py new file mode 100755 index 0000000..067a92f --- /dev/null +++ b/java/jvpp/gen/jvpp_gen.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +# +import argparse +import logging +import os +import sys + +from jvppgen.types_gen import generate_types +from jvppgen.enums_gen import generate_enums +from jvppgen.unions_gen import generate_unions +from jvppgen.dto_gen import generate_dtos +from jvppgen.jvpp_ifc_gen import generate_java_ifc +from jvppgen.jvpp_impl_gen import generate_java_impl +from jvppgen.callback_gen import generate_callbacks +from jvppgen.jni_gen import generate_jni +from jvppgen.notification_gen import generate_notifications +from jvppgen.jvpp_future_facade_gen import generate_future_facade +from jvppgen.jvpp_callback_facade_gen import generate_callback_facade +from jvppgen.jvpp_model import JVppModel + + +def generate_jvpp(root_dir, model, logger): + base_dir = "%s/target/%s" % (root_dir, model.plugin_package.replace(".", "/")) + generate_types(_work_dir(base_dir, "types"), model, logger) + generate_enums(_work_dir(base_dir, "types"), model, logger) + generate_unions(_work_dir(base_dir, "types"), model, logger) + generate_dtos(_work_dir(base_dir, "dto"), model, logger) + generate_java_ifc(_work_dir(base_dir), model, logger) + generate_java_impl(_work_dir(base_dir), model, logger) + generate_callbacks(_work_dir(base_dir, "callback"), model, logger) + generate_jni(root_dir, model, logger) + generate_notifications(_work_dir(base_dir, "notification"), model, logger) + generate_future_facade(_work_dir(base_dir, "future"), model, logger) + generate_callback_facade(_work_dir(base_dir, "callfacade"), model, logger) + + +def _work_dir(work_dir, sub_dir=None): + if sub_dir: + work_dir = "%s/%s" % (work_dir, sub_dir) + try: + os.makedirs(work_dir) + except OSError: + if not os.path.isdir(work_dir): + raise + return work_dir + + +def _init_logger(): + try: + verbose = int(os.getenv("V", 0)) + except: + verbose = 0 + + log_level = logging.WARNING + if verbose == 1: + log_level = logging.INFO + elif verbose >= 2: + log_level = logging.DEBUG + + logging.basicConfig(stream=sys.stdout, level=log_level) + logger = logging.getLogger("JVPP GEN") + logger.setLevel(log_level) + return logger + + +if __name__ == '__main__': + logger = _init_logger() + + argparser = argparse.ArgumentParser(description="VPP Java API generator") + argparser.add_argument('-i', nargs='+', metavar='api_file.json', help="json vpp api file(s)") + argparser.add_argument('--plugin_name') + argparser.add_argument('--root_dir') + args = argparser.parse_args() + + logger.info("Generating Java API for %s" % args.i) + logger.debug("plugin_name: %s" % args.plugin_name) + logger.debug("root_dir: %s" % args.root_dir) + + model = JVppModel(logger, args.i, args.plugin_name) + generate_jvpp(args.root_dir, model, logger) diff --git a/java/jvpp/gen/jvppgen/__init__.py b/java/jvpp/gen/jvppgen/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/java/jvpp/gen/jvppgen/__init__.py diff --git a/java/jvpp/gen/jvppgen/callback_gen.py b/java/jvpp/gen/jvppgen/callback_gen.py new file mode 100755 index 0000000..b1ad201 --- /dev/null +++ b/java/jvpp/gen/jvppgen/callback_gen.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +# +from string import Template + +from jvpp_model import is_request, is_dump, is_control_ping, is_control_ping_reply + + +def generate_callbacks(work_dir, model, logger): + json_api_files = model.json_api_files + logger.debug("Generating Callback interfaces for %s" % json_api_files) + plugin_package = model.plugin_package + + callbacks = [] + for msg in model.messages: + name = msg.java_name_upper + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + callbacks.append("%s.callback.%sCallback" % (plugin_package, name)) + callback = _CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=json_api_files, + name=name) + + with open("%s/%sCallback.java" % (work_dir, name), "w") as f: + f.write(callback) + + plugin_name = model.plugin_java_name + with open("%s/JVpp%sGlobalCallback.java" % (work_dir, plugin_name), "w") as f: + f.write(_GLOBAL_CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=json_api_files, + plugin_name=plugin_name, + callbacks=", ".join(callbacks) + )) + +_CALLBACK_TEMPLATE = Template("""package $plugin_package.callback; + +/** + * <p>Represents callback for plugin's api message. + * <br>It was generated by jvppgen/callback_gen.py based on $json_filename. + */ +public interface ${name}Callback extends io.fd.vpp.jvpp.callback.JVppCallback { + + void on${name}(${plugin_package}.dto.${name} reply); +} +""") + +_GLOBAL_CALLBACK_TEMPLATE = Template("""package $plugin_package.callback; + +/** + * <p>Global aggregated callback interface. + * <br>It was generated by jvppgen/callback_gen.py based on $json_filename. + */ +public interface JVpp${plugin_name}GlobalCallback extends io.fd.vpp.jvpp.callback.ControlPingCallback, $callbacks { +} +""") diff --git a/java/jvpp/gen/jvppgen/dto_gen.py b/java/jvpp/gen/jvppgen/dto_gen.py new file mode 100755 index 0000000..cbd969d --- /dev/null +++ b/java/jvpp/gen/jvppgen/dto_gen.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. + +from string import Template + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import is_request, is_reply, is_retval, is_dump, is_details, is_event, is_control_ping, \ + is_control_ping_reply + + +def generate_dtos(work_dir, model, logger): + logger.debug("Generating DTOs for %s " % model.json_api_files) + _generate_message_dtos(work_dir, model, logger) + _generate_dump_reply_wrappers(work_dir, model, logger) + + +def _generate_message_dtos(work_dir, model, logger): + for msg in model.messages: + logger.debug("Generating DTO for message %s", msg) + class_name = msg.java_name_upper + + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_request(msg): + dto = _generate_request_dto(msg, model, base_type="JVppRequest") + elif is_dump(msg): + dto = _generate_request_dto(msg, model, base_type="JVppDump") + elif is_reply(msg) or is_details(msg): + dto = _generate_reply_dto(msg, model) + elif is_event(msg): + dto = _generate_event_dto(msg, model) + else: + logger.warn("Failed to generate DTO for: %s. Message type is not supported." % msg) + continue + with open("%s/%s.java" % (work_dir, class_name), "w") as f: + f.write(dto) + + +def _generate_request_dto(msg, model, base_type): + msg_java_name_upper = msg.java_name_upper + fields = msg.fields + return _REQUEST_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + base_type=base_type, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields), + send=_generate_send(model, msg)) + +_REQUEST_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents request DTO. + * <br>It was generated by dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name implements io.fd.vpp.jvpp.dto.$base_type { +$fields +$hash_code +$equals +$to_string +$send +} +""") + + +def _generate_send(model, msg): + return _SEND_TEMPLATE.substitute( + plugin_package=model.plugin_package, + plugin_name=model.plugin_java_name, + method_name=msg.java_name_lower, + args="this" if msg.has_fields else "" + ) + +_SEND_TEMPLATE = Template(""" + @Override + public int send(final io.fd.vpp.jvpp.JVpp jvpp) throws io.fd.vpp.jvpp.VppInvocationException { + return (($plugin_package.JVpp${plugin_name})jvpp).$method_name($args); + }""") + + +def _generate_reply_dto(msg, model): + msg_java_name_upper = msg.java_name_upper + # Negative retval is mapped to java exception, so filter it out: + fields = filter(lambda field: not is_retval(field), msg.fields) + return _REPLY_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + request_name=msg.request_java, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields)) + +_REPLY_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents reply DTO. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name implements io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_name> { +$fields +$hash_code +$equals +$to_string +} +""") + + +def _generate_event_dto(msg, model): + msg_java_name_upper = msg.java_name_upper + # Negative retval is mapped to java exception, so filter it out: + fields = filter(lambda field: not is_retval(field), msg.fields) + return _EVENT_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + class_name=msg_java_name_upper, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(msg_java_name_upper, fields), + to_string=generate_to_string(msg_java_name_upper, fields)) + +_EVENT_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents event DTO. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $class_name { +$fields +$hash_code +$equals +$to_string +} +""") + + +def _generate_dump_reply_wrappers(work_dir, model, logger): + for msg in model.messages: + if is_details(msg): + logger.debug("Generating ReplyDump DTO for message %s", msg) + details_class = msg.java_name_upper + dto = _REPLY_DUMP_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + json_definition=msg.doc, + details_class=details_class, + details_field=msg.java_name_lower, + dump_class=msg.request_java + ) + with open("%s/%sReplyDump.java" % (work_dir, details_class), "w") as f: + f.write(dto) + +_REPLY_DUMP_TEMPLATE = Template(""" +package $plugin_package.dto; + +/** + * <p>This class represents dump reply wrapper. + * <br>It was generated by jvpp_dto_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class ${details_class}ReplyDump implements io.fd.vpp.jvpp.dto.JVppReplyDump<${plugin_package}.dto.${dump_class}, ${plugin_package}.dto.${details_class}> { + + public java.util.List<${details_class}> ${details_field} = new java.util.ArrayList<>(); + + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash(${details_field}); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + final ${details_class}ReplyDump other = (${details_class}ReplyDump) o; + + if (!java.util.Objects.equals(this.${details_field}, other.${details_field})) { + return false; + } + + return true; + } + + @Override + public java.lang.String toString() { + return "${details_class}ReplyDump{" + + "${details_field}=" + ${details_field} + "}"; + } + + +} +""") diff --git a/java/jvpp/gen/jvppgen/enums_gen.py b/java/jvpp/gen/jvppgen/enums_gen.py new file mode 100755 index 0000000..8ba9655 --- /dev/null +++ b/java/jvpp/gen/jvppgen/enums_gen.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +from string import Template + +from jvpp_model import Enum + + +def generate_enums(work_dir, model, logger): + logger.debug("Generating enums for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Enum): + continue + logger.debug("Generating DTO for enum %s", t) + type_class_name = t.java_name + type_class = _ENUM_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_enum_name=type_class_name, + constants=_generate_constants(t.constants), + value_type=t.value.type.java_name + ) + with open("%s/%s.java" % (work_dir, type_class_name), "w") as f: + f.write(type_class) + +_ENUM_TEMPLATE = Template(""" +package $plugin_package.types; + +/** + * <p>This class represents $c_type_name enum definition. + * <br>It was generated by enums_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public enum $java_enum_name { +$constants; + + public final $value_type value; + + $java_enum_name(final $value_type value) { + this.value = value; + } + + public static $java_enum_name forValue(final $value_type value) { + for ($java_enum_name enumeration : $java_enum_name.values()) { + if (value == enumeration.value) { + return enumeration; + } + } + return null; + } +} +""") + + +def _generate_constants(constants): + return ",\n".join(_CONSTANT_TEMPLATE.substitute(name=c['name'], value=c['value']) for c in constants) + +_CONSTANT_TEMPLATE = Template(""" $name($value)""") diff --git a/java/jvpp/gen/jvppgen/jni_common_gen.py b/java/jvpp/gen/jvppgen/jni_common_gen.py new file mode 100755 index 0000000..b52e5ff --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_common_gen.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +# +from string import Template + +from jvpp_model import is_array, is_retval, Class, Enum, Union + + +def generate_j2c_identifiers(element, class_ref_name, object_ref_name): + identifiers = [] + for field in element.fields: + field_type = field.type + identifiers.append(_REQUEST_FIELD_IDENTIFIER_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=class_ref_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name + )) + return "".join(identifiers) + +_REQUEST_FIELD_IDENTIFIER_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}, "${java_name}", "${jni_signature}"); + ${jni_type} ${java_name} = (*env)->Get${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId); +""") + + +# TODO(VPP-1187): do not inline JNI object creation inside message handlers to reduce number of special cases +def generate_j2c_swap(element, struct_ref_name, is_alias): + initialization = [] + for field in element.fields: + initialization.append(generate_j2c_field_swap(field, struct_ref_name, is_alias)) + return "\n".join(initialization) + + +def generate_j2c_field_swap(field, struct_ref_name, is_alias): + if is_array(field): + return _generate_j2c_array_swap(field, struct_ref_name, is_alias) + else: + return _generate_j2c_scalar_swap(field, struct_ref_name, is_alias) + + +def _generate_j2c_array_swap(field, struct_ref_name, is_alias): + # TODO(VPP-1186): move the logic to JNI generators + base_type = field.type.base_type + if isinstance(base_type, (Class, Enum, Union)): + return _generate_j2c_object_array_swap(field, struct_ref_name) + elif base_type.is_swap_needed: + return _generate_j2c_primitive_type_array_swap(field, struct_ref_name) + else: + return _generate_j2c_primitive_type_array_no_swap(field, struct_ref_name, is_alias) + + +def _generate_j2c_object_array_swap(field, struct_ref_name): + field_type = field.type + field_reference_name = field.java_name + c_name = field.name + host = "%sArrayElement" % field_reference_name + net = "%s->%s[_i]" % (struct_ref_name, c_name) + swap_elements = field_type.get_host_to_net_function(host, net) + return _J2C_OBJECT_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field_reference_name, + field_length_check=_generate_field_length_check(field), + swap_elements=swap_elements) + +_J2C_OBJECT_ARRAY_SWAP_TEMPLATE = Template(""" + { + if (${field_reference_name}) { + size_t _i; + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->GetObjectArrayElement(env, ${field_reference_name}, _i); + ${swap_elements}; + } + } + } +""") + + +def _generate_j2c_primitive_type_array_swap(field, struct_ref_name): + field_reference_name = field.java_name + field_type = field.type + host = "%sArrayElements[_i]" % field_reference_name + net = "%s->%s[_i]" % (struct_ref_name, field.name) + swap_elements = field_type.get_host_to_net_function(host, net) + return _J2C_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field_reference_name, + field_length_check=_generate_field_length_check(field), + base_type=field_type.base_type.jni_accessor, + jni_base_type=field_type.base_type.jni_type, + swap_elements=swap_elements + ) + +_J2C_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + ${jni_base_type} * ${field_reference_name}ArrayElements = (*env)->Get${base_type}ArrayElements(env, ${field_reference_name}, NULL); + size_t _i; + jsize cnt = (*env)->GetArrayLength(env, ${field_reference_name}); + ${field_length_check} + for (_i = 0; _i < cnt; _i++) { + ${swap_elements}; + } + (*env)->Release${base_type}ArrayElements (env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + } + """) + + +def _generate_j2c_primitive_type_array_no_swap(field, struct_ref_name, is_alias): + field_type = field.type + if not is_alias: + template = _J2C_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + else: + template = _J2C_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + + return template.substitute( + field_reference_name=field.java_name, + field_length_check=_generate_field_length_check(field), + base_type=field_type.base_type.jni_accessor, + jni_base_type=field_type.base_type.jni_type, + struct_reference_name=struct_ref_name, + c_name=field.name + ) + +_J2C_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + (*env)->Get${base_type}ArrayRegion(env, ${field_reference_name}, 0, cnt, (${jni_base_type} *)${struct_reference_name}->${c_name}); + } +""") + + +_J2C_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + if (${field_reference_name}) { + jsize cnt = (*env)->GetArrayLength (env, ${field_reference_name}); + ${field_length_check} + (*env)->Get${base_type}ArrayRegion(env, ${field_reference_name}, 0, cnt, (${jni_base_type} *)${struct_reference_name}); + } +""") + + +def _generate_field_length_check(field): + # Enforce max length if array has fixed length or uses variable length syntax + field_length = str(field.array_len) + if field.array_len_field: + field_length = field.array_len_field.java_name + + # TODO: remove when ZLAs without length field are disabled + if field_length != "0": + return _FIELD_LENGTH_CHECK.substitute(field_length=field_length) + else: + return "" + + +# Make sure we do not write more elements that are expected +_FIELD_LENGTH_CHECK = Template(""" + size_t max_size = ${field_length}; + if (cnt > max_size) cnt = max_size;""") + + +def _generate_j2c_scalar_swap(field, struct_ref_name, is_alias): + field_type = field.type + if field_type.is_swap_needed: + host = field.java_name + if not is_alias: + net = "%s->%s" % (struct_ref_name, field.name) + if field_type.name == "string": + net = "%s->%s" % (struct_ref_name, field.name) + return " _host_to_net_%s(env, %s, (vl_api_string_t *) &%s);" % (field_type.name, host, net) + else: + return " %s;" % field_type.get_host_to_net_function(host, net) + else: + net = "%s" % struct_ref_name + return " *%s;" % field_type.get_host_to_net_function(host, net) + else: + return " %s->%s = %s;" % (struct_ref_name, field.name, field.java_name) + + +def generate_c2j_swap(element, object_ref_name, struct_ref_name, is_alias): + msg_java_name = element.java_name_lower + initialization = [] + for field in element.fields: + if is_retval(field): + # For retval don't generate setters and generate retval check + continue + elif is_array(field): + initialization.append(_generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias)) + else: + initialization.append(_generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias)) + return "".join(initialization) + + +def _generate_c2j_array_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + # TODO(VPP-1186): move the logic to JNI generators + base_type = field.type.base_type + if isinstance(base_type, (Class, Union)): + return _generate_c2j_object_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif isinstance(base_type, Enum): + return _generate_c2j_enum_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif base_type.is_swap_needed: + return _generate_c2j_primitive_type_array_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias) + + +def _generate_c2j_object_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_OBJECT_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=field_type.base_type.jni_name, + field_length=_generate_array_length(field, struct_ref_name), + net_to_host_function=field_type.net_to_host_function, + struct_ref_name=struct_ref_name, + object_ref_name=object_ref_name, + c_name=field.name + ) + +_C2J_OBJECT_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${jni_name}"); + jobjectArray ${field_reference_name} = (*env)->NewObjectArray(env, ${field_length}, ${field_reference_name}Class, 0); + jmethodID ${field_reference_name}Constructor = (*env)->GetMethodID(env, ${field_reference_name}Class, "<init>", "()V"); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->NewObject(env, ${field_reference_name}Class, ${field_reference_name}Constructor); + ${net_to_host_function}(env, &(${struct_ref_name}->${c_name}[_i]), ${field_reference_name}ArrayElement); + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_enum_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + base_type = field_type.base_type + return _C2J_ENUM_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=base_type.jni_name, + field_length=_generate_array_length(field, struct_ref_name), + net_to_host_function=field_type.net_to_host_function, + jni_signature_enum_value=base_type.value.type.jni_signature, + struct_ref_name=struct_ref_name, + object_ref_name=object_ref_name, + c_name=field.name + ) + +_C2J_ENUM_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + jclass ${field_reference_name}Class = (*env)->FindClass(env, "${jni_name}"); + jobjectArray ${field_reference_name} = (*env)->NewObjectArray(env, ${field_length}, ${field_reference_name}Class, 0); + jmethodID ${field_reference_name}Constructor = (*env)->GetStaticMethodID(env, ${field_reference_name}Class, "forValue", "(${jni_signature_enum_value})${jni_signature}"); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + jobject ${field_reference_name}ArrayElement = (*env)->CallStaticObjectMethod(env, ${field_reference_name}Class, ${field_reference_name}Constructor, ${net_to_host_function}(${struct_ref_name}->${c_name}[_i])); + (*env)->SetObjectArrayElement(env, ${field_reference_name}, _i, ${field_reference_name}ArrayElement); + (*env)->DeleteLocalRef(env, ${field_reference_name}ArrayElement); + } + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_primitive_type_array_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + base_type=field_type.base_type.jni_accessor, + field_length=_generate_array_length(field, struct_ref_name), + jni_base_type=field_type.base_type.jni_type, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_ARRAY_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + { + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + ${jni_base_type} * ${field_reference_name}ArrayElements = (*env)->Get${base_type}ArrayElements(env, ${field_reference_name}, NULL); + unsigned int _i; + for (_i = 0; _i < ${field_length}; _i++) { + ${field_reference_name}ArrayElements[_i] = ${net_to_host_function}(${struct_ref_name}->${c_name}[_i]); + } + + (*env)->Release${base_type}ArrayElements(env, ${field_reference_name}, ${field_reference_name}ArrayElements, 0); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); + } +""") + + +def _generate_c2j_primitive_type_array_no_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + field_type = field.type + if not is_alias: + template = _C2J_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + else: + template = _C2J_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE + return template.substitute( + field_reference_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + base_type=field_type.base_type.jni_accessor, + field_length=_generate_array_length(field, struct_ref_name), + jni_base_type=field_type.base_type.jni_type, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + (*env)->Set${base_type}ArrayRegion(env, ${field_reference_name}, 0, ${field_length}, (const ${jni_base_type}*)${struct_ref_name}->${c_name}); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); +""") + + +_C2J_ALIAS_PRIMITIVE_TYPE_ARRAY_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${field_reference_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${field_reference_name}", "${jni_signature}"); + ${jni_type} ${field_reference_name} = (*env)->New${base_type}Array(env, ${field_length}); + (*env)->Set${base_type}ArrayRegion(env, ${field_reference_name}, 0, ${field_length}, (const ${jni_base_type}*)${struct_ref_name}); + (*env)->SetObjectField(env, ${object_ref_name}, ${field_reference_name}FieldId, ${field_reference_name}); + (*env)->DeleteLocalRef(env, ${field_reference_name}); +""") + + +def _generate_array_length(field, struct_ref_name): + if field.array_len_field: + len_field = field.array_len_field + if len_field.type.is_swap_needed: + return "%s(%s->%s)" % (len_field.type.host_to_net_function, struct_ref_name, len_field.name) + else: + return "%s->%s" % (struct_ref_name, len_field.name) + return field.array_len + + +def _generate_c2j_scalar_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + field_type = field.type + if field_type.is_swap_needed: + # TODO(VPP-1186): move the logic to JNI generators + if isinstance(field_type, (Class, Union)): + return _generate_c2j_object_swap(msg_java_name, field, object_ref_name, struct_ref_name) + elif isinstance(field_type, Enum): + return _generate_c2j_enum_swap(msg_java_name, field, object_ref_name, struct_ref_name) + else: + return _generate_c2j_primitive_type_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias) + else: + return _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name) + + +def _generate_c2j_object_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_OBJECT_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_name=field_type.jni_name, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name) + +_C2J_OBJECT_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + jclass ${java_name}Class = (*env)->FindClass(env, "${jni_name}"); + jmethodID ${java_name}Constructor = (*env)->GetMethodID(env, ${java_name}Class, "<init>", "()V"); + jobject ${java_name} = (*env)->NewObject(env, ${java_name}Class, ${java_name}Constructor); + ${net_to_host_function}(env, &(${struct_ref_name}->${c_name}), ${java_name}); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${java_name}); + (*env)->DeleteLocalRef(env, ${java_name}); +""") + + +def _generate_c2j_enum_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_ENUM_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_signature_enum_value=field_type.value.type.jni_signature, + jni_name=field_type.jni_name, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + net_to_host_function=field_type.net_to_host_function, + c_name=field.name) + +_C2J_ENUM_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + jclass ${java_name}Class = (*env)->FindClass(env, "${jni_name}"); + jmethodID ${java_name}Constructor = (*env)->GetStaticMethodID(env, ${java_name}Class, "forValue", "(${jni_signature_enum_value})${jni_signature}"); + jobject ${java_name} = (*env)->CallStaticObjectMethod(env, ${java_name}Class, ${java_name}Constructor, ${net_to_host_function}(${struct_ref_name}->${c_name})); + (*env)->SetObjectField(env, ${object_ref_name}, ${java_name}FieldId, ${java_name}); + (*env)->DeleteLocalRef(env, ${java_name}); +""") + + +def _generate_c2j_primitive_type_swap(msg_java_name, field, object_ref_name, struct_ref_name, is_alias): + field_type = field.type + if not is_alias: + if field_type.name == "string": + template = _C2J_STRING_TYPE_SWAP_TEMPLATE + else: + template = _C2J_PRIMITIVE_TYPE_SWAP_TEMPLATE + else: + template = _C2J_ALIAS_PRIMITIVE_TYPE_SWAP_TEMPLATE + return template.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + net_to_host_function=field_type.net_to_host_function, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(${struct_ref_name}->${c_name})); +""") + + +_C2J_STRING_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(env, (const vl_api_string_t *) &${struct_ref_name}->${c_name})); +""") + + +_C2J_ALIAS_PRIMITIVE_TYPE_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${net_to_host_function}(*${struct_ref_name})); +""") + + +def _generate_c2j_primitive_type_no_swap(msg_java_name, field, object_ref_name, struct_ref_name): + field_type = field.type + return _C2J_PRIMITIVE_TYPE_NO_SWAP_TEMPLATE.substitute( + java_name=field.java_name, + class_ref_name=msg_java_name, + jni_signature=field_type.jni_signature, + jni_accessor=field_type.jni_accessor, + object_ref_name=object_ref_name, + struct_ref_name=struct_ref_name, + c_name=field.name + ) + +_C2J_PRIMITIVE_TYPE_NO_SWAP_TEMPLATE = Template(""" + jfieldID ${java_name}FieldId = (*env)->GetFieldID(env, ${class_ref_name}Class, "${java_name}", "${jni_signature}"); + (*env)->Set${jni_accessor}Field(env, ${object_ref_name}, ${java_name}FieldId, ${struct_ref_name}->${c_name}); +""") diff --git a/java/jvpp/gen/jvppgen/jni_gen.py b/java/jvpp/gen/jvppgen/jni_gen.py new file mode 100755 index 0000000..ad6c261 --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_gen.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +# +from string import Template + +from jni_impl_gen import generate_jni_impl +from jni_msg_handlers_gen import generate_jni_handlers +from jni_type_handlers_gen import generate_type_handlers +from jvpp_model import is_control_ping, is_dump, is_request, is_control_ping_reply + + +def generate_jni(work_dir, model, logger): + logger.debug("Generating jvpp C for %s" % model.json_api_files) + plugin_name = model.plugin_name + messages = model.messages + + with open("%s/jvpp_%s_gen.h" % (work_dir, plugin_name), "w") as f: + f.write(_JVPP_C_TEMPLATE.substitute( + json_filename=model.json_api_files, + class_cache=_generate_class_cache(plugin_name, messages), + api_verification=_generate_api_verification(messages), + type_handlers=generate_type_handlers(model, logger), + jni_implementations=generate_jni_impl(model), + msg_handlers=generate_jni_handlers(model), + handler_registration=_generate_handler_registration(messages))) + +_JVPP_C_TEMPLATE = Template("""/** + * This file contains JNI bindings for jvpp Java API. + * It was generated by jvpp_jni_gen.py based on $json_filename. + */ +$class_cache + +$api_verification + +// Type handlers +$type_handlers + +// JNI bindings +$jni_implementations + +// Message handlers +$msg_handlers + +$handler_registration +""") + + +def _generate_class_cache(plugin_name, messages): + references = [] + for msg in messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + references.append(( + msg.java_name_lower, + 'io/fd/vpp/jvpp/%s/dto/%s' % (plugin_name, msg.java_name_upper) + )) + + references.append(('callbackException', 'io/fd/vpp/jvpp/VppCallbackException')) + + return _CLASS_CACHE_TEMPLATE.substitute( + class_references=_generate_class_references(references), + create_references=_generate_create_references(references), + delete_references=_generate_delete_references(references) + ) + +_CLASS_CACHE_TEMPLATE = Template(""" +// JAVA class reference cache +$class_references + +static int cache_class_references(JNIEnv* env) { +$create_references + return 0; +} + +static void delete_class_references(JNIEnv* env) { +$delete_references +}""") + + +def _generate_class_references(references): + return "\n".join("jclass %sClass;" % r[0] for r in references) + + +def _generate_create_references(references): + items = [] + for r in references: + items.append(_CREATE_GLOBAL_REF_TEMPLATE.substitute( + ref_name=r[0], + fqn_name=r[1] + )) + return "".join(items) + +_CREATE_GLOBAL_REF_TEMPLATE = Template(""" + ${ref_name}Class = (jclass)(*env)->NewGlobalRef(env, (*env)->FindClass(env, "${fqn_name}")); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + return JNI_ERR; + }""") + + +def _generate_delete_references(references): + items = [] + for r in references: + items.append(_DELETE_CLASS_INVOCATION_TEMPLATE.substitute(ref_name=r[0])) + return "".join(items) + +_DELETE_CLASS_INVOCATION_TEMPLATE = Template(""" + if (${ref_name}Class) { + (*env)->DeleteGlobalRef(env, ${ref_name}Class); + }""") + + +def _generate_api_verification(messages): + items = [] + for msg in messages: + items.append("_(%s_%s) \\" % (msg.name, msg.crc)) + return _API_VERIFICATION_TEMPLATE.substitute(messages="\n".join(items)) + +_API_VERIFICATION_TEMPLATE = Template(""" +// List of supported API messages used for verification +#define foreach_supported_api_message \\ +$messages +""") + + +def _generate_handler_registration(messages): + """ + Generates msg handler registration for all messages except for dumps and requests. + :param messages: collection of VPP API messages. + """ + handlers = [] + for msg in messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + name = msg.name + crc = msg.crc + handlers.append("_(%s_%s, %s) \\" % (name, crc, name)) + return _HANDLER_REGISTRATION_TEMPLATE.substitute(handlers="\n".join(handlers)) + +_HANDLER_REGISTRATION_TEMPLATE = Template(""" +// Registration of message handlers in vlib +#define foreach_api_reply_handler \\ +$handlers +""") diff --git a/java/jvpp/gen/jvppgen/jni_impl_gen.py b/java/jvpp/gen/jvppgen/jni_impl_gen.py new file mode 100755 index 0000000..ef1bcbb --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_impl_gen.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +# +from string import Template + +from jni_common_gen import generate_j2c_identifiers, generate_j2c_swap +from jvpp_model import is_dump, is_request, is_control_ping, is_control_ping_reply + + +def generate_jni_impl(model): + """ + Generates JNI bindings for sending dump and request messages. + :param model: meta-model of VPP API used for jVPP generation. + """ + jni_impl = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + continue + arguments = "" + request_class = "" + jni_identifiers = "" + msg_initialization = "" + + if msg.has_fields: + arguments = ", jobject request" + request_class = _REQUEST_CLASS_TEMPLATE.substitute( + plugin_name=model.plugin_name, + java_dto_name=msg.java_name_upper + ) + jni_identifiers = generate_j2c_identifiers(msg, class_ref_name="requestClass", object_ref_name="request") + msg_initialization = generate_j2c_swap(msg, struct_ref_name="mp", is_alias=False) + + jni_impl.append(_JNI_IMPL_TEMPLATE.substitute( + c_name=msg.name, + json_filename=model.json_api_files, + json_definition=msg.doc, + plugin_name=model.plugin_name, + plugin_java_name=model.plugin_java_name, + java_method_name=msg.java_name_lower, + arguments=arguments, + request_class=request_class, + jni_identifiers=jni_identifiers, + msg_size=_generate_msg_size(msg), + crc=msg.crc, + msg_initialization=msg_initialization + )) + return "".join(jni_impl) + + +_JNI_IMPL_TEMPLATE = Template(""" +/** + * JNI binding for sending ${c_name} message. + * Generated based on $json_filename: +$json_definition + */ +JNIEXPORT jint JNICALL Java_io_fd_vpp_jvpp_${plugin_name}_JVpp${plugin_java_name}Impl_${java_method_name}0 +(JNIEnv * env, jclass clazz${arguments}) { + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + vl_api_${c_name}_t * mp; + u32 my_context_id = vppjni_get_context_id (&jvpp_main); +$request_class +$jni_identifiers + + // create message: + const size_t _size = ${msg_size}; + mp = vl_msg_api_alloc(_size); + memset (mp, 0, _size); + mp->_vl_msg_id = ntohs (get_message_id(env, "${c_name}_${crc}")); + mp->client_index = plugin_main->my_client_index; + mp->context = clib_host_to_net_u32 (my_context_id); + +$msg_initialization + + // send message: + if (CLIB_DEBUG > 1) + clib_warning ("Sending ${c_name} message"); + vl_msg_api_send_shmem (plugin_main->vl_input_queue, (u8 *)&mp); + if ((*env)->ExceptionCheck(env)) { + return JNI_ERR; + } + return my_context_id; +}""") + +# TODO: cache method and field identifiers to achieve better performance +# https://jira.fd.io/browse/HONEYCOMB-42 +_REQUEST_CLASS_TEMPLATE = Template(""" jclass requestClass = (*env)->FindClass(env, "io/fd/vpp/jvpp/${plugin_name}/dto/${java_dto_name}"); +""") + + +def _generate_msg_size(msg): + msg_size = "sizeof(*mp)" + _size_components = [] + for field in msg.fields: + # Ignore ZLAs for simplicity (to support them we need to call JNI functions to check actual size) + if field.array_len_field: + _size_components += " + %s*sizeof(%s)" % (field.array_len_field.java_name, field.type.base_type.vpp_name) + # FIXME(VPP-586): for proper nested structures support, we need generate functions computing type sizes + # and use it instead of sizeof + if field.type.name == "string": + _size_components += " + 1 + jstr_length(env, %s) * sizeof(u8)" % field.name + return msg_size + "".join(_size_components) diff --git a/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py b/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py new file mode 100755 index 0000000..ccd3dbc --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_msg_handlers_gen.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +# +from string import Template + +from jni_common_gen import generate_c2j_swap +from jvpp_model import is_dump, is_request, is_control_ping, is_control_ping_reply, is_retval + + +def generate_jni_handlers(model): + """ + Generates msg handlers for all messages except for dumps and requests (handled by vpp, not client). + :param model: meta-model of VPP API used for jVPP generation. + """ + jni_impl = [] + for msg in model.messages: + msg_name = msg.name + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + jni_impl.append(_MSG_HANDLER_TEMPLATE.substitute( + c_name=msg_name, + json_filename=model.json_api_files, + json_definition=msg.doc, + plugin_name=model.plugin_name, + err_handler=_generate_error_handler(msg), + class_ref_name=msg.java_name_lower, + dto_name=msg.java_name_upper, + dto_setters=generate_c2j_swap(msg, object_ref_name="dto", struct_ref_name="mp", is_alias=False) + )) + return "".join(jni_impl) + + +_MSG_HANDLER_TEMPLATE = Template(""" +/** + * Handler for ${c_name} message. + * Generated based on $json_filename: +$json_definition + */ +static void vl_api_${c_name}_t_handler (vl_api_${c_name}_t * mp) +{ + ${plugin_name}_main_t *plugin_main = &${plugin_name}_main; + JNIEnv *env = jvpp_main.jenv; + jthrowable exc; +$err_handler + + if (CLIB_DEBUG > 1) + clib_warning ("Received ${c_name} event message"); + + jmethodID constructor = (*env)->GetMethodID(env, ${class_ref_name}Class, "<init>", "()V"); + + // User does not have to provide callbacks for all VPP messages. + // We are ignoring messages that are not supported by user. + (*env)->ExceptionClear(env); // just in case exception occurred in different place and was not properly cleared + jmethodID callbackMethod = (*env)->GetMethodID(env, plugin_main->callbackClass, "on${dto_name}", "(Lio/fd/vpp/jvpp/${plugin_name}/dto/${dto_name};)V"); + exc = (*env)->ExceptionOccurred(env); + if (exc) { + clib_warning("Unable to extract on${dto_name} method reference from ${plugin_name} plugin's callbackClass. Ignoring message.\\n"); + (*env)->ExceptionDescribe(env); + (*env)->ExceptionClear(env); + return; + } + + jobject dto = (*env)->NewObject(env, ${class_ref_name}Class, constructor); +$dto_setters + + (*env)->CallVoidMethod(env, plugin_main->callbackObject, callbackMethod, dto); + // free DTO as per http://stackoverflow.com/questions/1340938/memory-leak-when-calling-java-code-from-c-using-jni + (*env)->DeleteLocalRef(env, dto); +}""") + + +def _generate_error_handler(msg): + err_handler = "" + for field in msg.fields: + if is_retval(field): + err_handler = _ERR_HANDLER_TEMPLATE.substitute(name=msg.name) + return err_handler + + +# Code fragment for checking result of the operation before sending request reply. +# Error checking is optional (some messages, e.g. detail messages do not have retval field). +_ERR_HANDLER_TEMPLATE = Template(""" + // for negative result don't send callback message but send error callback + if (mp->retval<0) { + call_on_error("${name}", mp->context, mp->retval, plugin_main->callbackClass, plugin_main->callbackObject, callbackExceptionClass); + return; + } + if (mp->retval == VNET_API_ERROR_IN_PROGRESS) { + clib_warning("Result in progress"); + return; + }""") diff --git a/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py b/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py new file mode 100755 index 0000000..05c3e52 --- /dev/null +++ b/java/jvpp/gen/jvppgen/jni_type_handlers_gen.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +# +from string import Template + +from jni_common_gen import generate_j2c_swap, generate_j2c_field_swap, generate_j2c_identifiers, generate_c2j_swap +from jvpp_model import Class, Enum, Union + + +def generate_type_handlers(model, logger): + """ + Generates host-to-net and net-to-host functions for all custom types defined in the VPP API + :param model: meta-model of VPP API used for jVPP generation. + :param logger: jVPP logger + """ + type_handlers = [] + for t in model.types: + #TODO(VPP-1186): move the logic to JNI generators + if isinstance(t, Class): + _generate_class(model, t, type_handlers) + elif isinstance(t, Enum): + _generate_enum(model, t, type_handlers) + elif isinstance(t, Union): + _generate_union(model, t, type_handlers) + else: + logger.debug("Skipping custom JNI type handler generation for %s", t) + + return "\n".join(type_handlers) + + +def _generate_class(model, t, type_handlers): + ref_name = t.java_name_lower + is_alias = t.name in model._aliases + + type_handlers.append(_TYPE_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=ref_name, + class_FQN=t.jni_name, + jni_identifiers=generate_j2c_identifiers(t, class_ref_name="%sClass" % ref_name, object_ref_name="_host"), + type_swap=generate_j2c_swap(t, struct_ref_name="_net", is_alias=is_alias) + )) + type_handlers.append(_TYPE_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=ref_name, + class_FQN=t.jni_name, + type_swap=generate_c2j_swap(t, object_ref_name="_host", struct_ref_name="_net", is_alias=is_alias) + )) + + +_TYPE_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$jni_identifiers +$type_swap +}""") + +_TYPE_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline void _net_to_host_${c_name}(JNIEnv * env, vl_api_${c_name}_t * _net, jobject _host) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$type_swap +}""") + + +def _generate_enum(model, t, type_handlers): + value_type = t.value.type + type_handlers.append(_ENUM_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + jni_signature=value_type.jni_signature, + jni_type=value_type.jni_type, + jni_accessor=value_type.jni_accessor, + swap=_generate_scalar_host_to_net_swap(t.value) + )) + + type_handlers.append(_ENUM_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + jni_type=value_type.jni_type, + type_swap=_generate_scalar_net_to_host_swap(t.value) + )) + + +_ENUM_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} enum. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass enumClass = (*env)->FindClass(env, "${class_FQN}"); + jmethodID getValueMethod = (*env)->GetMethodID(env, enumClass, "ordinal", "()I"); + ${jni_type} value = (*env)->CallIntMethod(env, _host, getValueMethod); + ${swap}; +}""") + +_ENUM_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} type. + * Generated based on $json_filename: +$json_definition + */ +static inline ${jni_type} _net_to_host_${c_name}(vl_api_${c_name}_t _net) +{ + return (${jni_type}) $type_swap +}""") + + +def _generate_scalar_host_to_net_swap(field): + field_type = field.type + if field_type.is_swap_needed: + return field_type.get_host_to_net_function(field.java_name, "*_net") + else: + return "*_net = %s" % field.java_name + + +def _generate_scalar_net_to_host_swap(field): + field_type = field.type + if field_type.is_swap_needed: + return "%s((%s) _net);" % (field_type.net_to_host_function, field_type.name) + else: + return "_net" + + +def _generate_union(model, t, type_handlers): + type_handlers.append(_generate_union_host_to_net(model, t)) + type_handlers.append(_generate_union_net_to_host(model, t)) + + +def _generate_union_host_to_net(model, t): + swap = [] + for i, field in enumerate(t.fields): + field_type = field.type + is_alias = t.name in model._aliases + swap.append(_UNION_FIELD_HOST_TO_NET_TEMPLATE.substitute( + field_index=i, + java_name=field.java_name, + jni_signature=field_type.jni_signature, + jni_type=field_type.jni_type, + jni_accessor=field_type.jni_accessor, + swap=generate_j2c_field_swap(field, struct_ref_name="_net", is_alias=is_alias) + )) + + return _UNION_HOST_TO_NET_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + class_FQN=t.jni_name, + swap="".join(swap) + ) + + +_UNION_FIELD_HOST_TO_NET_TEMPLATE = Template(""" + if (_activeMember == ${field_index}) { + jfieldID fieldId = (*env)->GetFieldID(env, _class, "${java_name}", "${jni_signature}"); + ${jni_type} ${java_name} = (*env)->Get${jni_accessor}Field(env, _host, fieldId); + ${swap} + }""") + +_UNION_HOST_TO_NET_TEMPLATE = Template(""" +/** + * Host to network byte order conversion for ${c_name} union. + * Generated based on $json_filename: +$json_definition + */ +static inline void _host_to_net_${c_name}(JNIEnv * env, jobject _host, vl_api_${c_name}_t * _net) +{ + jclass _class = (*env)->FindClass(env, "${class_FQN}"); + + jfieldID _activeMemberFieldId = (*env)->GetFieldID(env, _class, "_activeMember", "I"); + jint _activeMember = (*env)->GetIntField(env, _host, _activeMemberFieldId); +$swap +}""") + + +def _generate_union_net_to_host(model, t): + is_alias = t.name in model._aliases + return _UNION_NET_TO_HOST_TEMPLATE.substitute( + c_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + type_reference_name=t.java_name_lower, + class_FQN=t.jni_name, + swap=generate_c2j_swap(t, object_ref_name="_host", struct_ref_name="_net", is_alias=is_alias) + ) + + +_UNION_NET_TO_HOST_TEMPLATE = Template(""" +/** + * Network to host byte order conversion for ${c_name} union. + * Generated based on $json_filename: +$json_definition + */ +static inline void _net_to_host_${c_name}(JNIEnv * env, vl_api_${c_name}_t * _net, jobject _host) +{ + jclass ${type_reference_name}Class = (*env)->FindClass(env, "${class_FQN}"); +$swap +}""") diff --git a/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py b/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py new file mode 100644 index 0000000..ebc552b --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_callback_facade_gen.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +from string import Template + +from jvpp_model import is_control_ping, is_dump, is_request, is_event, is_control_ping_reply + + +def generate_callback_facade(work_dir, model, logger): + """ Generates callback facade """ + logger.debug("Generating JVpp callback facade for %s" % model.json_api_files) + _generate_ifc(work_dir, model), + _generate_impl(work_dir, model) + _generate_callback(work_dir, model) + + +def _generate_ifc(work_dir, model): + with open("%s/CallbackJVpp%s.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_IFC_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_ifc_methods(model) + )) + +_IFC_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Callback Java API representation of $plugin_package plugin. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public interface CallbackJVpp${plugin_name} extends io.fd.vpp.jvpp.notification.EventRegistryProvider, java.lang.AutoCloseable { + + // TODO add send + +$methods +} +""") + + +def _generate_ifc_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_control_ping(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + # Skip replies and messages that do not not have replies (e.g events/counters). + continue + template = _IFC_NO_ARG_METHOD_TEMPLATE + if msg.has_fields: + template = _IFC_METHOD_TEMPLATE + methods.append(template.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + request=msg.java_name_upper, + reply=msg.reply_java + )) + return "\n".join(methods) + +_IFC_NO_ARG_METHOD_TEMPLATE = Template( + """ void $name($plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException;""") + +_IFC_METHOD_TEMPLATE = Template( + """ void $name($plugin_package.dto.$request request, $plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException;""") + + +def _generate_impl(work_dir, model): + with open("%s/CallbackJVpp%sFacade.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_IMPL_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_impl_methods(model) + )) + +_IMPL_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Default implementation of Callback${plugin_name}JVpp interface. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public final class CallbackJVpp${plugin_name}Facade implements CallbackJVpp${plugin_name} { + + private final $plugin_package.JVpp${plugin_name} jvpp; + private final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> callbacks; + private final $plugin_package.notification.${plugin_name}EventRegistryImpl eventRegistry = new $plugin_package.notification.${plugin_name}EventRegistryImpl(); + /** + * <p>Create CallbackJVpp${plugin_name}Facade object for provided JVpp instance. + * Constructor internally creates CallbackJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided io.fd.vpp.jvpp.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public CallbackJVpp${plugin_name}Facade(final io.fd.vpp.jvpp.JVppRegistry registry, final $plugin_package.JVpp${plugin_name} jvpp) throws java.io.IOException { + this.jvpp = java.util.Objects.requireNonNull(jvpp,"jvpp is null"); + this.callbacks = new java.util.HashMap<>(); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new CallbackJVpp${plugin_name}FacadeCallback(this.callbacks, eventRegistry)); + } + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry() { + return eventRegistry; + } + + @Override + public void close() throws Exception { + jvpp.close(); + } + + // TODO add send() + +$methods +} +""") + + +def _generate_impl_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_control_ping(msg): + # Skip control ping managed by jvpp registry. + continue + if not (is_dump(msg) or is_request(msg)): + # Skip replies and messages that do not not have replies (e.g events/counters). + continue + template = _IMPL_NO_ARG_METHOD_TEMPLATE + if msg.has_fields: + template = _IMPL_METHOD_TEMPLATE + methods.append(template.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + request=msg.java_name_upper, + reply=msg.reply_java + )) + return "\n".join(methods) + +_IMPL_NO_ARG_METHOD_TEMPLATE = Template( + """ public final void $name($plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(), callback); + } + } +""") + +_IMPL_METHOD_TEMPLATE = Template(""" public final void $name($plugin_package.dto.$request request, $plugin_package.callback.${reply}Callback callback) throws io.fd.vpp.jvpp.VppInvocationException { + synchronized (callbacks) { + callbacks.put(jvpp.$name(request), callback); + } + } +""") + + +def _generate_callback(work_dir, model): + with open("%s/CallbackJVpp%sFacadeCallback.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_CALLBACK_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_callback_methods(model) + )) + +_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.callfacade; + +/** + * <p>Implementation of JVppGlobalCallback interface for Java Callback API. + * <br>It was generated by jvpp_callback_facade_gen.py based on $json_filename. + */ +public final class CallbackJVpp${plugin_name}FacadeCallback implements $plugin_package.callback.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> requests; + private final $plugin_package.notification.Global${plugin_name}EventCallback eventCallback; + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(CallbackJVpp${plugin_name}FacadeCallback.class.getName()); + + public CallbackJVpp${plugin_name}FacadeCallback(final java.util.Map<Integer, io.fd.vpp.jvpp.callback.JVppCallback> requestMap, + final $plugin_package.notification.Global${plugin_name}EventCallback eventCallback) { + this.requests = requestMap; + this.eventCallback = eventCallback; + } + + @Override + public void onError(io.fd.vpp.jvpp.VppCallbackException reply) { + + io.fd.vpp.jvpp.callback.JVppCallback failedCall; + synchronized(requests) { + failedCall = requests.remove(reply.getCtxId()); + } + + if(failedCall != null) { + try { + failedCall.onError(reply); + } catch(RuntimeException ex) { + ex.addSuppressed(reply); + LOG.log(java.util.logging.Level.WARNING, java.lang.String.format("Callback: %s failed while handling exception: %s", failedCall, reply), ex); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final io.fd.vpp.jvpp.dto.ControlPingReply reply) { + + io.fd.vpp.jvpp.callback.ControlPingCallback callback; + final int replyId = reply.context; + synchronized(requests) { + callback = (io.fd.vpp.jvpp.callback.ControlPingCallback) requests.remove(replyId); + } + + if(callback != null) { + callback.onControlPingReply(reply); + } + } + +$methods +} +""") + + +def _generate_callback_methods(model): + plugin_package = model.plugin_package + methods = [] + for msg in model.messages: + if is_dump(msg) or is_request(msg): + continue + if is_control_ping_reply(msg): + # Skip control ping managed by jvpp registry. + continue + + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + template = _CALLBACK_METHOD_TEMPLATE + if is_event(msg): + template = _CALLBACK_EVENT_METHOD_TEMPLATE + msg_name = msg.java_name_upper + methods.append(template.substitute( + message=msg_name, + callback="%sCallback" % msg_name, + plugin_package=plugin_package + )) + return "\n".join(methods) + +_CALLBACK_METHOD_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on${message}(final $plugin_package.dto.${message} reply) { + + $plugin_package.callback.$callback callback; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received ${message} event message: %s", reply)); + } + synchronized(requests) { + callback = ($plugin_package.callback.$callback) requests.remove(replyId); + } + + if(callback != null) { + callback.on${message}(reply); + } + } +""") + +_CALLBACK_EVENT_METHOD_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on${message}($plugin_package.dto.${message} notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received ${message} event message: %s", notification)); + } + eventCallback.on${message}(notification); + } +""") diff --git a/java/jvpp/gen/jvppgen/jvpp_common_gen.py b/java/jvpp/gen/jvppgen/jvpp_common_gen.py new file mode 100755 index 0000000..499adbc --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_common_gen.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +from string import Template + +from jvpp_model import is_array + + +def generate_fields(fields, access_modifier="public"): + return "\n".join(_FIELD_TEMPLATE + .substitute(access_modifier=access_modifier, type=f.type.java_name_fqn, name=f.java_name) + for f in fields) + +_FIELD_TEMPLATE = Template(""" ${access_modifier} ${type} ${name};""") + + +def generate_hash_code(fields): + if len(fields) == 1 and is_array(fields[0]): + return _HASH_CODE_SINGLE_ARRAY_TEMPLATE.substitute(array_field=fields[0].java_name) + return _HASH_CODE_TEMPLATE.substitute(fields=", ".join(f.java_name for f in fields)) + +_HASH_CODE_TEMPLATE = Template(""" + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Objects.hash($fields); + }""") + +_HASH_CODE_SINGLE_ARRAY_TEMPLATE = Template(""" + @Override + @io.fd.vpp.jvpp.coverity.SuppressFBWarnings("UWF_UNWRITTEN_PUBLIC_OR_PROTECTED_FIELD") + public int hashCode() { + return java.util.Arrays.hashCode($array_field); + }""") + + +def generate_equals(class_name, fields): + comparisons = [] + for f in fields: + if is_array(f): + comparisons.append(_EQUALS_ARRAY_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + else: + comparisons.append(_EQUALS_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + + if comparisons: + comparisons.insert(0, _EQUALS_OTHER_TEMPLATE.substitute(cls_name=class_name)) + return _EQUALS_TEMPLATE.substitute(comparisons="\n".join(comparisons)) + +_EQUALS_TEMPLATE = Template(""" + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } +$comparisons + + return true; + }""") + +_EQUALS_OTHER_TEMPLATE = Template(""" + final $cls_name other = ($cls_name) o; +""") + +_EQUALS_FIELD_TEMPLATE = Template(""" if (!java.util.Objects.equals(this.$field_name, other.$field_name)) { + return false; + }""") + +_EQUALS_ARRAY_FIELD_TEMPLATE = Template(""" if (!java.util.Arrays.equals(this.$field_name, other.$field_name)) { + return false; + }""") + + +def generate_to_string(class_name, fields): + to_string = [] + for f in fields: + if is_array(f): + to_string.append(_TO_STRING_ARRAY_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + else: + to_string.append(_TO_STRING_FIELD_TEMPLATE.substitute(field_name=f.java_name)) + + to_string_fields = " \"}\";" + if to_string: + to_string_fields = " + \", \" +\n".join(to_string) + " + \"}\";" + + return _TO_STRING_TEMPLATE.substitute( + class_name=class_name, + to_string_fields=to_string_fields + ) + +_TO_STRING_TEMPLATE = Template(""" + @Override + public java.lang.String toString() { + return "$class_name{" + +$to_string_fields + }""") + +_TO_STRING_FIELD_TEMPLATE = Template(""" \"$field_name=\" + $field_name""") + +_TO_STRING_ARRAY_FIELD_TEMPLATE = Template( + """ \"$field_name=\" + java.util.Arrays.toString($field_name)""") diff --git a/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py b/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py new file mode 100644 index 0000000..3da367a --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_future_facade_gen.py @@ -0,0 +1,338 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +from string import Template + +from jvpp_model import is_control_ping, is_control_ping_reply, is_dump, is_request, is_details, is_reply, is_event + + +def generate_future_facade(work_dir, model, logger): + logger.debug("Generating JVpp future facade for %s" % model.json_api_files) + _generate_future_jvpp(work_dir, model), + _generate_future_jvpp_facade(work_dir, model) + _generate_future_jvpp_callback(work_dir, model) + + +def _generate_future_jvpp(work_dir, model): + with open("%s/FutureJVpp%s.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_methods(model) + )) + +_FUTURE_JVPP_TEMPLATE = Template(''' +package $plugin_package.future; + +/** + * <p>Async facade extension adding specific methods for each request invocation + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public interface FutureJVpp${plugin_name} extends io.fd.vpp.jvpp.future.FutureJVppInvoker { +$methods + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry(); + +} +''') + + +def _generate_future_jvpp_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + reply_name = None + if is_request(msg): + reply_name = msg.reply_java + elif is_dump(msg): + # use reply dump wrappers + reply_name = "%sReplyDump" % msg.reply_java + else: + continue + + methods.append(_FUTURE_JVPP_METHOD_TEMPLATE.substitute( + plugin_package=model.plugin_package, + method_name=msg.java_name_lower, + reply_name=reply_name, + request_name=msg.java_name_upper + )) + return "".join(methods) + +_FUTURE_JVPP_METHOD_TEMPLATE = Template(''' + java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}> ${method_name}(${plugin_package}.dto.${request_name} request); +''') + + +def _generate_future_jvpp_facade(work_dir, model): + with open("%s/FutureJVpp%sFacade.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_FACADE_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_facade_methods(model) + )) + +_FUTURE_JVPP_FACADE_TEMPLATE = Template(''' +package $plugin_package.future; + +/** + * <p>Implementation of FutureJVpp based on AbstractFutureJVppInvoker + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public class FutureJVpp${plugin_name}Facade extends io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker implements FutureJVpp${plugin_name} { + + private final $plugin_package.notification.${plugin_name}EventRegistryImpl eventRegistry = new $plugin_package.notification.${plugin_name}EventRegistryImpl(); + + /** + * <p>Create FutureJVpp${plugin_name}Facade object for provided JVpp instance. + * Constructor internally creates FutureJVppFacadeCallback class for processing callbacks + * and then connects to provided JVpp instance + * + * @param jvpp provided io.fd.vpp.jvpp.JVpp instance + * + * @throws java.io.IOException in case instance cannot connect to JVPP + */ + public FutureJVpp${plugin_name}Facade(final io.fd.vpp.jvpp.JVppRegistry registry, final io.fd.vpp.jvpp.JVpp jvpp) throws java.io.IOException { + super(jvpp, registry, new java.util.HashMap<>()); + java.util.Objects.requireNonNull(registry, "JVppRegistry should not be null"); + registry.register(jvpp, new FutureJVpp${plugin_name}FacadeCallback(getRequests(), eventRegistry)); + } + + @Override + public $plugin_package.notification.${plugin_name}EventRegistry getEventRegistry() { + return eventRegistry; + } + +$methods +} +''') + + +def _generate_future_jvpp_facade_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + template = None + if is_request(msg): + template = _FUTURE_JVPP_FACADE_REQUEST_TEMPLATE + elif is_dump(msg): + template = _FUTURE_JVPP_FACADE_DUMP_TEMPLATE + else: + continue + + methods.append(template.substitute( + plugin_package=model.plugin_package, + method_name=msg.java_name_lower, + reply_name=msg.reply_java, + request_name=msg.java_name_upper + )) + return "".join(methods) + +_FUTURE_JVPP_FACADE_REQUEST_TEMPLATE = Template(''' + @Override + public java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}> ${method_name}(${plugin_package}.dto.${request_name} request) { + return send(request); + } +''') + +_FUTURE_JVPP_FACADE_DUMP_TEMPLATE = Template(''' + @Override + public java.util.concurrent.CompletionStage<${plugin_package}.dto.${reply_name}ReplyDump> ${method_name}(${plugin_package}.dto.${request_name} request) { + return send(request, new ${plugin_package}.dto.${reply_name}ReplyDump()); + } +''') + + +def _generate_future_jvpp_callback(work_dir, model): + with open("%s/FutureJVpp%sFacadeCallback.java" % (work_dir, model.plugin_java_name), "w") as f: + f.write(_FUTURE_JVPP_CALLBACK_TEMPLATE.substitute( + plugin_package=model.plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + methods=_generate_future_jvpp_callback_methods(model) + )) + +_FUTURE_JVPP_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.future; + +/** + * <p>Async facade callback setting values to future objects + * <br>It was generated by jvpp_future_facade_gen.py based on $json_filename. + */ +public final class FutureJVpp${plugin_name}FacadeCallback implements $plugin_package.callback.JVpp${plugin_name}GlobalCallback { + + private final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends io.fd.vpp.jvpp.dto.JVppReply<?>>> requests; + private final $plugin_package.notification.Global${plugin_name}EventCallback notificationCallback; + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(FutureJVpp${plugin_name}FacadeCallback.class.getName()); + + public FutureJVpp${plugin_name}FacadeCallback( + final java.util.Map<java.lang.Integer, java.util.concurrent.CompletableFuture<? extends io.fd.vpp.jvpp.dto.JVppReply<?>>> requestMap, + final $plugin_package.notification.Global${plugin_name}EventCallback notificationCallback) { + this.requests = requestMap; + this.notificationCallback = notificationCallback; + } + + @Override + @SuppressWarnings("unchecked") + public void onError(io.fd.vpp.jvpp.VppCallbackException reply) { + final java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>> completableFuture; + + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>>) requests.get(reply.getCtxId()); + } + + if(completableFuture != null) { + completableFuture.completeExceptionally(reply); + + synchronized(requests) { + requests.remove(reply.getCtxId()); + } + } + } + + @Override + @SuppressWarnings("unchecked") + public void onControlPingReply(final io.fd.vpp.jvpp.dto.ControlPingReply reply) { + java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>> completableFuture; + + final int replyId = reply.context; + synchronized(requests) { + completableFuture = (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<?>>) requests.get(replyId); + + if(completableFuture != null) { + // Finish dump call + if (completableFuture instanceof io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) { + completableFuture.complete(((io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getReplyDump()); + // Remove future mapped to dump call context id + requests.remove(((io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture) completableFuture).getContextId()); + } else { + // reply to regular control ping, complete the future + completableFuture.complete(reply); + } + requests.remove(replyId); + } else { + // future not yet created by writer, create new future, complete it and put to map under ping id + completableFuture = new java.util.concurrent.CompletableFuture<>(); + completableFuture.complete(reply); + requests.put(replyId, completableFuture); + } + } + } + +$methods +} +""") + + +def _generate_future_jvpp_callback_methods(model): + methods = [] + for msg in model.messages: + if is_control_ping(msg) or is_control_ping_reply(msg): + # Skip control_ping managed by jvpp registry. + continue + if is_dump(msg) or is_request(msg): + continue + + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + template = None + request_dto = None + if is_details(msg): + template = _FUTURE_JVPP_FACADE_DETAILS_CALLBACK_TEMPLATE + request_dto = msg.request_java + elif is_reply(msg): + template = _FUTURE_JVPP_FACADE_REPLY_CALLBACK_TEMPLATE + request_dto = msg.request_java + elif is_event(msg): + template = _FUTURE_JVPP_FACADE_EVENT_CALLBACK_TEMPLATE + else: + raise TypeError("Unknown message type %s", msg) + + methods.append(template.substitute( + plugin_package=model.plugin_package, + callback_dto=msg.java_name_upper, + request_dto=request_dto, + callback_dto_field=msg.java_name_lower, + )) + return "".join(methods) + + +_FUTURE_JVPP_FACADE_DETAILS_CALLBACK_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.dto.$callback_dto reply) { + io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.dto.${callback_dto}ReplyDump> completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $callback_dto event message: %s", reply)); + } + synchronized(requests) { + completableFuture = (io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<$plugin_package.dto.${callback_dto}ReplyDump>) requests.get(replyId); + + if(completableFuture == null) { + // reply received before writer created future, + // create new future, and put into map to notify sender that reply is already received, + // following details replies will add information to this future + completableFuture = new io.fd.vpp.jvpp.future.AbstractFutureJVppInvoker.CompletableDumpFuture<>(replyId, + new $plugin_package.dto.${callback_dto}ReplyDump()); + requests.put(replyId, completableFuture); + } + completableFuture.getReplyDump().$callback_dto_field.add(reply); + } + } +""") + +_FUTURE_JVPP_FACADE_REPLY_CALLBACK_TEMPLATE = Template(""" + @Override + @SuppressWarnings("unchecked") + public void on$callback_dto(final $plugin_package.dto.$callback_dto reply) { + java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_dto>> completableFuture; + final int replyId = reply.context; + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $callback_dto event message: %s", reply)); + } + synchronized(requests) { + completableFuture = + (java.util.concurrent.CompletableFuture<io.fd.vpp.jvpp.dto.JVppReply<$plugin_package.dto.$request_dto>>) requests.get(replyId); + + if(completableFuture != null) { + // received reply on request, complete future created by sender and remove it from map + completableFuture.complete(reply); + requests.remove(replyId); + } else { + // reply received before writer created future, + // create new future, complete it and put into map to + // notify sender that reply is already received + completableFuture = new java.util.concurrent.CompletableFuture<>(); + completableFuture.complete(reply); + requests.put(replyId, completableFuture); + } + } + } +""") + +_FUTURE_JVPP_FACADE_EVENT_CALLBACK_TEMPLATE = Template(""" + @Override + public void on$callback_dto($plugin_package.dto.$callback_dto notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $callback_dto event message: %s", notification)); + } + notificationCallback.on$callback_dto(notification); + } +""") diff --git a/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py b/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py new file mode 100755 index 0000000..e2b2922 --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_ifc_gen.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +# +from string import Template + +from jvpp_model import is_request, is_dump, is_event + + +def generate_java_ifc(work_dir, model, logger): + logger.debug("Generating JVpp interface for %s" % model.json_api_files) + messages = filter(_jvpp_ifc_filter, model.messages) + plugin_package = model.plugin_package + methods = [] + for msg in messages: + if msg.has_fields: + methods.append(_JVPP_IFC_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + type=msg.java_name_upper)) + else: + methods.append(_JVPP_IFC_NO_ARG_METHOD_TEMPLATE.substitute(name=msg.java_name_lower)) + + plugin_name = model.plugin_java_name + jvpp_interface = _JVPP_IFC_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=model.json_api_files, + plugin_name=plugin_name, + methods="\n".join(methods) + ) + with open("%s/JVpp%s.java" % (work_dir, plugin_name), "w") as f: + f.write(jvpp_interface) + + +def _jvpp_ifc_filter(msg): + return is_request(msg) or is_dump(msg) or is_event(msg) + + +_JVPP_IFC_METHOD_TEMPLATE = Template( + """ int $name($plugin_package.dto.$type request) throws io.fd.vpp.jvpp.VppInvocationException;""") + +_JVPP_IFC_NO_ARG_METHOD_TEMPLATE = Template(""" int $name() throws io.fd.vpp.jvpp.VppInvocationException;""") + +_JVPP_IFC_TEMPLATE = Template("""package $plugin_package; + +/** + * <p>Java representation of plugin's api file. + * <br>It was generated by jvpp_impl_gen.py based on $json_filename. + * <br>(python representation of api file generated by vppapigen) + */ +public interface JVpp${plugin_name} extends io.fd.vpp.jvpp.JVpp { + /** + * Generic dispatch method for sending requests to VPP + * + * @throws io.fd.vpp.jvpp.VppInvocationException if send request had failed + */ + int send(io.fd.vpp.jvpp.dto.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException; +$methods +} +""") diff --git a/java/jvpp/gen/jvppgen/jvpp_impl_gen.py b/java/jvpp/gen/jvppgen/jvpp_impl_gen.py new file mode 100755 index 0000000..376952b --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_impl_gen.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +# +from string import Template + +from jvpp_model import is_request, is_dump, is_event + + +def generate_java_impl(work_dir, model, logger): + logger.debug("Generating JVpp implementation for %s" % model.json_api_files) + messages = filter(_jvpp_impl_filter, model.messages) + plugin_package = model.plugin_package + methods = [] + for msg in messages: + if msg.has_fields: + methods.append(_JVPP_IMPL_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + plugin_package=plugin_package, + type=msg.java_name_upper)) + else: + methods.append(_JVPP_IMPL_NO_ARG_METHOD_TEMPLATE.substitute( + name=msg.java_name_lower, + type=msg.java_name_upper)) + + plugin_name = model.plugin_java_name + jvpp_impl = _JVPP_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + json_filename=model.json_api_files, + plugin_name=model.plugin_java_name, + plugin_name_underscore=model.plugin_name, + methods="\n".join(methods)) + + with open("%s/JVpp%sImpl.java" % (work_dir, plugin_name), "w") as f: + f.write(jvpp_impl) + + +def _jvpp_impl_filter(msg): + return is_request(msg) or is_dump(msg) or is_event(msg) + + +_JVPP_IMPL_TEMPLATE = Template("""package $plugin_package; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Set; +import java.util.logging.Logger; +import java.util.logging.Level; +import io.fd.vpp.jvpp.callback.JVppCallback; +import io.fd.vpp.jvpp.VppConnection; +import io.fd.vpp.jvpp.JVppRegistry; + +/** + * <p>Default implementation of JVpp interface. + * <br>It was generated by jvpp_impl_gen.py based on $json_filename. + * <br>(python representation of api file generated by vppapigen) + */ +public final class JVpp${plugin_name}Impl implements $plugin_package.JVpp${plugin_name} { + + private final static Logger LOG = Logger.getLogger(JVpp${plugin_name}Impl.class.getName()); + private static final java.lang.String LIBNAME = "libjvpp_${plugin_name_underscore}.so"; + + // FIXME using NativeLibraryLoader makes load fail could not find (WantInterfaceEventsReply). + static { + try { + loadLibrary(); + } catch (Exception e) { + LOG.severe("Can't find jvpp jni library: " + LIBNAME); + throw new ExceptionInInitializerError(e); + } + } + + private static void loadStream(final InputStream is) throws IOException { + final Set<PosixFilePermission> perms = PosixFilePermissions.fromString("rwxr-x---"); + final Path p = Files.createTempFile(LIBNAME, null, PosixFilePermissions.asFileAttribute(perms)); + try { + Files.copy(is, p, StandardCopyOption.REPLACE_EXISTING); + + try { + Runtime.getRuntime().load(p.toString()); + } catch (UnsatisfiedLinkError e) { + throw new IOException("Failed to load library " + p, e); + } + } finally { + try { + Files.deleteIfExists(p); + } catch (IOException e) { + } + } + } + + private static void loadLibrary() throws IOException { + try (final InputStream is = JVpp${plugin_name}Impl.class.getResourceAsStream('/' + LIBNAME)) { + if (is == null) { + throw new IOException("Failed to open library resource " + LIBNAME); + } + loadStream(is); + } + } + + private VppConnection connection; + private JVppRegistry registry; + + private static native void init0(final JVppCallback callback, final long queueAddress, final int clientIndex); + @Override + public void init(final JVppRegistry registry, final JVppCallback callback, final long queueAddress, final int clientIndex) { + this.registry = java.util.Objects.requireNonNull(registry, "registry should not be null"); + this.connection = java.util.Objects.requireNonNull(registry.getConnection(), "connection should not be null"); + connection.checkActive(); + init0(callback, queueAddress, clientIndex); + } + + private static native void close0(); + @Override + public void close() { + close0(); + } + + @Override + public int send(io.fd.vpp.jvpp.dto.JVppRequest request) throws io.fd.vpp.jvpp.VppInvocationException { + return request.send(this); + } + + @Override + public final int controlPing(final io.fd.vpp.jvpp.dto.ControlPing controlPing) throws io.fd.vpp.jvpp.VppInvocationException { + return registry.controlPing(JVpp${plugin_name}Impl.class); + } +$methods +} +""") + +_JVPP_IMPL_METHOD_TEMPLATE = Template(""" + private static native int ${name}0($plugin_package.dto.$type request); + public final int $name($plugin_package.dto.$type request) throws io.fd.vpp.jvpp.VppInvocationException { + java.util.Objects.requireNonNull(request, "Null request object"); + connection.checkActive(); + if (LOG.isLoggable(Level.FINE)) { + LOG.fine(java.lang.String.format("Sending $type event message: %s", request)); + } + int result=${name}0(request); + if (result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}", result); + } + return result; + }""") + +_JVPP_IMPL_NO_ARG_METHOD_TEMPLATE = Template(""" + private static native int ${name}0() throws io.fd.vpp.jvpp.VppInvocationException; + public final int $name() throws io.fd.vpp.jvpp.VppInvocationException { + connection.checkActive(); + LOG.fine("Sending $type event message"); + int result=${name}0(); + if(result<0){ + throw new io.fd.vpp.jvpp.VppInvocationException("${name}", result); + } + return result; + }""") diff --git a/java/jvpp/gen/jvppgen/jvpp_model.py b/java/jvpp/gen/jvppgen/jvpp_model.py new file mode 100755 index 0000000..9a3204e --- /dev/null +++ b/java/jvpp/gen/jvppgen/jvpp_model.py @@ -0,0 +1,624 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. +# +import json +import pprint +from collections import OrderedDict + +import binascii + +BASE_PACKAGE = "io.fd.vpp.jvpp" + + +class ParseException(Exception): + pass + + +class Type(object): + def __init__(self, name, java_name, java_name_fqn, jni_signature, jni_type, jni_accessor, + host_to_net_function, net_to_host_function): + """ + Initializes Type class. + + :param name: name of type as defined in .api file, e.g. u8, u32[] or mac_entry + :param java_name: corresponding java name, e.g. byte, int[] or MacEntry + :param java_name_fqn: fully qualified java name, e.g. io.fd.vpp.jvpp.core.types.MacEntry + :param jni_signature: JNI Type signature, e.g. B, [I or Lio.fd.vpp.jvpp.core.types.MacEntry; + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#type_signatures + :param jni_type: JNI reference type, e.g. jbyte jintArray, jobject + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#reference_types + :param jni_accessor: Java type do by used in Get<type>Field, Set<type>Field and other functions. + See https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#accessing_fields_of_objects + :param host_to_net_function: name of function host to net byte order swap function + :param net_to_host_function: name of function net to host byte order swap function + """ + self.name = name + self.java_name = java_name + + # Java generation specific properties, TODO(VPP-1186): move to Java specific subclass + self.java_name_fqn = java_name_fqn + + # JNI generation specific properties, TODO(VPP-1186): move to JNI specific subclass + self.jni_signature = jni_signature + + # Native type, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#primitive_types + # and + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/types.html#reference_types + self.jni_type = jni_type + + # Java type do by used in Get<type>Field, Set<type>Field and other functions, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#accessing_fields_of_objects + self.jni_accessor = jni_accessor + + self.host_to_net_function = host_to_net_function + self.net_to_host_function = net_to_host_function + self.is_swap_needed = host_to_net_function and net_to_host_function + + +class SimpleType(Type): + def __init__(self, name, java_name, jni_signature, jni_type, jni_accessor, + host_to_net_function=None, net_to_host_function=None): + super(SimpleType, self).__init__( + name=name, + java_name=java_name, + java_name_fqn=java_name, + jni_signature=jni_signature, + jni_type=jni_type, + jni_accessor=jni_accessor, + host_to_net_function=host_to_net_function, + net_to_host_function=net_to_host_function + ) + self.vpp_name = name + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "%s = %s(%s)" % (net_ref_name, self.host_to_net_function, host_ref_name) + + def __str__(self): + return "SimpleType{name:%s, java_name:%s}" % (self.name, self.java_name) + + +# TODO(VPP-1187): add array host to net functions to reduce number of members and simplify JNI generation +class Array(Type): + def __init__(self, base_type, name=None): + if name is None: + name = base_type.name + _ARRAY_SUFFIX + super(Array, self).__init__( + name=name, + java_name=base_type.java_name + _ARRAY_SUFFIX, + java_name_fqn=base_type.java_name_fqn + _ARRAY_SUFFIX, + jni_signature="[%s" % base_type.jni_signature, + jni_type="%sArray" % base_type.jni_type, + jni_accessor="Object", + host_to_net_function=base_type.host_to_net_function, + net_to_host_function=base_type.net_to_host_function + ) + self.base_type = base_type + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return self.base_type.get_host_to_net_function(host_ref_name, net_ref_name) + + def __str__(self): + return "Array{name:%s, java_name:%s}" % (self.name, self.java_name) + + +class Enum(Type): + def __init__(self, name, value, constants, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Enum, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.value = value + self.constants = constants + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Class(Type): + def __init__(self, name, crc, fields, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Class, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.crc = crc + self.fields = fields + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Union(Type): + def __init__(self, name, crc, fields, definition, plugin_name): + _java_name = _underscore_to_camelcase_upper(name) + + super(Union, self).__init__( + name=name, + java_name=_java_name, + java_name_fqn="io.fd.vpp.jvpp.%s.types.%s" % (plugin_name, _java_name), + jni_signature="Lio/fd/vpp/jvpp/%s/types/%s;" % (plugin_name, _java_name), + jni_type="jobject", + jni_accessor="Object", + host_to_net_function="_host_to_net_%s" % name, + net_to_host_function="_net_to_host_%s" % name + ) + + self.crc = crc + self.fields = fields + self.doc = _message_to_javadoc(definition) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.vpp_name = "%s%s%s" % (_VPP_TYPE_PREFIX, name, _VPP_TYPE_SUFFIX) + # Fully qualified class name used by FindClass function, see: + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#FindClass + self.jni_name = "io/fd/vpp/jvpp/%s/types/%s" % (plugin_name, _java_name) + + def get_host_to_net_function(self, host_ref_name, net_ref_name): + return "_host_to_net_%s(env, %s, &(%s))" % (self.name, host_ref_name, net_ref_name) + + +class Field(object): + def __init__(self, name, field_type, array_len=None, array_len_field=None): + self.name = name + self.java_name = _underscore_to_camelcase_lower(name) + self.java_name_upper = _underscore_to_camelcase_upper(name) + self.type = field_type + self.array_len = array_len + self.array_len_field = array_len_field + + def __str__(self): + return "Field{name:%s, java_name:%s, type:%s}" % (self.name, self.java_name, self.type) + + +class Message(object): + def __init__(self, name, crc, fields, definition): + self.name = name + self.java_name_upper = _underscore_to_camelcase_upper(name) + self.java_name_lower = _underscore_to_camelcase_lower(name) + self.crc = crc[2:] + self.fields = fields + self.has_fields = fields != [] + self.doc = _message_to_javadoc(definition) + + def __str__(self): + return "Message{name:%s, java_name:%s, crc:%s, fields:%s}" % ( + self.name, self.java_name_upper, self.crc, self.fields) + + +class Event(Message): + def __init__(self, name, crc, fields, definition): + super(Event, self).__init__(name, crc, fields, definition) + + +class Request(Message): + def __init__(self, name, reply, crc, fields, definition): + super(Request, self).__init__(name, crc, fields, definition) + self.reply = reply + self.reply_java = _underscore_to_camelcase_upper(reply) + + def __str__(self): + return "Request{name:%s, reply:%s, crc:%s, fields:%s}" % (self.name, self.reply, self.crc, self.fields) + + +class Reply(Message): + def __init__(self, name, request, crc, fields, definition): + super(Reply, self).__init__(name, crc, fields, definition) + self.request = request + self.request_java = _underscore_to_camelcase_upper(request) + + def __str__(self): + return "Reply{name:%s, request:%s, crc:%s, fields:%s}" % (self.name, self.request, self.crc, self.fields) + + +class Dump(Message): + def __init__(self, name, details, crc, fields, definition): + super(Dump, self).__init__(name, crc, fields, definition) + self.details = details + self.reply_java = _underscore_to_camelcase_upper(details) + + def __str__(self): + return "Dump{name:%s, details:%s, crc:%s, fields:%s}" % (self.name, self.details, self.crc, self.fields) + + +class Details(Message): + def __init__(self, name, dump, crc, fields, definition): + super(Details, self).__init__(name, crc, fields, definition) + self.dump = dump + self.request_java = _underscore_to_camelcase_upper(dump) + + def __str__(self): + return "Details{name:%s, dump:%s, crc:%s, fields:%s}" % (self.name, self.dump, self.crc, self.fields) + + +def is_retval(field): + return field.name == u'retval' + + +def is_array(field): + return field.array_len is not None + + +def is_request(msg): + return hasattr(msg, 'reply') + + +def is_reply(msg): + return hasattr(msg, 'request') + + +def is_dump(msg): + return hasattr(msg, 'details') + + +def is_details(msg): + return hasattr(msg, 'dump') + + +def is_event(msg): + return isinstance(msg, Event) + + +def is_control_ping(msg): + return msg.name == u'control_ping' + + +def is_control_ping_reply(msg): + return msg.name == u'control_ping_reply' + + +def crc(block): + s = str(block).encode() + return binascii.crc32(s) & 0xffffffff + + +class JVppModel(object): + def __init__(self, logger, json_api_files, plugin_name): + self.logger = logger + # TODO(VPP-1188): provide json_file_by_definition map to improve javadoc + self.json_api_files = json_api_files + self.plugin_package = BASE_PACKAGE + "." + plugin_name + self.plugin_name = plugin_name + self.plugin_java_name = _underscore_to_camelcase_upper(plugin_name) + self._load_json_files(json_api_files) + self._parse_services() + self._parse_messages() + self._validate_messages() + + def _load_json_files(self, json_api_files): + types = {} + self._messages = [] + self._services = {} + self._aliases = {} + for file_name in json_api_files: + with open(file_name) as f: + j = json.load(f) + types.update({d[0]: {'type': 'enum', 'data': d} for d in j['enums']}) + types.update({d[0]: {'type': 'type', 'data': d} for d in j['types']}) + types.update({d[0]: {'type': 'union', 'data': d} for d in j['unions']}) + self._messages.extend(j['messages']) + self._services.update(j['services']) + self._aliases.update(j['aliases']) + + self._parse_types(types) + + def _parse_aliases(self, types): + + # model aliases + for alias_name in self._aliases: + alias = self._aliases[alias_name] + alias_type = {"type": "type"} + java_name_lower = _underscore_to_camelcase_lower(alias_name) + vpp_type = alias["type"] + crc_value = '0x%08x' % crc(alias_name) + if "length" in alias: + length = alias["length"] + alias_type["data"] = [ + alias_name, + [ + vpp_type, + java_name_lower, + length + ], + { + "crc": crc_value + } + ] + else: + alias_type["data"] = [ + alias_name, + [ + vpp_type, + java_name_lower + ], + { + "crc": crc_value + } + ] + + types[alias_name] = alias_type + + def _parse_types(self, types): + self._parse_simple_types() + self._parse_aliases(types) + i = 0 + while True: + unresolved = {} + for name, value in types.items(): + if name in self._types_by_name: + continue + + type = value['type'] + data = value['data'][1:] + try: + if type == 'enum': + type = self._parse_enum(name, data) + elif type == 'union': + type = self._parse_union(name, data) + elif type == 'type': + type = self._parse_type(name, data) + else: + self.logger.warning("Unsupported type %s. Ignoring...", type) + continue + + self._types_by_name[name] = type + self._types_by_name[name + _ARRAY_SUFFIX] = Array(type) + except ParseException as e: + self.logger.debug("Failed to parse %s type in iteration %s: %s.", name, i, e) + unresolved[name] = value + if len(unresolved) == 0: + break + if i > 3: + raise ParseException('Unresolved type definitions {}' + .format(unresolved)) + types = unresolved + i += 1 + + self.types = self._types_by_name.values() + + def _parse_simple_types(self): + # Mapping according to: + # http://docs.oracle.com/javase/7/do+'[]'cs/technotes/guides/jni/spec/types.html + # and + # https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/functions.html#Get_type_Field_routines + # + # Unsigned types are converted to signed java types that have the same size. + # It is the API user responsibility to interpret them correctly. + + self._types_by_name = OrderedDict({ + 'u8': SimpleType('u8', 'byte', 'B', 'jbyte', 'Byte'), + 'i8': SimpleType('i8', 'byte', 'B', 'jbyte', 'Byte'), + 'u16': SimpleType('u16', 'short', 'S', 'jshort', 'Short', + host_to_net_function='clib_host_to_net_u16', + net_to_host_function='clib_net_to_host_u16'), + 'i16': SimpleType('i16', 'short', 'S', 'jshort', 'Short', + host_to_net_function='clib_host_to_net_i16', + net_to_host_function='clib_net_to_host_i16'), + 'u32': SimpleType('u32', 'int', 'I', 'jint', 'Int', + host_to_net_function='clib_host_to_net_u32', + net_to_host_function='clib_net_to_host_u32'), + 'i32': SimpleType('i32', 'int', 'I', 'jint', 'Int', + host_to_net_function='clib_host_to_net_i32', + net_to_host_function='clib_net_to_host_i32'), + 'u64': SimpleType('u64', 'long', 'J', 'jlong', 'Long', + host_to_net_function='clib_host_to_net_u64', + net_to_host_function='clib_net_to_host_u64'), + 'i64': SimpleType('i64', 'long', 'J', 'jlong', 'Long', + host_to_net_function='clib_host_to_net_i64', + net_to_host_function='clib_net_to_host_i64'), + 'f64': SimpleType('f64', 'double', 'D', 'jdouble', 'Double'), + 'string': SimpleType('string', 'String', 'Ljava/lang/String;', 'jstring', 'Object', + host_to_net_function='_host_to_net_string', + net_to_host_function='_net_to_host_string') + }) + + for n, t in self._types_by_name.items(): + self._types_by_name[n + _ARRAY_SUFFIX] = Array(t) + + def _parse_enum(self, name, definition): + self.logger.debug("Parsing enum %s: %s", name, definition) + constants = [] + type_name = None + for item in definition: + if type(item) is dict and 'enumtype' in item: + type_name = item['enumtype'] + continue + constants.append({'name': item[0], 'value': item[1]}) + if not type_name: + raise ParseException("'enumtype' was not defined for %s" % definition) + return Enum(name, Field('value', self._types_by_name[type_name]), constants, definition, self.plugin_name) + + def _parse_union(self, name, definition): + self.logger.debug("Parsing union %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + return Union(name, crc, fields, definition, self.plugin_name) + + def _parse_type(self, name, definition): + self.logger.debug("Parsing type %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + return Class(name, crc, fields, definition, self.plugin_name) + + def _parse_services(self): + self._dumps_by_details = {} + self._requests_by_reply = {} + for name, service in self._services.iteritems(): + if _is_stream(service): + self._dumps_by_details[service['reply']] = name + else: + self._requests_by_reply[service['reply']] = name + + def _parse_messages(self): + # Preserve ordering from JSON file to make debugging easier. + self._messages_by_name = OrderedDict() + for msg in self._messages: + try: + name = msg[0] + definition = msg[1:] + self._messages_by_name[name] = self._parse_message(name, definition) + except ParseException as e: + self.logger.warning("Failed to parse message %s: %s. Skipping message.", name, e) + + def _parse_message(self, name, definition): + self.logger.debug("Parsing message %s: %s", name, definition) + crc, fields = self._parse_fields(definition) + if name in self._services: + service = self._services[name] + reply = service['reply'] + if _is_stream(service): + return Dump(name, reply, crc, filter(_is_request_field, fields), definition) + if reply: + return Request(name, reply, crc, filter(_is_request_field, fields), definition) + else: + return Event(name, crc, filter(_is_request_field, fields), definition) + elif name in self._requests_by_reply: + return Reply(name, self._requests_by_reply[name], crc, filter(_is_reply_field, fields), definition) + elif name in self._dumps_by_details: + return Details(name, self._dumps_by_details[name], crc, filter(_is_reply_field, fields), definition) + else: + # TODO: some messages like combined_counters are not visible in the services. + # Throw exception instead (requires fixing vppagigen). + return Event(name, crc, filter(_is_request_field, fields), definition) + + def _parse_fields(self, definition): + crc = None + fields = [] + for item in definition: + if type(item) == dict and 'crc' in item: + crc = item['crc'] + else: + fields.append(self._parse_field(item, fields)) + if not crc: + raise ParseException("CRC was not defined for %s" % definition) + return crc, fields + + def _parse_field(self, field, fields): + type_name = _extract_type_name(field[0]) + + if type_name in self._types_by_name: + if len(field) > 2: + # Array field + array_len_field = None + if len(field) == 4: + for f in fields: + if f.name == field[3]: + array_len_field = f + if not array_len_field: + raise ParseException("Could not find field %s declared as length of array %s", + field[3], field[1]) + return Field(field[1], self._types_by_name[type_name + _ARRAY_SUFFIX], field[2], array_len_field) + else: + return Field(field[1], self._types_by_name[type_name]) + else: + raise ParseException("Unknown field type %s" % field) + + def _validate_messages(self): + """ + In case if message A is known to be reply for message B, and message B was not correctly parsed, + remove message A from the set of all messages. + """ + to_be_removed = [] + messages = self._messages_by_name + for name, msg in messages.iteritems(): + if (is_request(msg) and msg.reply not in messages) \ + or (is_reply(msg) and msg.request not in messages) \ + or (is_dump(msg) and msg.details not in messages) \ + or (is_details(msg) and msg.dump not in messages): + to_be_removed.append(name) + + for name in to_be_removed: + del messages[name] + + self.messages = self._messages_by_name.values() + + +_ARRAY_SUFFIX = '[]' + + +def _underscore_to_camelcase_upper(name): + return name.title().replace("_", "") + + +def _underscore_to_camelcase_lower(name): + name = name.title().replace("_", "") + return name[0].lower() + name[1:] + + +def _message_to_javadoc(message_definition): + """ Converts JSON message definition to javadoc """ + formatted_message = pprint.pformat(message_definition, indent=4, width=120, depth=None) + return " * " + formatted_message.replace("\n", "\n * ") + + +def _is_stream(service): + """ + Checks if service represents stream, e.g.: + "ip_address_dump": { + "reply": "ip_address_details", + "stream": true + } + :param service: JSON definition of service + :return: value assigned to "stream" or None + """ + return "stream" in service + + +def _extract_type_name(name): + if name.startswith(_VPP_TYPE_PREFIX) and name.endswith(_VPP_TYPE_SUFFIX): + return name[len(_VPP_TYPE_PREFIX): - len(_VPP_TYPE_SUFFIX)] + return name + +_VPP_TYPE_PREFIX = "vl_api_" + +_VPP_TYPE_SUFFIX = "_t" + + +def _is_request_field(field): + # Skip fields that are hidden to the jvpp user (handled by JNI layer) + return field.name not in {'_vl_msg_id', 'client_index', 'context'} + + +def _is_reply_field(field): + # Skip fields that are hidden to the jvpp user: + # _vl_msg_id is handled at JNI layer, + # Unlike in the request case, context is visible to allow matching replies with requests at Java layer. + return field.name not in {'_vl_msg_id'} diff --git a/java/jvpp/gen/jvppgen/notification_gen.py b/java/jvpp/gen/jvppgen/notification_gen.py new file mode 100644 index 0000000..fa86fe4 --- /dev/null +++ b/java/jvpp/gen/jvppgen/notification_gen.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +from string import Template + +from jvpp_model import is_control_ping, is_control_ping_reply, is_dump, is_request + + +def generate_notifications(work_dir, model, logger): + """ Generates notification registry interface and implementation """ + logger.debug("Generating Notification interfaces and implementation for %s" % model.json_api_files) + messages = filter(_notification_filter, model.messages) + _generate_global_event_callback(work_dir, model, messages) + _generate_event_registry(work_dir, model, messages) + _generate_event_registry_impl(work_dir, model, messages) + _generate_event_registry_provider(work_dir, model) + + +def _notification_filter(msg): + # Generate callbacks for all messages except for dumps and requests (handled by vpp, not client). + # Also skip control ping managed by jvpp registry. + return (not is_control_ping(msg)) and \ + (not is_control_ping_reply(msg)) and \ + (not is_dump(msg)) and \ + (not is_request(msg)) + + +def _generate_event_registry(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + register_callback_methods = [] + for msg in messages: + name = _callback_name(msg) + fqn_name = _fqn_callback_name(plugin_package, name) + # TODO create NotificationListenerRegistration and return that instead of AutoCloseable to better indicate + # that the registration should be closed + register_callback_methods.append(" java.lang.AutoCloseable register%s(%s callback);" % (name, fqn_name)) + + with open("%s/%sEventRegistry.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + register_callback_methods="\n".join(register_callback_methods) + )) + +_EVENT_REGISTRY_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Registry for notification callbacks defined in ${plugin_name}. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public interface ${plugin_name}EventRegistry extends io.fd.vpp.jvpp.notification.EventRegistry { + +$register_callback_methods + + @Override + void close(); +} +""") + + +def _generate_event_registry_impl(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + register_callback_methods = [] + handler_methods = [] + for msg in messages: + notification = msg.java_name_upper + callback = "%sCallback" % notification + register_callback_methods.append(_REGISTER_CALLBACK_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + notification=notification, + callback=callback + )) + handler_methods.append(_HANDLER_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + notification=notification, + callback=callback + )) + + with open("%s/%sEventRegistryImpl.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_IMPL_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + register_callback_methods="".join(register_callback_methods), + handler_methods="".join(handler_methods) + )) + +_REGISTER_CALLBACK_IMPL_TEMPLATE = Template(""" + public java.lang.AutoCloseable register$callback(final $plugin_package.callback.$callback callback){ + if(null != registeredCallbacks.putIfAbsent($plugin_package.dto.$notification.class, callback)){ + throw new IllegalArgumentException("Callback for " + $plugin_package.dto.$notification.class + + "notification already registered"); + } + return () -> registeredCallbacks.remove($plugin_package.dto.$notification.class); + } +""") + +_HANDLER_IMPL_TEMPLATE = Template(""" + @Override + public void on$notification( + final $plugin_package.dto.$notification notification) { + if (LOG.isLoggable(java.util.logging.Level.FINE)) { + LOG.fine(java.lang.String.format("Received $notification event message: %s", notification)); + } + final io.fd.vpp.jvpp.callback.JVppCallback jVppCallback = registeredCallbacks.get($plugin_package.dto.$notification.class); + if (null != jVppCallback) { + (($plugin_package.callback.$callback) registeredCallbacks + .get($plugin_package.dto.$notification.class)) + .on$notification(notification); + } + } +""") + +_EVENT_REGISTRY_IMPL_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Notification registry delegating notification processing to registered callbacks. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public final class ${plugin_name}EventRegistryImpl implements ${plugin_name}EventRegistry, Global${plugin_name}EventCallback { + + // TODO add a special NotificationCallback interface and only allow those to be registered + private final java.util.concurrent.ConcurrentMap<Class<?>, io.fd.vpp.jvpp.callback.JVppCallback> registeredCallbacks = + new java.util.concurrent.ConcurrentHashMap<>(); + private static java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(${plugin_name}EventRegistryImpl.class.getName()); + + $register_callback_methods + $handler_methods + + @Override + public void close() { + registeredCallbacks.clear(); + } + + @Override + public void onError(io.fd.vpp.jvpp.VppCallbackException ex) { + java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(${plugin_name}EventRegistryImpl.class.getName()); + LOG.log(java.util.logging.Level.WARNING, java.lang.String.format("Received onError exception: call=%s, context=%d, retval=%d%n", ex.getMethodName(), + ex.getCtxId(), ex.getErrorCode()), ex); + } +} +""") + + +def _generate_global_event_callback(work_dir, model, messages): + plugin_name = model.plugin_java_name + plugin_package = model.plugin_package + + callbacks = "" + callback_list = [] + for msg in messages: + fqn_name = _fqn_callback_name(plugin_package, _callback_name(msg)) + callback_list.append(fqn_name) + + if callback_list: + callbacks = " extends %s" % ", ".join(callback_list) + + with open("%s/Global%sEventCallback.java" % (work_dir, plugin_name), "w") as f: + f.write(_GLOBAL_EVENT_CALLBACK_TEMPLATE.substitute( + plugin_package=plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files, + callbacks=callbacks + )) + +_GLOBAL_EVENT_CALLBACK_TEMPLATE = Template(""" +package $plugin_package.notification; + +/** + * <p>Aggregated callback interface for notifications only. + * <br>It was generated by notification_gen.py based on $json_filename. + */ +public interface Global${plugin_name}EventCallback$callbacks { + +} +""") + + +def _generate_event_registry_provider(work_dir, model): + plugin_name = model.plugin_java_name + with open("%s/%sEventRegistryProvider.java" % (work_dir, plugin_name), "w") as f: + f.write(_EVENT_REGISTRY_PROVIDER_TEMPLATE.substitute( + plugin_package=model.plugin_package, + plugin_name=plugin_name, + json_filename=model.json_api_files + )) + +_EVENT_REGISTRY_PROVIDER_TEMPLATE = Template(""" +package $plugin_package.notification; + + /** + * Provides ${plugin_name}EventRegistry. + * <br>The file was generated by notification_gen.py based on $json_filename. + */ +public interface ${plugin_name}EventRegistryProvider extends io.fd.vpp.jvpp.notification.EventRegistryProvider { + + @Override + public ${plugin_name}EventRegistry getEventRegistry(); +} +""") + + +def _callback_name(msg): + return "%sCallback" % msg.java_name_upper + + +def _fqn_callback_name(plugin_package, callback_name): + return "%s.callback.%s" % (plugin_package, callback_name) diff --git a/java/jvpp/gen/jvppgen/types_gen.py b/java/jvpp/gen/jvppgen/types_gen.py new file mode 100755 index 0000000..3767b53 --- /dev/null +++ b/java/jvpp/gen/jvppgen/types_gen.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2016,2018 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. +from string import Template + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import Class + + +def generate_types(work_dir, model, logger): + logger.debug("Generating custom types for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Class): + continue + logger.debug("Generating DTO for type %s", t) + type_class_name = t.java_name + fields = t.fields + type_class = _TYPE_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_type_name=type_class_name, + fields=generate_fields(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(type_class_name, fields), + to_string=generate_to_string(type_class_name, fields) + ) + with open("%s/%s.java" % (work_dir, type_class_name), "w") as f: + f.write(type_class) + +_TYPE_TEMPLATE = Template(""" +package $plugin_package.types; + +/** + * <p>This class represents $c_type_name type definition. + * <br>It was generated by jvpp_types_gen.py based on $json_filename: + * <pre> +$json_definition + * </pre> + */ +public final class $java_type_name { +$fields +$hash_code +$equals +$to_string +} +""") diff --git a/java/jvpp/gen/jvppgen/unions_gen.py b/java/jvpp/gen/jvppgen/unions_gen.py new file mode 100755 index 0000000..f67704f --- /dev/null +++ b/java/jvpp/gen/jvppgen/unions_gen.py @@ -0,0 +1,98 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2018 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. + +from string import Template + +from jvpp_common_gen import generate_hash_code, generate_equals, generate_to_string, generate_fields +from jvpp_model import Union + + +def generate_unions(work_dir, model, logger): + logger.debug("Generating unions for %s " % model.json_api_files) + + for t in model.types: + if not isinstance(t, Union): + continue + logger.debug("Generating DTO for union %s", t) + java_union_name = t.java_name + fields = t.fields + type_class = _UNION_TEMPLATE.substitute( + plugin_package=model.plugin_package, + c_type_name=t.name, + json_filename=model.json_api_files, + json_definition=t.doc, + java_union_name=java_union_name, + fields=generate_fields(fields, access_modifier="private"), + constructors=_generate_constructors(java_union_name, fields), + getters=_generate_getters(fields), + hash_code=generate_hash_code(fields), + equals=generate_equals(java_union_name, fields), + to_string=generate_to_string(java_union_name, fields) + ) + with open("%s/%s.java" % (work_dir, java_union_name), "w") as f: + f.write(type_class) + +_UNION_TEMPLATE = Template(""" +package ${plugin_package}.types; + +/** + * <p>This class represents ${c_type_name} union definition. + * <br>It was generated by unions_gen.py based on ${json_filename}: + * <pre> +${json_definition} + * </pre> + */ +public class ${java_union_name} { + private final int _activeMember; +${fields} + private ${java_union_name}() { + // Constructor for JNI usage. All members can be read. + _activeMember = -1; + } +${constructors} +${getters} +${hash_code} +${equals} +${to_string} +} +""") + + +def _generate_constructors(union_name, fields): + return "".join( + _CONSTRUCTOR_TEMPLATE + .substitute(union_name=union_name, + field_type=f.type.java_name_fqn, + field_name=f.java_name, + field_index=i) for i, f in enumerate(fields)) + +_CONSTRUCTOR_TEMPLATE = Template(""" + public ${union_name}(${field_type} ${field_name}) { + this.${field_name} = java.util.Objects.requireNonNull(${field_name}, "${field_name} should not be null"); + _activeMember = $field_index; + }""") + + +def _generate_getters(fields): + return "".join(_GETTER_TEMPLATE.substitute( + type=f.type.java_name_fqn, + getter_name=f.java_name_upper, + field_name=f.java_name + ) for f in fields) + +_GETTER_TEMPLATE = Template(""" + public ${type} get${getter_name}() { + return ${field_name}; + }""") @@ -0,0 +1,63 @@ +#!/bin/bash + +# Copyright (c) 2018 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. + +path=$( cd "$(dirname "${BASH_SOURCE}")" ; pwd -P ) + +cd "$path" + +if [ -f .version ]; then + vstring=$(cat .version) +else + vstring=$(git describe) + if [ $? != 0 ]; then + exit 1 + fi +fi + +TAG=$(echo ${vstring} | cut -d- -f1 | sed -e 's/^v//') +ADD=$(echo ${vstring} | cut -s -d- -f2) + +git rev-parse 2> /dev/null +if [ $? == 0 ]; then + CMT=$(git describe --dirty --long | cut -s -d- -f3,4) +else + CMT=$(echo ${vstring} | cut -s -d- -f3,4) +fi +CMTR=$(echo $CMT | sed 's/-/_/') + +if [ -n "${BUILD_NUMBER}" ]; then + BLD="~b${BUILD_NUMBER}" +fi + +if [ "$1" = "rpm-version" ]; then + echo ${TAG} + exit +fi + +if [ "$1" = "rpm-release" ]; then + [ -z "${ADD}" ] && echo release && exit + echo ${ADD}${CMTR:+~${CMTR}}${BLD} + exit +fi + + if [ -n "${ADD}" ]; then + if [ "$1" = "rpm-string" ]; then + echo ${TAG}-${ADD}${CMTR:+~${CMTR}}${BLD} + else + echo ${TAG}-${ADD}${CMT:+~${CMT}}${BLD} + fi + else + echo ${TAG}-release +fi |