From 97b18417ef503828d0636756c9f1cc330578869c Mon Sep 17 00:00:00 2001 From: "Lott, Christopher (cl778h)" Date: Thu, 9 Apr 2020 18:25:19 -0400 Subject: [PATCH] Move wrapped C library to subpackage Create new subpackage rmr/rmrclib with the C library loaded via ctypes. Add method to get constants from RMR library that recognizes mocks. Split test files into test_rmr and test_rmrclib. Extend sphinx configuration to mock the rmrclib subpackage. This change allows generation of API documentation by Sphinx when the .so file is not available and the import would fail, for example at ReadTheDocs. Signed-off-by: Lott, Christopher (cl778h) Change-Id: Idb9bb1d7a534c4142ea0353b5a48d3132e55f6e6 --- docs/conf.py | 8 +- docs/rmr_api.rst | 1087 +--------------------------------- ricxappframe/rmr/rmr.py | 117 +--- ricxappframe/rmr/rmrclib/__init__.py | 16 + ricxappframe/rmr/rmrclib/rmrclib.py | 88 +++ tests/test_rmr.py | 32 - tests/test_rmrclib.py | 49 ++ 7 files changed, 193 insertions(+), 1204 deletions(-) mode change 100644 => 100755 docs/conf.py create mode 100644 ricxappframe/rmr/rmrclib/__init__.py create mode 100644 ricxappframe/rmr/rmrclib/rmrclib.py create mode 100644 tests/test_rmrclib.py diff --git a/docs/conf.py b/docs/conf.py old mode 100644 new mode 100755 index b92d4d9..47e5eb7 --- a/docs/conf.py +++ b/docs/conf.py @@ -2,16 +2,22 @@ import os import sys from docs_conf.conf import * +# autodoc needs this to find the code sys.path.insert(0, os.path.abspath("../")) extensions = ["sphinx.ext.autodoc", "sphinx.ext.viewcode", "numpydoc"] -# dont alphabetically order +# don't alphabetically order autodoc_member_order = "bysource" linkcheck_ignore = ["http://localhost.*", "http://127.0.0.1.*", "https://gerrit.o-ran-sc.org.*"] + +# silence complaints from autodoc gen nitpick_ignore = [ ('py:class', 'ctypes.c_char_p'), ('py:class', 'ctypes.c_void_p'), ('py:class', 'ricxappframe.rmr.rmr.LP_rmr_mbuf_t'), ] + +# RMR c library is not available in ReadTheDocs +autodoc_mock_imports = ['ricxappframe.rmr.rmrclib'] diff --git a/docs/rmr_api.rst b/docs/rmr_api.rst index 4db5b7e..a33d6a1 100644 --- a/docs/rmr_api.rst +++ b/docs/rmr_api.rst @@ -15,1087 +15,12 @@ requires that you have the RMR shared-object library installed. RMR API ------- -.. - Sphinx can generate API documentation by running Python to pull doc strings - from the binding code using these Sphinx directives that are commented out: - .. automodule:: ricxappframe.rmr.rmr - :members: - But that approach requires the RMR library to be installed, which is difficult - to achieve at ReadTheDocs.io. Instead, the RST below was generated and captured - according to the method shown at - https://stackoverflow.com/questions/2668187/make-sphinx-generate-rst-class-documentation-from-pydoc +.. automodule:: ricxappframe.rmr.rmr + :members: +RMR Helper API +-------------- -.. py:module:: ricxappframe.rmr.rmr - - -.. - !! processed by numpydoc !! - -.. py:data:: RMR_MAX_RCV_BYTES - :module: ricxappframe.rmr.rmr - :value: 65536 - - - Maximum size message to receive - - - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:data:: RMRFL_MTCALL - :module: ricxappframe.rmr.rmr - :value: 2 - - - Multi-threaded initialization flag - - - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:data:: RMRFL_NONE - :module: ricxappframe.rmr.rmr - :value: 0 - - - Empty flag - - - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:data:: RMR_OK - :module: ricxappframe.rmr.rmr - :value: 0 - - - State constant for OK - - - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:data:: RMR_ERR_TIMEOUT - :module: ricxappframe.rmr.rmr - :value: 12 - - - State constant for timeout - - - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:data:: RMR_ERR_RETRY - :module: ricxappframe.rmr.rmr - :value: 10 - - - State constant for retry - - - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:class:: rmr_mbuf_t - :module: ricxappframe.rmr.rmr - - - Mirrors public members of type rmr_mbuf_t from RMR header file src/common/include/rmr.h - - | typedef struct { - | int state; // state of processing - | int mtype; // message type - | int len; // length of data in the payload (send or received) - | unsigned char* payload; // transported data - | unsigned char* xaction; // pointer to fixed length transaction id bytes - | int sub_id; // subscription id - | int tp_state; // transport state (a.k.a errno) - | - | these things are off limits to the user application - | - | void* tp_buf; // underlying transport allocated pointer (e.g. nng message) - | void* header; // internal message header (whole buffer: header+payload) - | unsigned char* id; // if we need an ID in the message separate from the xaction id - | int flags; // various MFL (private) flags as needed - | int alloc_len; // the length of the allocated space (hdr+payload) - | } rmr_mbuf_t; - - RE PAYLOADs type below, see the documentation for c_char_p: - class ctypes.c_char_p - Represents the C char * datatype when it points to a zero-terminated string. - For a general character pointer that may also point to binary data, POINTER(c_char) - must be used. The constructor accepts an integer address, or a bytes object. - - - - - - - - - - - - - - :Attributes: - - **len** - Structure/Union member - - **mtype** - Structure/Union member - - **payload** - Structure/Union member - - **state** - Structure/Union member - - **sub_id** - Structure/Union member - - **tp_state** - Structure/Union member - - **xaction** - Structure/Union member - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_init(uproto_port: ctypes.c_char_p, max_msg_size: int, flags: int) -> ctypes.c_void_p - :module: ricxappframe.rmr.rmr - - - Prepares the environment for sending and receiving messages. - Refer to RMR C documentation for method:: - - extern void* rmr_init(char* uproto_port, int max_msg_size, int flags) - - This function raises an exception if the returned context is None. - - :Parameters: - - **uproto_port: c_char_p** - Pointer to a buffer with the port number as a string; e.g., "4550" - - **max_msg_size: integer** - Maximum message size to receive - - **flags: integer** - RMR option flags - - :Returns: - - c_void_p: - Pointer to RMR context - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_ready(vctx: ctypes.c_void_p) -> int - :module: ricxappframe.rmr.rmr - - - Checks if a routing table has been received and installed. - Refer to RMR C documentation for method:: - - extern int rmr_ready(void* vctx) - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - :Returns: - - 1 for yes, 0 for no - .. - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_close(vctx: ctypes.c_void_p) - :module: ricxappframe.rmr.rmr - - - Closes the listen socket. - Refer to RMR C documentation for method:: - - extern void rmr_close(void* vctx) - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - :Returns: - - None - .. - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_set_stimeout(vctx: ctypes.c_void_p, rloops: int) -> int - :module: ricxappframe.rmr.rmr - - - Sets the configuration for how RMR will retry message send operations. - Refer to RMR C documentation for method:: - - extern int rmr_set_stimeout(void* vctx, int rloops) - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - **rloops: int** - Number of retry loops - - :Returns: - - 0 on success, -1 on failure - .. - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_alloc_msg(vctx: ctypes.c_void_p, size: int, payload=None, gen_transaction_id: bool = False, mtype=None, meid=None, sub_id=None, fixed_transaction_id=None) - :module: ricxappframe.rmr.rmr - - - Allocates and returns a buffer to write and send through the RMR library. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_alloc_msg(void* vctx, int size) - - Optionally populates the message from the remaining arguments. - - TODO: on next API break, clean up transaction_id ugliness. Kept for now to preserve API. - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - **size: int** - How much space to allocate - - **payload: bytes** - if not None, attempts to set the payload - - **gen_transaction_id: bool** - if True, generates and sets a transaction ID. - Note, option fixed_transaction_id overrides this option - - **mtype: bytes** - if not None, sets the sbuf's message type - - **meid: bytes** - if not None, sets the sbuf's meid - - **sub_id: bytes** - if not None, sets the sbuf's subscription id - - **fixed_transaction_id: bytes** - if not None, used as the transaction ID. - Note, this overrides the option gen_transaction_id - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_realloc_payload(ptr_mbuf: ctypes.c_void_p, new_len: int, copy: bool = False, clone: bool = False) - :module: ricxappframe.rmr.rmr - - - Allocates and returns a message buffer large enough for the new length. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_realloc_payload(rmr_mbuf_t*, int, int, int) - - :Parameters: - - **ptr_mbuf: c_void_p** - Pointer to rmr_mbuf structure - - **new_len: int** - Length - - **copy: bool** - Whether to copy the original paylod - - **clone: bool** - Whether to clone the original buffer - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_free_msg(ptr_mbuf: ctypes.c_void_p) - :module: ricxappframe.rmr.rmr - - - Releases the message buffer. - Refer to RMR C documentation for method:: - - extern void rmr_free_msg(rmr_mbuf_t* mbuf ) - - :Parameters: - - **ptr_mbuf: c_void_p** - Pointer to rmr_mbuf structure - - :Returns: - - None - .. - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_payload_size(ptr_mbuf: ctypes.c_void_p) -> int - :module: ricxappframe.rmr.rmr - - - Gets the number of bytes available in the payload. - Refer to RMR C documentation for method:: - - extern int rmr_payload_size(rmr_mbuf_t* msg) - - :Parameters: - - **ptr_mbuf: c_void_p** - Pointer to rmr_mbuf structure - - :Returns: - - int: - Number of bytes available - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_send_msg(vctx: ctypes.c_void_p, ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t) -> ricxappframe.rmr.rmr.LP_rmr_mbuf_t - :module: ricxappframe.rmr.rmr - - - Sends the message according to the routing table and returns an empty buffer. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_send_msg(void* vctx, rmr_mbuf_t* msg) - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - **ptr_mbuf: c_void_p** - Pointer to rmr_mbuf structure - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_rcv_msg(vctx: ctypes.c_void_p, ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t) -> ricxappframe.rmr.rmr.LP_rmr_mbuf_t - :module: ricxappframe.rmr.rmr - - - Waits for a message to arrive, and returns it. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_rcv_msg(void* vctx, rmr_mbuf_t* old_msg) - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - **ptr_mbuf: c_void_p** - Pointer to rmr_mbuf structure - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_torcv_msg(vctx: ctypes.c_void_p, ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t, ms_to: int) -> ricxappframe.rmr.rmr.LP_rmr_mbuf_t - :module: ricxappframe.rmr.rmr - - - Waits up to the timeout value for a message to arrive, and returns it. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_torcv_msg(void* vctx, rmr_mbuf_t* old_msg, int ms_to) - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to RMR context - - **ptr_mbuf: c_void_p** - Pointer to rmr_mbuf structure - - **ms_to: int** - Time out value in milliseconds - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_rts_msg(vctx: ctypes.c_void_p, ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t, payload=None, mtype=None) -> ricxappframe.rmr.rmr.LP_rmr_mbuf_t - :module: ricxappframe.rmr.rmr - - - Sends a message to the originating endpoint and returns an empty buffer. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_rts_msg(void* vctx, rmr_mbuf_t* msg) - - additional features beyond c-rmr: - if payload is not None, attempts to set the payload - if mtype is not None, sets the sbuf's message type - - :Parameters: - - **vctx: ctypes c_void_p** - Pointer to an RMR context - - **ptr_mbuf: ctypes c_void_p** - Pointer to an RMR message buffer - - **payload: bytes** - Payload - - **mtype: bytes** - Message type - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_call(vctx: ctypes.c_void_p, ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t) -> ricxappframe.rmr.rmr.LP_rmr_mbuf_t - :module: ricxappframe.rmr.rmr - - - Sends a message, waits for a response and returns it. - Refer to RMR C documentation for method:: - - extern rmr_mbuf_t* rmr_call(void* vctx, rmr_mbuf_t* msg) - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an RMR message buffer - - :Returns: - - c_void_p: - Pointer to rmr_mbuf structure - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_set_meid(ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t, byte_str: bytes) -> int - :module: ricxappframe.rmr.rmr - - - Sets the managed entity field in the message and returns the number of bytes copied. - Refer to RMR C documentation for method:: - - extern int rmr_bytes2meid(rmr_mbuf_t* mbuf, unsigned char const* src, int len); - - Caution: the meid length supported in an RMR message is 32 bytes, but C applications - expect this to be a nil terminated string and thus only 31 bytes are actually available. - - Raises: exceptions.MeidSizeOutOfRang - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an RMR message buffer - - **byte_tr: bytes** - Managed entity ID value - - :Returns: - - int: - number of bytes copied - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_get_meid(ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t) -> bytes - :module: ricxappframe.rmr.rmr - - - Gets the managed entity ID (meid) from the message header. - This is a python-friendly version of RMR C method:: - - extern unsigned char* rmr_get_meid(rmr_mbuf_t* mbuf, unsigned char* dest); - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an RMR message buffer - - :Returns: - - bytes: - Managed entity ID - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: rmr_get_src(ptr_mbuf: ricxappframe.rmr.rmr.LP_rmr_mbuf_t, dest: ctypes.c_char_p) -> ctypes.c_char_p - :module: ricxappframe.rmr.rmr - - - Copies the message-source information to the buffer. - Refer to RMR C documentation for method:: - - extern unsigned char* rmr_get_src(rmr_mbuf_t* mbuf, unsigned char* dest); - - :Parameters: - - **ptr_mbuf: ctypes POINTER(rmr_mbuf_t)** - Pointer to an RMR message buffer - - **dest: ctypes c_char_p** - Pointer to a buffer to receive the message source - - :Returns: - - string: - message-source information - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: get_payload(ptr_mbuf: ctypes.c_void_p) -> bytes - :module: ricxappframe.rmr.rmr - - - Gets the binary payload from the rmr_buf_t*. - - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - :Returns: - - bytes: - the message payload - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: get_xaction(ptr_mbuf: ctypes.c_void_p) -> bytes - :module: ricxappframe.rmr.rmr - - - Gets the transaction ID from the rmr_buf_t*. - - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - :Returns: - - bytes: - the transaction id - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: message_summary(ptr_mbuf: ctypes.c_void_p) -> dict - :module: ricxappframe.rmr.rmr - - - Returns a dict with the fields of an RMR message. - - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - :Returns: - - dict: - dict message summary - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: set_payload_and_length(byte_str: bytes, ptr_mbuf: ctypes.c_void_p) - :module: ricxappframe.rmr.rmr - - - Sets an rmr payload and content length. - In place method, no return. - - - :Parameters: - - **byte_str: bytes** - the bytes to set the payload to - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: generate_and_set_transaction_id(ptr_mbuf: ctypes.c_void_p) - :module: ricxappframe.rmr.rmr - - - Generates a UUID and sets the RMR transaction id to it - - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: set_transaction_id(ptr_mbuf: ctypes.c_void_p, tid_bytes: bytes) - :module: ricxappframe.rmr.rmr - - - Sets an RMR transaction id - TODO: on next API break, merge these two functions. Not done now to preserve API. - - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - **tid_bytes: bytes** - bytes of the desired transaction id - - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - -.. py:function:: get_src(ptr_mbuf: ctypes.c_void_p) -> str - :module: ricxappframe.rmr.rmr - - - Gets the message source (likely host:port) - - - :Parameters: - - **ptr_mbuf: ctypes c_void_p** - Pointer to an rmr message buffer - - :Returns: - - string: - message source - - - - - - - - - - - - - - .. - !! processed by numpydoc !! - +.. automodule:: ricxappframe.rmr.helpers + :members: diff --git a/ricxappframe/rmr/rmr.py b/ricxappframe/rmr/rmr.py index 80617a7..0c7d125 100644 --- a/ricxappframe/rmr/rmr.py +++ b/ricxappframe/rmr/rmr.py @@ -15,107 +15,46 @@ # limitations under the License. # ================================================================================== import uuid -import json -from ctypes import CDLL, POINTER, RTLD_GLOBAL, Structure -from ctypes import c_char, c_char_p, c_int, c_void_p, cast, create_string_buffer, memmove +from ctypes import POINTER, Structure +from ctypes import c_int, c_char, c_char_p, c_void_p, memmove, cast, create_string_buffer from ricxappframe.rmr.exceptions import BadBufferAllocation, MeidSizeOutOfRange, InitFailed +from ricxappframe.rmr.rmrclib.rmrclib import rmr_c_lib, get_constants, state_to_status -# https://docs.python.org/3.7/library/ctypes.html -# https://stackoverflow.com/questions/2327344/ctypes-loading-a-c-shared-library-that-has-dependencies/30845750#30845750 -# make sure you do a set -x LD_LIBRARY_PATH /usr/local/lib/; -rmr_c_lib = CDLL("librmr_si.so", mode=RTLD_GLOBAL) - - -# Internal Helpers (not a part of public api) - - -_rmr_const = rmr_c_lib.rmr_get_consts -_rmr_const.argtypes = [] -_rmr_const.restype = c_char_p - - -def _get_constants(cache={}) -> dict: - """ - Gets constants published by RMR and caches for subsequent calls. - TODO: are there constants that end user applications need? - """ - if cache: - return cache - - js = _rmr_const() # read json string - cache = json.loads(str(js.decode())) # create constants value object as a hash - return cache - - -def _get_mapping_dict(cache={}) -> dict: - """ - Builds a state mapping dict from constants and caches for subsequent calls. - Relevant constants at this writing include: - - RMR_OK 0 state is good - RMR_ERR_BADARG 1 argument passd to function was unusable - RMR_ERR_NOENDPT 2 send/call could not find an endpoint based on msg type - RMR_ERR_EMPTY 3 msg received had no payload; attempt to send an empty message - RMR_ERR_NOHDR 4 message didn't contain a valid header - RMR_ERR_SENDFAILED 5 send failed; errno has nano reason - RMR_ERR_CALLFAILED 6 unable to send call() message - RMR_ERR_NOWHOPEN 7 no wormholes are open - RMR_ERR_WHID 8 wormhole id was invalid - RMR_ERR_OVERFLOW 9 operation would have busted through a buffer/field size - RMR_ERR_RETRY 10 request (send/call/rts) failed, but caller should retry (EAGAIN for wrappers) - RMR_ERR_RCVFAILED 11 receive failed (hard error) - RMR_ERR_TIMEOUT 12 message processing call timed out - RMR_ERR_UNSET 13 the message hasn't been populated with a transport buffer - RMR_ERR_TRUNC 14 received message likely truncated - RMR_ERR_INITFAILED 15 initialization of something (probably message) failed - - """ - if cache: - return cache - - rmr_consts = _get_constants() - for key in rmr_consts: # build the state mapping dict - if key[:7] in ["RMR_ERR", "RMR_OK"]: - en = int(rmr_consts[key]) - cache[en] = key - - return cache +############## +# PRIVATE API +############## -def _state_to_status(stateno: int) -> str: +def _get_rmr_constant(key: str, default=None): """ - Converts a msg state integer to a status string. - Returns "UNKNOWN STATE" if the int value is not known. - + Gets the constant with the named key from the RMR C library. + Returns None if the value is not a simple type. This happens + during sphinx autodoc document generation, which mocks the + rmrclib package to work without the RMR shared object file. """ - sdict = _get_mapping_dict() - return sdict.get(stateno, "UNKNOWN STATE") - - -_RCONST = _get_constants() + val = get_constants().get(key, default) + return val if isinstance(val, (type(None), str, int, float, bool)) else None ############## # PUBLIC API ############## - -# These constants are directly usable by importers of this library +# Publish constants from RMR C-language header files for use by importers of this library. # TODO: Are there others that will be useful? - -#: Maximum size message to receive -RMR_MAX_RCV_BYTES = _RCONST["RMR_MAX_RCV_BYTES"] +#: Typical size message to receive; size is not limited +RMR_MAX_RCV_BYTES = _get_rmr_constant('RMR_MAX_RCV_BYTES') #: Multi-threaded initialization flag -RMRFL_MTCALL = _RCONST.get("RMRFL_MTCALL", 0x02) # initialization flags +RMRFL_MTCALL = _get_rmr_constant('RMRFL_MTCALL', 0x02) # initialization flags #: Empty flag -RMRFL_NONE = _RCONST.get("RMRFL_NONE", 0x0) +RMRFL_NONE = _get_rmr_constant('RMRFL_NONE', 0x0) #: State constant for OK -RMR_OK = _RCONST["RMR_OK"] +RMR_OK = _get_rmr_constant('RMR_OK', 0x00) #: State constant for timeout -RMR_ERR_TIMEOUT = _RCONST["RMR_ERR_TIMEOUT"] +RMR_ERR_TIMEOUT = _get_rmr_constant('RMR_ERR_TIMEOUT') #: State constant for retry -RMR_ERR_RETRY = _RCONST["RMR_ERR_RETRY"] +RMR_ERR_RETRY = _get_rmr_constant('RMR_ERR_RETRY') class rmr_mbuf_t(Structure): @@ -160,8 +99,6 @@ class rmr_mbuf_t(Structure): # argtypes and restype are important: https://stackoverflow.com/questions/24377845/ctype-why-specify-argtypes - - _rmr_init = rmr_c_lib.rmr_init _rmr_init.argtypes = [c_char_p, c_int, c_int] _rmr_init.restype = c_void_p @@ -607,7 +544,7 @@ def rmr_set_meid(ptr_mbuf: POINTER(rmr_mbuf_t), byte_str: bytes) -> int: int: number of bytes copied """ - max = _get_constants().get("RMR_MAX_MEID", 32) + max = _get_rmr_constant("RMR_MAX_MEID", 32) if len(byte_str) >= max: raise MeidSizeOutOfRange @@ -643,7 +580,7 @@ def rmr_get_meid(ptr_mbuf: POINTER(rmr_mbuf_t)) -> bytes: bytes: Managed entity ID """ - sz = _get_constants().get("RMR_MAX_MEID", 32) # size for buffer to fill + sz = _get_rmr_constant("RMR_MAX_MEID", 32) # size for buffer to fill buf = create_string_buffer(sz) _rmr_get_meid(ptr_mbuf, buf) return buf.value @@ -715,7 +652,7 @@ def get_xaction(ptr_mbuf: c_void_p) -> bytes: the transaction id """ val = cast(ptr_mbuf.contents.xaction, c_char_p).value - sz = _get_constants().get("RMR_MAX_XID", 0) + sz = _get_rmr_constant("RMR_MAX_XID", 0) return val[:sz] @@ -740,7 +677,7 @@ def message_summary(ptr_mbuf: c_void_p) -> dict: "subscription id": ptr_mbuf.contents.sub_id, "transaction id": get_xaction(ptr_mbuf), "message state": ptr_mbuf.contents.state, - "message status": _state_to_status(ptr_mbuf.contents.state), + "message status": state_to_status(ptr_mbuf.contents.state), "payload max size": rmr_payload_size(ptr_mbuf), "meid": rmr_get_meid(ptr_mbuf), "message source": get_src(ptr_mbuf), @@ -794,7 +731,7 @@ def set_transaction_id(ptr_mbuf: c_void_p, tid_bytes: bytes): tid_bytes: bytes bytes of the desired transaction id """ - sz = _get_constants().get("RMR_MAX_XID", 0) + sz = _get_rmr_constant("RMR_MAX_XID", 0) memmove(ptr_mbuf.contents.xaction, tid_bytes, sz) @@ -812,7 +749,7 @@ def get_src(ptr_mbuf: c_void_p) -> str: string: message source """ - sz = _get_constants().get("RMR_MAX_SRC", 64) # size to fill + sz = _get_rmr_constant("RMR_MAX_SRC", 64) # size to fill buf = create_string_buffer(sz) rmr_get_src(ptr_mbuf, buf) return buf.value.decode() diff --git a/ricxappframe/rmr/rmrclib/__init__.py b/ricxappframe/rmr/rmrclib/__init__.py new file mode 100644 index 0000000..a1dc889 --- /dev/null +++ b/ricxappframe/rmr/rmrclib/__init__.py @@ -0,0 +1,16 @@ +# ================================================================================== +# Copyright (c) 2019-2020 Nokia +# Copyright (c) 2018-2020 AT&T Intellectual Property. +# +# 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. +# ================================================================================== diff --git a/ricxappframe/rmr/rmrclib/rmrclib.py b/ricxappframe/rmr/rmrclib/rmrclib.py new file mode 100644 index 0000000..ad770ab --- /dev/null +++ b/ricxappframe/rmr/rmrclib/rmrclib.py @@ -0,0 +1,88 @@ +# ================================================================================== +# Copyright (c) 2019-2020 Nokia +# Copyright (c) 2018-2020 AT&T Intellectual Property. +# +# 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 ctypes +import json + +# Subpackage that creates and publishes a reference to the C shared library. +# Intended to be private; RMR-python and xapp-frame-py users should not need this. +# Sphinx and autodoc mock this module to run without the .so file present. + +# https://docs.python.org/3.7/library/ctypes.html +# https://stackoverflow.com/questions/2327344/ctypes-loading-a-c-shared-library-that-has-dependencies/30845750#30845750 +# make sure you do a set -x LD_LIBRARY_PATH /usr/local/lib/; +rmr_c_lib = ctypes.CDLL("librmr_si.so", mode=ctypes.RTLD_GLOBAL) +_rmr_get_consts = rmr_c_lib.rmr_get_consts +_rmr_get_consts.argtypes = [] +_rmr_get_consts.restype = ctypes.c_char_p + + +def get_constants(cache={}): + """ + Gets constants published by RMR and caches for subsequent calls. + TODO: are there constants that end user applications need? + """ + if cache: + return cache + + js = _rmr_get_consts() # read json string + cache = json.loads(str(js.decode())) # create constants value object as a hash + return cache + + +def get_mapping_dict(cache={}): + """ + Builds a state mapping dict from constants and caches for subsequent calls. + Relevant constants at this writing include: + + RMR_OK 0 state is good + RMR_ERR_BADARG 1 argument passd to function was unusable + RMR_ERR_NOENDPT 2 send/call could not find an endpoint based on msg type + RMR_ERR_EMPTY 3 msg received had no payload; attempt to send an empty message + RMR_ERR_NOHDR 4 message didn't contain a valid header + RMR_ERR_SENDFAILED 5 send failed; errno has nano reason + RMR_ERR_CALLFAILED 6 unable to send call() message + RMR_ERR_NOWHOPEN 7 no wormholes are open + RMR_ERR_WHID 8 wormhole id was invalid + RMR_ERR_OVERFLOW 9 operation would have busted through a buffer/field size + RMR_ERR_RETRY 10 request (send/call/rts) failed, but caller should retry (EAGAIN for wrappers) + RMR_ERR_RCVFAILED 11 receive failed (hard error) + RMR_ERR_TIMEOUT 12 message processing call timed out + RMR_ERR_UNSET 13 the message hasn't been populated with a transport buffer + RMR_ERR_TRUNC 14 received message likely truncated + RMR_ERR_INITFAILED 15 initialization of something (probably message) failed + + """ + if cache: + return cache + + rmr_consts = get_constants() + for key in rmr_consts: # build the state mapping dict + if key[:7] in ["RMR_ERR", "RMR_OK"]: + en = int(rmr_consts[key]) + cache[en] = key + + return cache + + +def state_to_status(stateno): + """ + Converts a msg state integer to a status string. + + Returns "UNKNOWN STATE" if the int value is not known. + """ + sdict = get_mapping_dict() + return sdict.get(stateno, "UNKNOWN STATE") diff --git a/tests/test_rmr.py b/tests/test_rmr.py index a503968..afd9980 100644 --- a/tests/test_rmr.py +++ b/tests/test_rmr.py @@ -68,38 +68,6 @@ def _assert_new_sbuf(sbuf): assert summary["errno"] == 0 -def test_get_constants(expected_constants): - """ - test getting constants. We don't care what values are returned as those - should be meaningful only to RMR. We do care that all of the constants - which are defined in expected_contents are returned. Further, we don't - consider it to be an error if the returned list has more constants than - what are in our list. - - To avoid frustration, this should list all missing keys, not fail on the - first missing key. - """ - errors = 0 - econst = expected_constants - rconst = rmr._get_constants() - for key in econst: # test all expected constants - if key not in rconst: # expected value not listed by rmr - errors += 1 - print("did not find required constant in list from RMR: %s" % key) - - assert errors == 0 - - -def test_get_mapping_dict(expected_states): - """ - test getting mapping string - """ - assert rmr._get_mapping_dict() == expected_states - assert rmr._state_to_status(0) == "RMR_OK" - assert rmr._state_to_status(12) == "RMR_ERR_TIMEOUT" - assert rmr._state_to_status(666) == "UNKNOWN STATE" - - def test_meid(): """ test meid stringification diff --git a/tests/test_rmrclib.py b/tests/test_rmrclib.py new file mode 100644 index 0000000..eba4436 --- /dev/null +++ b/tests/test_rmrclib.py @@ -0,0 +1,49 @@ +# =================================================================================2 +# Copyright (c) 2019-2020 Nokia +# Copyright (c) 2018-2020 AT&T Intellectual Property. +# +# 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 ricxappframe.rmr.rmrclib import rmrclib + + +def test_get_constants(expected_constants): + """ + test getting constants. We don't care what values are returned as those + should be meaningful only to RMR. We do care that all of the constants + which are defined in expected_contents are returned. Further, we don't + consider it to be an error if the returned list has more constants than + what are in our list. + + To avoid frustration, this should list all missing keys, not fail on the + first missing key. + """ + errors = 0 + econst = expected_constants + rconst = rmrclib.get_constants() + for key in econst: # test all expected constants + if key not in rconst: # expected value not listed by rmr + errors += 1 + print("did not find required constant in list from RMR: %s" % key) + + assert errors == 0 + + +def test_get_mapping_dict(expected_states): + """ + test getting mapping string + """ + assert rmrclib.get_mapping_dict() == expected_states + assert rmrclib.state_to_status(0) == "RMR_OK" + assert rmrclib.state_to_status(12) == "RMR_ERR_TIMEOUT" + assert rmrclib.state_to_status(666) == "UNKNOWN STATE" -- 2.16.6