From a03c517f024363d54865cf75f8e566bb446decf5 Mon Sep 17 00:00:00 2001 From: "Lott, Christopher (cl778h)" Date: Mon, 6 Jul 2020 15:13:07 -0400 Subject: [PATCH] Revise Alarm manager to send via RMR wormhole Read target host and port from environment variables ALARM_MGR_SERVICE_NAME and ALARM_MGR_SERVICE_PORT Bump version to 1.4.0 Issue-ID: RIC-529 Signed-off-by: Lott, Christopher (cl778h) Change-Id: I672fa35146fb1a71148e1a69efd67d8421372880 --- docs/alarm_api.rst | 13 ++++++++----- docs/release-notes.rst | 4 ++++ ricxappframe/alarm/alarm.py | 32 ++++++++++++++++++++++++-------- ricxappframe/alarm/exceptions.py | 23 +++++++++++++++++++++++ ricxappframe/rmr/rmr.py | 3 ++- ricxappframe/xapp_frame.py | 5 ++--- setup.py | 2 +- tests/fixtures/test_local.rt | 2 -- tests/test_alarm.py | 23 ++++++++++++++++++++--- 9 files changed, 84 insertions(+), 23 deletions(-) create mode 100644 ricxappframe/alarm/exceptions.py diff --git a/docs/alarm_api.rst b/docs/alarm_api.rst index 87b7914..0734075 100644 --- a/docs/alarm_api.rst +++ b/docs/alarm_api.rst @@ -14,13 +14,16 @@ methods for creating, raising and clearing alarms. The alarm feature reuses the `ricxappframe.rmr` subpackage for transporting alarm messages. That in turn requires the RMR -shared-object library to be available in a system library that is +shared-object library to be available in a system directory that is searched by default, usually something like /usr/local/lib. -The alarm feature sends messages using RMR message type -`RIC_ALARM_UPDATE` in the `ricxappframe.alarm.alarm` module, currently -value 13111. The Xapp's routing table must have one (or more) entries -for that message type. +The alarm feature opens a direct connection to the alarm manager +using the RMR library's wormhole feature, taking the host name and +port number from environment variables defined as the constants +`ALARM_MGR_SERVICE_NAME_ENV` and `ALARM_MGR_SERVICE_PORT_ENV` in +the `ricxappframe.alarm.alarm` module. The message type is set to +constant `RIC_ALARM_UPDATE` in the `ricxappframe.alarm.alarm` module, +currently 13111. The complete API for the Alarm feature appears below. diff --git a/docs/release-notes.rst b/docs/release-notes.rst index b4378f0..c122621 100644 --- a/docs/release-notes.rst +++ b/docs/release-notes.rst @@ -11,6 +11,10 @@ The format is based on `Keep a Changelog `__ and this project adheres to `Semantic Versioning `__. +[1.4.0] - 2020-07-06 +-------------------- +* Revise Alarm manager to send via RMR wormhole (`RIC-529 `_) + [1.3.0] - 2020-06-24 -------------------- * Add configuration-change API (`RIC-425 `_) diff --git a/ricxappframe/alarm/alarm.py b/ricxappframe/alarm/alarm.py index 2c8272d..3711165 100644 --- a/ricxappframe/alarm/alarm.py +++ b/ricxappframe/alarm/alarm.py @@ -16,16 +16,19 @@ # ================================================================================== """ Provides classes and methods to define, raise, reraise and clear alarms. -All actions are implemented by sending RMR messages to the Alarm Adapter -that comply with the JSON schema in file alarm-schema.json. +All actions are implemented by sending RMR messages to the Alarm Adapter. +The alarm target host and port are set by environment variables. The alarm +message contents comply with the JSON schema in file alarm-schema.json. """ from ctypes import c_void_p from enum import Enum, auto import json +import os import time from mdclogpy import Logger from ricxappframe.rmr import rmr +from ricxappframe.alarm.exceptions import InitFailed ############## # PRIVATE API @@ -40,6 +43,8 @@ RETRIES = 4 # constants RIC_ALARM_UPDATE = 110 +ALARM_MGR_SERVICE_NAME_ENV = "ALARM_MGR_SERVICE_NAME" +ALARM_MGR_SERVICE_PORT_ENV = "ALARM_MGR_SERVICE_PORT" # Publish dict keys as constants for convenience of client code. # Mixed lower/upper casing to comply with the Adapter JSON requirements. @@ -123,15 +128,15 @@ class AlarmDetail(dict): class AlarmManager: """ Provides an API for an Xapp to raise and clear alarms by sending messages - via RMR, which should route the messages to an Alarm Adapter. + via RMR directly to an Alarm Adapter. Requires environment variables + ALARM_MGR_SERVICE_NAME and ALARM_MGR_SERVICE_PORT with the destination host + (service) name and port number; raises an exception if not found. Parameters ---------- vctx: ctypes c_void_p Pointer to RMR context obtained by initializing RMR. The context is used to allocate space and send messages. - The RMR routing table must have a destination for message - type RIC_ALARM_UPDATE as defined in this module. managed_object_id: str The name of the managed object that raises alarms @@ -149,6 +154,16 @@ class AlarmManager: self.vctx = vctx self.managed_object_id = managed_object_id self.application_id = application_id + service = os.environ.get(ALARM_MGR_SERVICE_NAME_ENV, None) + port = os.environ.get(ALARM_MGR_SERVICE_PORT_ENV, None) + if service is None or port is None: + mdc_logger.error("init: missing env var(s) {0}, {1}".format(ALARM_MGR_SERVICE_NAME_ENV, ALARM_MGR_SERVICE_PORT_ENV)) + raise InitFailed + target = "{0}:{1}".format(service, port) + self._wormhole_id = rmr.rmr_wh_open(self.vctx, target.encode('utf-8')) + if rmr.rmr_wh_state(self.vctx, self._wormhole_id) != rmr.RMR_OK: + mdc_logger.error("init: failed to open wormhole to target {}".format(target)) + raise InitFailed def create_alarm(self, specific_problem: int, @@ -157,7 +172,7 @@ class AlarmManager: additional_info: str = ""): """ Convenience method that creates an alarm instance, an AlarmDetail object, - using cached values for managed object ID and application ID. + using cached values for the managed object ID and application ID. Parameters ---------- @@ -204,7 +219,8 @@ class AlarmManager: def _rmr_send_alarm(self, msg: dict): """ - Serializes the dict and sends the result via RMR using a predefined message type. + Serializes the dict and sends the result via RMR using a predefined message + type to the wormhole initialized at start. Parameters ---------- @@ -222,7 +238,7 @@ class AlarmManager: mtype=RIC_ALARM_UPDATE, gen_transaction_id=True) for _ in range(0, RETRIES): - sbuf = rmr.rmr_send_msg(self.vctx, sbuf) + sbuf = rmr.rmr_wh_send_msg(self.vctx, self._wormhole_id, sbuf) post_send_summary = rmr.message_summary(sbuf) mdc_logger.debug("_rmr_send_alarm: try {0} result is {1}".format(_, post_send_summary[rmr.RMR_MS_MSG_STATE])) # stop trying if RMR does not indicate retry diff --git a/ricxappframe/alarm/exceptions.py b/ricxappframe/alarm/exceptions.py new file mode 100644 index 0000000..c69bc4d --- /dev/null +++ b/ricxappframe/alarm/exceptions.py @@ -0,0 +1,23 @@ +# ================================================================================== +# Copyright (c) 2020 AT&T Intellectual Property. +# Copyright (c) 2020 Nokia +# +# 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. +# ================================================================================== +""" +Custom Exceptions +""" + + +class InitFailed(BaseException): + """alarm init failure, the manager is unusable""" diff --git a/ricxappframe/rmr/rmr.py b/ricxappframe/rmr/rmr.py index 34c6b00..d9519e5 100644 --- a/ricxappframe/rmr/rmr.py +++ b/ricxappframe/rmr/rmr.py @@ -720,7 +720,8 @@ def rmr_wh_open(vctx: c_void_p, target: c_char_p) -> c_int: vctx: ctypes c_void_p Pointer to RMR context target: str - name/IP and port combination of the target process; e.g., "localhost:6123" + Pointer to bytes built from the target process host name and port number + as a string; e.g., b'localhost:4550' Returns ------- diff --git a/ricxappframe/xapp_frame.py b/ricxappframe/xapp_frame.py index 0d37f28..11ea210 100644 --- a/ricxappframe/xapp_frame.py +++ b/ricxappframe/xapp_frame.py @@ -158,9 +158,8 @@ class _BaseXapp: New payload to set new_mtype: int (optional) New message type (replaces the received message) - retries: int (optional) - Number of times to retry at the application level before - throwing exception RMRFailure + retries: int (optional, default 100) + Number of times to retry at the application level Returns ------- diff --git a/setup.py b/setup.py index da1362e..d20eda6 100644 --- a/setup.py +++ b/setup.py @@ -32,7 +32,7 @@ def _long_descr(): setup( name="ricxappframe", - version="1.3.0", + version="1.4.0", packages=find_packages(exclude=["tests.*", "tests"]), author="O-RAN Software Community", description="Xapp and RMR framework for python", diff --git a/tests/fixtures/test_local.rt b/tests/fixtures/test_local.rt index b94ef0a..f9d14ce 100644 --- a/tests/fixtures/test_local.rt +++ b/tests/fixtures/test_local.rt @@ -1,7 +1,6 @@ # do NOT use localhost, seems unresolved on jenkins VMs # first 3 lines (port 4564) are used for xapp frame tests # next 4 lines (port 3563-3564) are used in the rmr submodule -# last line (port 4567) is used for alarms newrt|start mse| 60000 | -1 | 127.0.0.1:4564 mse| 60001 | -1 | 127.0.0.1:4564 @@ -10,5 +9,4 @@ mse| 0 | -1 | 127.0.0.1:3563 mse| 46656 | 777 | 127.0.0.1:3563 mse| 1 | -1 | 127.0.0.1:3564 mse| 2 | -1 | 127.0.0.1:3564 -mse| 110 | -1 | 127.0.0.1:4567 newrt|end diff --git a/tests/test_alarm.py b/tests/test_alarm.py index 0fad272..0cde0ce 100644 --- a/tests/test_alarm.py +++ b/tests/test_alarm.py @@ -15,9 +15,11 @@ # limitations under the License. # ================================================================================== import json +import pytest import time from ricxappframe.alarm import alarm -from ricxappframe.alarm.alarm import AlarmAction, AlarmDetail, AlarmManager, AlarmSeverity +from ricxappframe.alarm.alarm import AlarmAction, AlarmDetail, AlarmManager, AlarmSeverity, ALARM_MGR_SERVICE_NAME_ENV, ALARM_MGR_SERVICE_PORT_ENV +from ricxappframe.alarm.exceptions import InitFailed from ricxappframe.rmr import rmr MRC_SEND = None @@ -47,7 +49,7 @@ def teardown_module(): rmr.rmr_close(MRC_SEND) -def test_alarm_set_get(): +def test_alarm_set_get(monkeypatch): """ test set functions """ @@ -65,6 +67,19 @@ def test_alarm_set_get(): assert det[alarm.KEY_IDENTIFYING_INFO] == "4" assert det[alarm.KEY_ADDITIONAL_INFO] == "5" + # missing environment variables + with pytest.raises(InitFailed): + alarm.AlarmManager(MRC_SEND, "missing", "envvars") + + # invalid environment variables + monkeypatch.setenv(ALARM_MGR_SERVICE_NAME_ENV, "0") + monkeypatch.setenv(ALARM_MGR_SERVICE_PORT_ENV, "a") + with pytest.raises(InitFailed): + alarm.AlarmManager(MRC_SEND, "bogus", "envvars") + + # good environment variables + monkeypatch.setenv(ALARM_MGR_SERVICE_NAME_ENV, "localhost") + monkeypatch.setenv(ALARM_MGR_SERVICE_PORT_ENV, "4567") # any int is ok here mgr = alarm.AlarmManager(MRC_SEND, "moid2", "appid2") assert mgr is not None assert mgr.managed_object_id == "moid2" @@ -86,10 +101,12 @@ def _receive_alarm_msg(action: AlarmAction): assert data[alarm.KEY_ALARM_ACTION] == action.name -def test_alarm_manager(): +def test_alarm_manager(monkeypatch): """ test send functions and ensure a message arrives """ + monkeypatch.setenv(ALARM_MGR_SERVICE_NAME_ENV, "localhost") + monkeypatch.setenv(ALARM_MGR_SERVICE_PORT_ENV, "4567") # must match rcv port above mgr = AlarmManager(MRC_SEND, "moid", "appid") assert mgr is not None -- 2.16.6