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.
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>`_)
# ==================================================================================
"""
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
# 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.
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
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,
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
----------
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
----------
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
--- /dev/null
+# ==================================================================================
+# 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"""
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
-------
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
-------
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",
# 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
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
# 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
rmr.rmr_close(MRC_SEND)
-def test_alarm_set_get():
+def test_alarm_set_get(monkeypatch):
"""
test set functions
"""
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"
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