diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..e30b695 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,74 @@ +cmake_minimum_required(VERSION 2.4.0) + +# Set the plugin name to build +project(HttpNorthC) + +# Supported options: +# -DFOGLAMP_INCLUDE +# -DFOGLAMP_LIB +# -DFOGLAMP_SRC +# -DFOGLAMP_INSTALL +# +# If no -D options are given and FOGLAMP_ROOT environment variable is set +# then FogLAMP libraries and header files are pulled from FOGLAMP_ROOT path. + +set(CMAKE_CXX_FLAGS "-std=c++11 -O0") + +# Set plugin type (south, north, filter) +set(PLUGIN_TYPE "north") + +# Add here all needed FogLAMP libraries as list +set(NEEDED_FOGLAMP_LIBS common-lib plugins-common-lib) + +# Find source files +file(GLOB SOURCES *.cpp) + +# Find FogLAMP includes and libs, by including FindFogLAMP.cmak file +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}) +find_package(FogLAMP) +# If errors: make clean and remove Makefile +if (NOT FOGLAMP_FOUND) + if (EXISTS "${CMAKE_BINARY_DIR}/Makefile") + execute_process(COMMAND make clean WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) + file(REMOVE "${CMAKE_BINARY_DIR}/Makefile") + endif() + # Stop the build process + message(FATAL_ERROR "FogLAMP plugin '${PROJECT_NAME}' build error.") +endif() +# On success, FOGLAMP_INCLUDE_DIRS and FOGLAMP_LIB_DIRS variables are set + +# Add ./include +include_directories(include) + +# Add FogLAMP include dir(s) +include_directories(${FOGLAMP_INCLUDE_DIRS}) + +# Add other include paths this plugin needs +if (FOGLAMP_SRC) + message(STATUS "Using third-party includes " ${FOGLAMP_SRC}/C/thirdparty/Simple-Web-Server) + include_directories(${FOGLAMP_SRC}/C/thirdparty/Simple-Web-Server) +else() + include_directories(${FOGLAMP_INCLUDE_DIRS}/Simple-Web-Server) +endif() + +# Add FogLAMP lib path +link_directories(${FOGLAMP_LIB_DIRS}) + +# Create shared library +add_library(${PROJECT_NAME} SHARED ${SOURCES}) + +# Add FogLAMP library names +target_link_libraries(${PROJECT_NAME} ${NEEDED_FOGLAMP_LIBS}) + +# Add additional libraries +target_link_libraries(${PROJECT_NAME} -lssl -lcrypto) + +# Set the build version +set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION 1) + +set(FOGLAMP_INSTALL "" CACHE INTERNAL "") +# Install library +if (FOGLAMP_INSTALL) + message(STATUS "Installing ${PROJECT_NAME} in ${FOGLAMP_INSTALL_DIR}/plugins/${PLUGIN_TYPE}/${PROJECT_NAME}") + install(TARGETS ${PROJECT_NAME} DESTINATION ${FOGLAMP_INSTALL_DIR}/plugins/${PLUGIN_TYPE}/${PROJECT_NAME}) +endif() diff --git a/FindFogLAMP.cmake b/FindFogLAMP.cmake new file mode 100644 index 0000000..28e6a86 --- /dev/null +++ b/FindFogLAMP.cmake @@ -0,0 +1,142 @@ +# This CMake file locates the FogLAMP header files and libraries +# +# The following variables are set: +# FOGLAMP_INCLUDE_DIRS - Path(s) to FogLAMP headers files found +# FOGLAMP_LIB_DIRS - Path to FogLAMP shared libraries +# FOGLAMP_SUCCESS - Set on succes +# +# In case of error use SEND_ERROR and return() +# + +# Set defaults paths of installed FogLAMP SDK package +set(FOGLAMP_DEFAULT_INCLUDE_DIR "/usr/include/foglamp" CACHE INTERNAL "") +set(FOGLAMP_DEFAULT_LIB_DIR "/usr/lib/foglamp" CACHE INTERNAL "") + +# CMakeLists.txt options +set(FOGLAMP_SRC "" CACHE INTERNAL "") +set(FOGLAMP_INCLUDE "" CACHE INTERNAL "") +set(FOGLAMP_LIB "" CACHE INTERNAL "") + +# Return variables +set(FOGLAMP_INCLUDE_DIRS "" CACHE INTERNAL "") +set(FOGLAMP_LIB_DIRS "" CACHE INTERNAL "") +set(FOGLAMP_FOUND "" CACHE INTERNAL "") + +# No options set +# If FOGLAMP_ROOT env var is set, use it +if (NOT FOGLAMP_SRC AND NOT FOGLAMP_INCLUDE AND NOT FOGLAMP_LIB) + if (DEFINED ENV{FOGLAMP_ROOT}) + message(STATUS "No options set.\n" + " +Using found FOGLAMP_ROOT $ENV{FOGLAMP_ROOT}") + set(FOGLAMP_SRC $ENV{FOGLAMP_ROOT}) + endif() +endif() + +# -DFOGLAMP_SRC=/some_path or FOGLAMP_ROOT path +# Set return variable FOGLAMP_INCLUDE_DIRS +if (FOGLAMP_SRC) + unset(_INCLUDE_LIST CACHE) + file(GLOB_RECURSE _INCLUDE_COMMON "${FOGLAMP_SRC}/C/common/*.h") + file(GLOB_RECURSE _INCLUDE_SERVICES "${FOGLAMP_SRC}/C/services/common/*.h") + file(GLOB_RECURSE _INCLUDE_PLUGINS_FILTER_COMMON "${FOGLAMP_SRC}/C/plugins/filter/common/*.h") + list(APPEND _INCLUDE_LIST ${_INCLUDE_COMMON} ${_INCLUDE_SERVICES} ${_INCLUDE_PLUGINS_FILTER_COMMON}) + foreach(_ITEM ${_INCLUDE_LIST}) + get_filename_component(_ITEM_PATH ${_ITEM} DIRECTORY) + list(APPEND FOGLAMP_INCLUDE_DIRS ${_ITEM_PATH}) + endforeach() + # Add rapidjson header files + list(APPEND FOGLAMP_INCLUDE_DIRS "${FOGLAMP_SRC}/C/thirdparty/rapidjson/include") + unset(INCLUDE_LIST CACHE) + + list(REMOVE_DUPLICATES FOGLAMP_INCLUDE_DIRS) + + string (REPLACE ";" "\n +" DISPLAY_PATHS "${FOGLAMP_INCLUDE_DIRS}") + if (NOT DEFINED ENV{FOGLAMP_ROOT}) + message(STATUS "Using -DFOGLAMP_SRC option for includes\n +" "${DISPLAY_PATHS}") + else() + message(STATUS "Using FOGLAMP_ROOT for includes\n +" "${DISPLAY_PATHS}") + endif() + + if (NOT FOGLAMP_INCLUDE_DIRS) + message(SEND_ERROR "Needed FogLAMP header files not found in path ${FOGLAMP_SRC}/C") + return() + endif() +else() + # -DFOGLAMP_INCLUDE=/some_path + if (NOT FOGLAMP_INCLUDE) + set(FOGLAMP_INCLUDE ${FOGLAMP_DEFAULT_INCLUDE_DIR}) + message(STATUS "Using FogLAMP dev package includes " ${FOGLAMP_INCLUDE}) + else() + message(STATUS "Using -DFOGLAMP_INCLUDE option " ${FOGLAMP_INCLUDE}) + endif() + # Remove current value from cache + unset(_FIND_INCLUDES CACHE) + # Get up to date var from find_path + find_path(_FIND_INCLUDES NAMES plugin_api.h PATHS ${FOGLAMP_INCLUDE}) + if (_FIND_INCLUDES) + list(APPEND FOGLAMP_INCLUDE_DIRS ${_FIND_INCLUDES}) + endif() + # Remove current value from cache + unset(_FIND_INCLUDES CACHE) + + if (NOT FOGLAMP_INCLUDE_DIRS) + message(SEND_ERROR "Needed FogLAMP header files not found in path ${FOGLAMP_INCLUDE}") + return() + endif() +endif() + +# +# FogLAMP Libraries +# +# Check -DFOGLAMP_LIB=/some path is valid +# or use FOGLAMP_SRC/cmake_build/C/lib +# FOGLAMP_SRC might have been set to FOGLAMP_ROOT above +# +if (FOGLAMP_SRC) + # Set return variable FOGLAMP_LIB_DIRS + set(FOGLAMP_LIB "${FOGLAMP_SRC}/cmake_build/C/lib") + + if (NOT DEFINED ENV{FOGLAMP_ROOT}) + message(STATUS "Using -DFOGLAMP_SRC option for libs \n +" "${FOGLAMP_SRC}/cmake_build/C/lib") + else() + message(STATUS "Using FOGLAMP_ROOT for libs \n +" "${FOGLAMP_SRC}/cmake_build/C/lib") + endif() + + if (NOT EXISTS "${FOGLAMP_SRC}/cmake_build") + message(SEND_ERROR "FogLAMP has not been built yet in ${FOGLAMP_SRC} Compile it first.") + return() + endif() + + # Set return variable FOGLAMP_LIB_DIRS + set(FOGLAMP_LIB_DIRS "${FOGLAMP_SRC}/cmake_build/C/lib") +else() + if (NOT FOGLAMP_LIB) + set(FOGLAMP_LIB ${FOGLAMP_DEFAULT_LIB_DIR}) + message(STATUS "Using FogLAMP dev package libs " ${FOGLAMP_LIB}) + else() + message(STATUS "Using -DFOGLAMP_LIB option " ${FOGLAMP_LIB}) + endif() + # Set return variable FOGLAMP_LIB_DIRS + set(FOGLAMP_LIB_DIRS ${FOGLAMP_LIB}) +endif() + +# Check NEEDED_FOGLAMP_LIBS in libraries in FOGLAMP_LIB_DIRS +# NEEDED_FOGLAMP_LIBS variables comes from CMakeLists.txt +foreach(_LIB ${NEEDED_FOGLAMP_LIBS}) + # Remove current value from cache + unset(_FOUND_LIB CACHE) + # Get up to date var from find_library + find_library(_FOUND_LIB NAME ${_LIB} PATHS ${FOGLAMP_LIB_DIRS}) + if (_FOUND_LIB) + # Extract path form founf library file + get_filename_component(_DIR_LIB ${_FOUND_LIB} DIRECTORY) + else() + message(SEND_ERROR "Needed FogLAMP library ${_LIB} not found in ${FOGLAMP_LIB_DIRS}") + return() + endif() + # Remove current value from cache + unset(_FOUND_LIB CACHE) +endforeach() + +# Set return variable FOGLAMP_FOUND +set(FOGLAMP_FOUND "true") diff --git a/LICENSE b/LICENSE index 261eeb9..2ee1993 100644 --- a/LICENSE +++ b/LICENSE @@ -178,7 +178,7 @@ APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" + boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [yyyy] [name of copyright owner] + Copyright 2017 OSIsoft, LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..57458e5 --- /dev/null +++ b/README.rst @@ -0,0 +1,66 @@ +========================================= +FogLAMP "north" C++ North Plugin +========================================= + +This plugins allows buffered reading to be sent to an +arbitrary historian service or another FogLAMP data ingest. + +Build +----- +To build FogLAMP "north" C++ filter plugin: + +.. code-block:: console + + $ mkdir build + $ cd build + $ cmake .. + +- By default the FogLAMP develop package header files and libraries + are expected to be located in /usr/include/foglamp and /usr/lib/foglamp +- If **FOGLAMP_ROOT** env var is set and no -D options are set, + the header files and libraries paths are pulled from the ones under the + FOGLAMP_ROOT directory. + Please note that you must first run 'make' in the FOGLAMP_ROOT directory. + +You may also pass one or more of the following options to cmake to override +this default behaviour: + +- **FOGLAMP_SRC** sets the path of a FogLAMP source tree +- **FOGLAMP_INCLUDE** sets the path to FogLAMP header files +- **FOGLAMP_LIB sets** the path to FogLAMP libraries +- **FOGLAMP_INSTALL** sets the installation path of Random plugin + +NOTE: + - The **FOGLAMP_INCLUDE** option should point to a location where all the FogLAMP + header files have been installed in a single directory. + - The **FOGLAMP_LIB** option should point to a location where all the FogLAMP + libraries have been installed in a single directory. + - 'make install' target is defined only when **FOGLAMP_INSTALL** is set + +Examples: + +- no options + + $ cmake .. + +- no options and FOGLAMP_ROOT set + + $ export FOGLAMP_ROOT=/some_foglamp_setup + + $ cmake .. + +- set FOGLAMP_SRC + + $ cmake -DFOGLAMP_SRC=/home/source/develop/FogLAMP .. + +- set FOGLAMP_INCLUDE + + $ cmake -DFOGLAMP_INCLUDE=/dev-package/include .. +- set FOGLAMP_LIB + + $ cmake -DFOGLAMP_LIB=/home/dev/package/lib .. +- set FOGLAMP_INSTALL + + $ cmake -DFOGLAMP_INSTALL=/home/source/develop/FogLAMP + + $ cmake -DFOGLAMP_INSTALL=/usr/local/foglamp diff --git a/plugin.cpp b/plugin.cpp new file mode 100644 index 0000000..ef43ebf --- /dev/null +++ b/plugin.cpp @@ -0,0 +1,235 @@ +/* + * FogLAMP HTTP north plugin. + * + * Copyright (c) 2018 Dianomic Systems + * + * Released under the Apache 2.0 Licence + * + * Author: Amandeep Singh Arora, Massimiliano Pinto + */ +#include +#include +#include +#include "plugin_api.h" +#include "simple_https.h" +#include "simple_http.h" +#include "reading.h" +#include "config_category.h" + +using namespace std; + +/** + * Plugin specific default configuration + */ +#define PLUGIN_DEFAULT_CONFIG "{ " \ + "\"plugin\": { " \ + "\"description\": \"HTTP North C Plugin\", " \ + "\"type\": \"string\", " \ + "\"default\": \"http-north\" }, " \ + "\"URL\": { " \ + "\"description\": \"The URL of the HTTP Connector to send data to\", " \ + "\"type\": \"string\", " \ + "\"default\": \"http://localhost:6683/sensor-reading\" }, " \ + "\"HttpTimeout\": { " \ + "\"description\": \"Timeout in seconds for the HTTP operations with the HTTP Connector Relay\", " \ + "\"type\": \"integer\", \"default\": \"10\" }, " \ + "\"verifySSL\": { " \ + "\"description\": \"Verify SSL certificate\", " \ + "\"type\": \"boolean\", \"default\": \"False\" } " \ + " }" + +/** + * The HTTP north plugin interface + */ +extern "C" { + +/** + * The C API plugin information structure + */ +static PLUGIN_INFORMATION info = { + "http", // Name + "1.0.0", // Version + 0, // Flags + PLUGIN_TYPE_NORTH, // Type + "1.0.0", // Interface version + PLUGIN_DEFAULT_CONFIG // Configuration +}; + +/** + * HTTP Server connector info + */ +typedef struct +{ + string proto; + HttpSender *sender; // HttpSender is the base class for SimpleHttp and SimpleHttp classes + // and sendRequest is a virtual method of HttpSender class + string path; +} CONNECTOR_INFO; + +uint32_t sendToServer(const vector& readings, CONNECTOR_INFO *connInfo); +const vector> createMessageHeader(); +const string& getReadingString(const Reading& reading); + +/** + * Return the information about this plugin + */ +PLUGIN_INFORMATION *plugin_info() +{ + return &info; +} + +/** + * Initialise the plugin with configuration. + * + * This funcion is called to get the plugin handle. + */ +PLUGIN_HANDLE plugin_init(const ConfigCategory* configData) +{ + Logger::getLogger()->info("http-north C plugin: %s", __FUNCTION__); + + /** + * Handle the HTTP(S) parameters here + */ + string url = configData->getValue("URL"); + unsigned int timeout = atoi(configData->getValue("HttpTimeout").c_str()); + + /** + * Extract host, port, path from URL + */ + size_t findProtocol = url.find_first_of(":"); + string protocol = url.substr(0,findProtocol); + + string tmpUrl = url.substr(findProtocol + 3); + size_t findPort = tmpUrl.find_first_of(":"); + string hostName = tmpUrl.substr(0, findPort); + + size_t findPath = tmpUrl.find_first_of("/"); + string port = tmpUrl.substr(findPort + 1 , findPath - findPort -1); + string path = tmpUrl.substr(findPath); + + /** + * Allocate the HTTP(S) handler for "Hostname : port", + * connect_timeout and request_timeout. + */ + string hostAndPort(hostName + ":" + port); + + CONNECTOR_INFO *connector_info = new CONNECTOR_INFO; + if (protocol == string("http")) + connector_info->sender = new SimpleHttp(hostAndPort, timeout, timeout); + else if (protocol == string("https")) + connector_info->sender = new SimpleHttps(hostAndPort, timeout, timeout); + else + { + Logger::getLogger()->error("Didn't find http/https prefix in URL='%s', cannot proceed", url.c_str()); + throw new exception(); + } + + connector_info->path = path; + connector_info->proto = protocol; + Logger::getLogger()->info("HTTP plugin configured: URL='%s' ", url.c_str()); + + return (PLUGIN_HANDLE) (connector_info); +} + +/** + * Send Readings data to historian server + */ +uint32_t plugin_send(const PLUGIN_HANDLE handle, + const vector readings) +{ + CONNECTOR_INFO *connInfo = (CONNECTOR_INFO *) handle; + return sendToServer(readings, connInfo); +} + +/** + * Shutdown the plugin + * + * Delete allocated data + * + * @param handle The plugin handle + */ +void plugin_shutdown(PLUGIN_HANDLE handle) +{ + Logger::getLogger()->info("http-north C plugin: %s", __FUNCTION__); + CONNECTOR_INFO *connInfo = (CONNECTOR_INFO *) handle; + delete connInfo->sender; + delete connInfo; +} + +const string& getReadingString(const Reading& reading) +{ + string *value = new string; + string &m_value = *value; + + // Convert reading data into JSON string + m_value.append("{\"timestamp\" : \"" + reading.getAssetDateTime(Reading::FMT_STANDARD) + "Z" + "\""); + m_value.append(",\"asset\" : \"" + reading.getAssetName() + "\""); + m_value.append(",\"key\" : \"" + reading.getUuid() + "\""); + m_value.append(",\"readings\" : {"); + + // Get reading data + const vector data = reading.getReadingData(); + + for (vector::const_iterator it = data.begin(); it != data.end(); ++it) + { + m_value.append("\"" + (*it)->getName() + "\": " + (*it)->getData().toString() + ","); + } + + // remove last unrequired comma character because of above loop, if one was added + auto lastChar = m_value.rbegin(); + if (*lastChar == ',') + m_value.pop_back(); + + m_value.append("}}"); + return m_value; +} + +const vector> createMessageHeader() +{ + vector> res; + res.push_back(pair("Content-Type", "application/json")); + return res; +} + +uint32_t sendToServer(const vector& readings, CONNECTOR_INFO *connInfo) +{ + ostringstream jsonData; + jsonData << "["; + + // Fetch Reading* data + for (vector::const_iterator elem = readings.begin(); elem != readings.end(); ++elem) + { + jsonData << getReadingString(**elem) << (elem < (readings.end() -1 ) ? ", " : ""); + } + + jsonData << "]"; + + // Create header for Readings data + vector> readingData = createMessageHeader(); + + // Build a HTTP(S) POST with readingData headers and 'allReadings' JSON payload + // Then get HTTPS POST ret code and return 0 to client on error + try + { + int res = connInfo->sender->sendRequest("POST", connInfo->path, readingData, jsonData.str()); + if (res != 200 && res != 204 && res != 201) + { + Logger::getLogger()->error("http-north C plugin: Sending JSON readings HTTP(S) error: %d", res); + return 0; + } + + Logger::getLogger()->info("http-north C plugin: Successfully sent %d readings", readings.size()); + + // Return number of sent readings to the caller + return readings.size(); + } + catch (const std::exception& e) + { + Logger::getLogger()->error("http-north C plugin: Sending JSON data exception: %s", e.what()); + return 0; + } +} + +// End of extern "C" +}; +