Revise Alarm manager to send via RMR wormhole 23/4323/2
authorLott, Christopher (cl778h) <cl778h@att.com>
Mon, 6 Jul 2020 19:13:07 +0000 (15:13 -0400)
committerLott, Christopher (cl778h) <cl778h@att.com>
Mon, 6 Jul 2020 19:19:26 +0000 (15:19 -0400)
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) <cl778h@att.com>
Change-Id: I672fa35146fb1a71148e1a69efd67d8421372880

docs/alarm_api.rst
docs/release-notes.rst
ricxappframe/alarm/alarm.py
ricxappframe/alarm/exceptions.py [new file with mode: 0644]
ricxappframe/rmr/rmr.py
ricxappframe/xapp_frame.py
setup.py
tests/fixtures/test_local.rt
tests/test_alarm.py

index 87b7914..0734075 100644 (file)
@@ -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.
 
index b4378f0..c122621 100644 (file)
@@ -11,6 +11,10 @@ The format is based on `Keep a Changelog <http://keepachangelog.com/>`__
 and this project adheres to `Semantic Versioning <http://semver.org/>`__.
 
 
+[1.4.0] - 2020-07-06
+--------------------
+* Revise Alarm manager to send via RMR wormhole (`RIC-529 <https://jira.o-ran-sc.org/browse/RIC-529>`_)
+
 [1.3.0] - 2020-06-24
 --------------------
 * Add configuration-change API (`RIC-425 <https://jira.o-ran-sc.org/browse/RIC-425>`_)
index 2c8272d..3711165 100644 (file)
 # ==================================================================================
 """
 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 (file)
index 0000000..c69bc4d
--- /dev/null
@@ -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"""
index 34c6b00..d9519e5 100644 (file)
@@ -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
     -------
index 0d37f28..11ea210 100644 (file)
@@ -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
         -------
index da1362e..d20eda6 100644 (file)
--- 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",
index b94ef0a..f9d14ce 100644 (file)
@@ -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
index 0fad272..0cde0ce 100644 (file)
 #   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