def __init__(self, rmr_port=4562, rmr_wait_for_ready=True, use_fake_sdl=False, post_init=None): """ Documented in the class comment. """ # PUBLIC, can be used by xapps using self.(name): self.logger = Logger(name=__name__) # Start rmr rcv thread self._rmr_loop = xapp_rmr.RmrLoop(port=rmr_port, wait_for_ready=rmr_wait_for_ready) self._mrc = self._rmr_loop.mrc # for convenience # SDL self.sdl = SDLWrapper(use_fake_sdl) # Config # The environment variable specifies the path to the Xapp config file self._config_path = os.environ.get(CONFIG_FILE_ENV, None) if self._config_path and os.path.isfile(self._config_path): self._inotify = inotify_simple.INotify() self._inotify.add_watch(self._config_path, inotify_simple.flags.MODIFY) self.logger.debug("__init__: watching config file {}".format(self._config_path)) else: self._inotify = None self.logger.warning("__init__: NOT watching any config file") # run the optionally provided user post init if post_init: post_init(self)
def __init__(self, rmr_port=4562, rmr_wait_for_ready=True, use_fake_sdl=False, post_init=None): """ Documented in the class comment. """ # PUBLIC, can be used by xapps using self.(name): self.logger = Logger(name=__name__) self._appthread = None # Start rmr rcv thread self._rmr_loop = xapp_rmr.RmrLoop(port=rmr_port, wait_for_ready=rmr_wait_for_ready) self._mrc = self._rmr_loop.mrc # for convenience # SDL self.sdl = SDLWrapper(use_fake_sdl) # Config # The environment variable specifies the path to the Xapp config file self._config_path = os.environ.get(Constants.CONFIG_FILE_ENV, None) if self._config_path and os.path.isfile(self._config_path): self._inotify = inotify_simple.INotify() self._inotify.add_watch(self._config_path, inotify_simple.flags.MODIFY) self.logger.debug("__init__: watching config file {}".format( self._config_path)) else: self._inotify = None self.logger.warning("__init__: NOT watching any config file") # used for thread control of Registration of Xapp self._keep_registration = True # configuration data for xapp registration and deregistration self._config_data = None if self._config_path and os.path.isfile(self._config_path): with open(self._config_path) as json_file: self._config_data = json.load(json_file) else: self._keep_registration = False self.logger.error( "__init__: Cannot Read config file for xapp Registration") self._config_data = {} self._appthread = Thread(target=self.registerXapp).start() # run the optionally provided user post init if post_init: post_init(self)
def setup_module(): """module level setup""" # swap sdl for the fake backend data.SDL = SDLWrapper(use_fake_sdl=True) def noop(): pass # launch the thread with a fake init func and a patched rcv func; we will "repatch" later a1rmr.start_rmr_thread(init_func_override=noop, rcv_func_override=_fake_dequeue_none)
def test_sdl_set_get(): """ test set, get realted sdl methods """ sdl = SDLWrapper(use_fake_sdl=True) # set_if sdl.set(NS, "gs.df1", "old") assert sdl.get(NS, "gs.df1") == "old" sdl.set_if(NS, "gs.df1", "young", "new") assert sdl.get(NS, "gs.df1") == "old" sdl.set_if(NS, "gs.df1", "old", "new") assert sdl.get(NS, "gs.df1") == "new" # set_if_not_exists sdl.set(NS, "gs.df2", "old") assert sdl.get(NS, "gs.df2") == "old" sdl.set_if_not_exists(NS, "gs.df2", "new") assert sdl.get(NS, "gs.df2") == "old" sdl.set_if_not_exists(NS, "gs.df3", "new") assert sdl.get(NS, "gs.df3") == "new" # find_keys assert sdl.find_keys(NS, "gs") == ["gs.df1", "gs.df2", "gs.df3"] assert sdl.find_keys(NS, "gs.df1") == ["gs.df1"] assert sdl.find_keys(NS, "gs.df2") == ["gs.df2"] assert sdl.find_keys(NS, "gs.df3") == ["gs.df3"] # delete_if sdl.set(NS, "gs.df4", "delete_this") assert sdl.delete_if(NS, "gs.df4", "delete") is False assert sdl.delete_if(NS, "gs.df4", "delete_this") is True assert sdl.get(NS, "gs.df4") is None
def test_sdl(): """ test raw sdl functions """ sdl = SDLWrapper(use_fake_sdl=True) sdl.set(NS, "as.df1", "data") sdl.set(NS, "as.df2", "data2") assert sdl.get(NS, "as.df1") == "data" assert sdl.get(NS, "as.df2") == "data2" assert sdl.find_and_get(NS, "as.df1") == {"as.df1": "data"} assert sdl.find_and_get(NS, "as.df2") == {"as.df2": "data2"} assert sdl.find_and_get(NS, "as.df") == { "as.df1": "data", "as.df2": "data2" } assert sdl.find_and_get(NS, "as.d") == { "as.df1": "data", "as.df2": "data2" } assert sdl.find_and_get(NS, "as.") == {"as.df1": "data", "as.df2": "data2"} assert sdl.find_and_get(NS, "asd") == {} # delete 1 sdl.delete(NS, "as.df1") assert sdl.get(NS, "as.df1") is None assert sdl.get(NS, "as.df2") == "data2" # delete 2 sdl.delete(NS, "as.df2") assert sdl.get(NS, "as.df2") is None assert sdl.find_and_get(NS, "as.df") == {} assert sdl.find_and_get(NS, "") == {}
def test_sdl_remove_and_publish_with_start_event_listener(): """ test remove_and_publish* related sdl methods """ CH = "channel" EVENT = "event" CALLED = None def cb(channel, event): nonlocal CH nonlocal EVENT nonlocal CALLED CALLED = True assert channel == CH assert event[0] == EVENT sdl = SDLWrapper(use_fake_sdl=True) sdl.subscribe_channel(NS, cb, "channel") sdl.start_event_listener() # remove_and_publish success CALLED = False sdl.set(NS, "nt.df1", "old") sdl.remove_and_publish(NS, "channel", "event", "nt.df1") time.sleep(0.3) assert sdl.get(NS, "nt.df1") is None assert CALLED is True # remove_if_and_publish CALLED = False sdl.set(NS, "nt.df1", "old") # fail sdl.remove_if_and_publish(NS, "channel", "event", "nt.df1", "new") time.sleep(0.3) assert sdl.get(NS, "nt.df1") == "old" assert CALLED is False # success sdl.remove_if_and_publish(NS, "channel", "event", "nt.df1", "old") time.sleep(0.3) assert sdl.get(NS, "nt.df1") is None assert CALLED is True # remove_all_and_publish CALLED = False sdl.set(NS, "nt.df1", "data1") sdl.set(NS, "nt.df2", "data2") sdl.set(NS, "nt.df3", "data3") sdl.remove_all_and_publish(NS, "channel", "event") time.sleep(0.3) assert sdl.get(NS, "nt.df1") is None assert sdl.get(NS, "nt.df2") is None assert sdl.get(NS, "nt.df3") is None assert sdl.find_keys(NS, "*") == [] assert CALLED is True sdl.unsubscribe_channel(NS, "channel")
def test_sdl_set_and_publish_with_handle_events(): """ test set_and_publish* related sdl methods """ CH = "channel" EVENT = "event" CALLED = None def cb(channel, event): nonlocal CH nonlocal EVENT nonlocal CALLED # test is cb called CALLED = True assert channel == CH assert event[0] == EVENT sdl = SDLWrapper(use_fake_sdl=True) sdl.subscribe_channel(NS, cb, "channel") # set_and_publish CALLED = False sdl.set_and_publish(NS, "channel", "event", "nt.df1", "old") sdl.handle_events() assert sdl.get(NS, "nt.df1") == "old" assert CALLED is True # set_if_and_publish fail CALLED = False sdl.set_if_and_publish(NS, "channel", "event", "nt.df1", "young", "new") sdl.handle_events() assert sdl.get(NS, "nt.df1") == "old" assert CALLED is False # set_if_and_publish success sdl.set_if_and_publish(NS, "channel", "event", "nt.df1", "old", "new") sdl.handle_events() assert sdl.get(NS, "nt.df1") == "new" assert CALLED is True # set_if_not_exists_and_publish fail CALLED = False sdl.set_if_not_exists_and_publish(NS, "channel", "event", "nt.df1", "latest") sdl.handle_events() assert sdl.get(NS, "nt.df1") == "new" assert CALLED is False # set_if_not_exists_and_publish success sdl.set_if_not_exists_and_publish(NS, "channel", "event", "nt.df2", "latest") sdl.handle_events() assert sdl.get(NS, "nt.df2") == "latest" assert CALLED is True sdl.unsubscribe_channel(NS, "channel")
def test_sdl_member(): """ test member related sdl methods """ # add_member, remove_member, get_members sdl = SDLWrapper(use_fake_sdl=True) sdl.add_member(NS, "group1", "member1") assert sdl.is_member(NS, "group1", "member1") is True sdl.remove_member(NS, "group1", "not_member") assert sdl.is_member(NS, "group1", "member1") is True sdl.remove_member(NS, "group1", "member1") assert sdl.is_member(NS, "group1", "member1") is False # remove_group, group_size sdl.add_member(NS, "group2", "member1") sdl.add_member(NS, "group2", "member2") assert sdl.group_size(NS, "group2") == 2 sdl.remove_group(NS, "group2") assert sdl.group_size(NS, "group2") == 0 # get_members sdl.add_member(NS, "group3", "member1") sdl.add_member(NS, "group3", "member2") members = sdl.get_members(NS, "group3") assert "member1" in members assert "member2" in members
import json import os import pandas as pd import schedule from ricxappframe.xapp_frame import Xapp from ad_model.ad_model import ad_predict, CAUSE from ad_train import train from ricxappframe.xapp_sdl import SDLWrapper from database import DATABASE, DUMMY import insert as ins db = None cp = None ue_data = None # needs to be updated in future when live feed will be coming through KPIMON to influxDB pos = 0 sdl = SDLWrapper(use_fake_sdl=True) def entry(self): """ If ML model is not present in the path, It will trigger training module to train the model. Calls predict function every 10 millisecond(for now as we are using simulated data). """ if not os.path.isfile('model'): train() schedule.every(0.01).seconds.do(predict, self) while True: schedule.run_pending() def predict(self): """Read the latest ue sample from influxDB and detects if that is anomalous or normal..
class _BaseXapp: """ This class initializes RMR, starts a thread that checks for incoming messages, provisions an SDL object and optionally creates a config-file watcher. This private base class should not be instantiated by clients directly, but it defines many public methods that may be used by clients. If environment variable CONFIG_FILE is defined, and that variable contains a path to an existing file, a watcher is defined to monitor modifications (writes) to that file using the Linux kernel's inotify feature. The watcher must be polled by calling method config_check(). Parameters ---------- rmr_port: int (optional, default is 4562) Port on which the RMR library listens for incoming messages. rmr_wait_for_ready: bool (optional, default is True) If this is True, then init waits until RMR is ready to send, which includes having a valid routing file. This can be set to False if the client wants to *receive only*. use_fake_sdl: bool (optional, default is False) if this is True, it uses the DBaaS "fake dict backend" instead of Redis or other backends. Set this to True when developing an xapp or during unit testing to eliminate the need for DBaaS. post_init: function (optional, default is None) Runs this user-provided function at the end of the init method; its signature should be post_init(self) """ def __init__(self, rmr_port=4562, rmr_wait_for_ready=True, use_fake_sdl=False, post_init=None): """ Documented in the class comment. """ # PUBLIC, can be used by xapps using self.(name): self.logger = Logger(name=__name__) self._appthread = None # Start rmr rcv thread self._rmr_loop = xapp_rmr.RmrLoop(port=rmr_port, wait_for_ready=rmr_wait_for_ready) self._mrc = self._rmr_loop.mrc # for convenience # SDL self.sdl = SDLWrapper(use_fake_sdl) # Config # The environment variable specifies the path to the Xapp config file self._config_path = os.environ.get(Constants.CONFIG_FILE_ENV, None) if self._config_path and os.path.isfile(self._config_path): self._inotify = inotify_simple.INotify() self._inotify.add_watch(self._config_path, inotify_simple.flags.MODIFY) self.logger.debug("__init__: watching config file {}".format( self._config_path)) else: self._inotify = None self.logger.warning("__init__: NOT watching any config file") # used for thread control of Registration of Xapp self._keep_registration = True # configuration data for xapp registration and deregistration self._config_data = None if self._config_path and os.path.isfile(self._config_path): with open(self._config_path) as json_file: self._config_data = json.load(json_file) else: self._keep_registration = False self.logger.error( "__init__: Cannot Read config file for xapp Registration") self._config_data = {} self._appthread = Thread(target=self.registerXapp).start() # run the optionally provided user post init if post_init: post_init(self) def get_service(self, host, service): """ To find the url for connecting to the service Parameters ---------- host: string defines the hostname in the url service: string defines the servicename in the url Returns ------- string url for the service """ app_namespace = self._config_data.get("APP_NAMESPACE") if app_namespace is None: app_namespace = Constants.DEFAULT_XAPP_NS self.logger.debug("service : {} host : {},appnamespace : {}".format( service, host, app_namespace)) if app_namespace is not None and host is not None: svc = service.format(app_namespace.upper(), host.upper()) urlkey = svc.replace("-", "_") url = os.environ.get(urlkey).split("//") self.logger.debug("Service urlkey : {} and url: {}".format( urlkey, url)) if len(url) > 1: return url[1] return "" def do_post(self, plt_namespace, url, msg): """ registration of the xapp using the url and json msg Parameters ---------- plt_namespace: string platform namespace where the xapp is running url: string url for xapp registration msg: string json msg containing the xapp details Returns ------- bool whether or not the xapp is registered """ if url is None: self.logger.error("url is empty ") return False if plt_namespace is None: self.logger.error("plt_namespace is empty") return False try: request_url = url.format(plt_namespace, plt_namespace) resp = requests.post(request_url, json=msg) self.logger.debug("Post to '{}' done, status : {}".format( request_url, resp.status_code)) self.logger.debug("Response Text : {}".format(resp.text)) return resp.status_code == 200 or resp.status_code == 201 except requests.exceptions.RequestException as err: self.logger.error("Error : {}".format(err)) return format(err) except requests.exceptions.HTTPError as errh: self.logger.error("Http Error: {}".format(errh)) return errh except requests.exceptions.ConnectionError as errc: self.logger.error("Error Connecting: {}".format(errc)) return errc except requests.exceptions.Timeout as errt: self.logger.error("Timeout Error: {}".format(errt)) return errt def register(self): """ function to registers the xapp Returns ------- bool whether or not the xapp is registered """ hostname = os.environ.get("HOSTNAME") xappname = self._config_data.get("name") xappversion = self._config_data.get("version") pltnamespace = os.environ.get("PLT_NAMESPACE") if pltnamespace is None: pltnamespace = Constants.DEFAULT_PLT_NS self.logger.debug( "config details hostname : {} xappname: {} xappversion : {} pltnamespace : {}" .format(hostname, xappname, xappversion, pltnamespace)) http_endpoint = self.get_service(hostname, Constants.SERVICE_HTTP) rmr_endpoint = self.get_service(hostname, Constants.SERVICE_RMR) if http_endpoint == "" or rmr_endpoint == "": self.logger.error( "Couldn't resolve service endpoints: http_endpoint={} rmr_endpoint={}" .format(http_endpoint, rmr_endpoint)) return False self.logger.debug( "config details hostname : {} xappname: {} xappversion : {} pltnamespace : {} http_endpoint : {} rmr_endpoint " ": {} configpath : {}".format( hostname, xappname, xappversion, pltnamespace, http_endpoint, rmr_endpoint, self._config_data.get("CONFIG_PATH"))) request_string = { "appName": hostname, "appVersion": xappversion, "configPath": "", "appInstanceName": xappname, "httpEndpoint": http_endpoint, "rmrEndpoint": rmr_endpoint, "config": json.dumps(self._config_data) } self.logger.info("REQUEST STRING :{}".format(request_string)) return self.do_post(pltnamespace, Constants.REGISTER_PATH, request_string) def registerXapp(self): """ registers the xapp """ retries = 5 while self._keep_registration and retries > 0: time.sleep(2) retries = retries - 1 # checking for rmr/sdl/xapp health healthy = self.healthcheck() if not healthy: self.logger.warning( "Application='{}' is not ready yet, waiting ...".format( self._config_data.get("name"))) continue self.logger.debug( "Application='{}' is now up and ready, continue with registration ..." .format(self._config_data.get("name"))) if self.register(): self.logger.debug( "Registration done, proceeding with startup ...") break def deregister(self): """ Deregisters the xapp Returns ------- bool whether or not the xapp is registered """ healthy = self.healthcheck() if not healthy: self.logger.error("RMR or SDL or xapp == Not Healthy") return None if self._config_data is None: return None name = os.environ.get("HOSTNAME") xappname = self._config_data.get("name") pltnamespace = os.environ.get("PLT_NAMESPACE") if pltnamespace is None: pltnamespace = Constants.DEFAULT_PLT_NS request_string = { "appName": name, "appInstanceName": xappname, } return self.do_post(pltnamespace, Constants.DEREGISTER_PATH, request_string) def xapp_shutdown(self): """ Deregisters the xapp while shutting down """ self.deregister() self.logger.debug("Wait for xapp to get unregistered") time.sleep(10) # Public rmr methods def rmr_get_messages(self): """ Returns a generator iterable over all items in the queue that have not yet been read by the client xapp. Each item is a tuple (S, sbuf) where S is a message summary dict and sbuf is the raw message. The caller MUST call rmr.rmr_free_msg(sbuf) when finished with each sbuf to prevent memory leaks! """ while not self._rmr_loop.rcv_queue.empty(): (summary, sbuf) = self._rmr_loop.rcv_queue.get() yield (summary, sbuf) def rmr_send(self, payload, mtype, retries=100): """ Allocates a buffer, sets payload and mtype, and sends Parameters ---------- payload: bytes payload to set mtype: int message type retries: int (optional) Number of times to retry at the application level before excepting RMRFailure Returns ------- bool whether or not the send worked after retries attempts """ sbuf = rmr.rmr_alloc_msg(vctx=self._mrc, size=len(payload), payload=payload, gen_transaction_id=True, mtype=mtype) for _ in range(retries): sbuf = rmr.rmr_send_msg(self._mrc, sbuf) if sbuf.contents.state == 0: self.rmr_free(sbuf) return True self.rmr_free(sbuf) return False def rmr_rts(self, sbuf, new_payload=None, new_mtype=None, retries=100): """ Allows the xapp to return to sender, possibly adjusting the payload and message type before doing so. This does NOT free the sbuf for the caller as the caller may wish to perform multiple rts per buffer. The client needs to free. Parameters ---------- sbuf: ctypes c_void_p Pointer to an rmr message buffer new_payload: bytes (optional) New payload to set new_mtype: int (optional) New message type (replaces the received message) retries: int (optional, default 100) Number of times to retry at the application level Returns ------- bool whether or not the send worked after retries attempts """ for _ in range(retries): sbuf = rmr.rmr_rts_msg(self._mrc, sbuf, payload=new_payload, mtype=new_mtype) if sbuf.contents.state == 0: return True self.logger.warning("RTS Failed! Summary: {}".format( rmr.message_summary(sbuf))) return False def rmr_free(self, sbuf): """ Frees an rmr message buffer after use Note: this does not need to be a class method, self is not used. However if we break it out as a function we need a home for it. Parameters ---------- sbuf: ctypes c_void_p Pointer to an rmr message buffer """ rmr.rmr_free_msg(sbuf) # Convenience (pass-thru) function for invoking SDL. def sdl_set(self, namespace, key, value, usemsgpack=True): """ ** Deprecate Warning ** ** Will be removed in a future function ** Stores a key-value pair to SDL, optionally serializing the value to bytes using msgpack. Parameters ---------- namespace: string SDL namespace key: string SDL key value: Object or byte array to store. See the `usemsgpack` parameter. usemsgpack: boolean (optional, default is True) Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function `packb` is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes. """ self.sdl.set(namespace, key, value, usemsgpack) def sdl_get(self, namespace, key, usemsgpack=True): """ ** Deprecate Warning ** ** Will be removed in a future function ** Gets the value for the specified namespace and key from SDL, optionally deserializing stored bytes using msgpack. Parameters ---------- namespace: string SDL namespace key: string SDL key usemsgpack: boolean (optional, default is True) If usemsgpack is True, the byte array stored by SDL is deserialized using msgpack to yield the original object that was stored. If usemsgpack is False, the byte array stored by SDL is returned without further processing. Returns ------- Value See the usemsgpack parameter for an explanation of the returned value type. Answers None if the key is not found. """ return self.sdl.get(namespace, key, usemsgpack) def sdl_find_and_get(self, namespace, prefix, usemsgpack=True): """ ** Deprecate Warning ** ** Will be removed in a future function ** Gets all key-value pairs in the specified namespace with keys that start with the specified prefix, optionally deserializing stored bytes using msgpack. Parameters ---------- nnamespaces: string SDL namespace prefix: string the key prefix usemsgpack: boolean (optional, default is True) If usemsgpack is True, the byte array stored by SDL is deserialized using msgpack to yield the original value that was stored. If usemsgpack is False, the byte array stored by SDL is returned without further processing. Returns ------- Dictionary of key-value pairs Each key has the specified prefix. The value object (its type) depends on the usemsgpack parameter, but is either a Python object or raw bytes as discussed above. Answers an empty dictionary if no keys matched the prefix. """ return self.sdl.find_and_get(namespace, prefix, usemsgpack) def sdl_delete(self, namespace, key): """ ** Deprecate Warning ** ** Will be removed in a future function ** Deletes the key-value pair with the specified key in the specified namespace. Parameters ---------- namespace: string SDL namespace key: string SDL key """ self.sdl.delete(namespace, key) def _get_rnib_info(self, node_type): """ Since the difference between get_list_gnb_ids and get_list_enb_ids is only node-type, this function extracted from the duplicated logic. Parameters ---------- node_type: string Type of node. This is EnumDescriptor. Available node types - UNKNOWN - ENB - GNB Returns ------- List: (NbIdentity) Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ nbid_strings: Set[bytes] = self.sdl.get_members( sdl_namespaces.E2_MANAGER, node_type, usemsgpack=False) ret: List[NbIdentity] = [] for nbid_string in nbid_strings: nbid = NbIdentity() nbid.ParseFromString(nbid_string) ret.append(nbid) return ret def get_list_gnb_ids(self): """ Retrieves the list of gNodeb identity entities gNodeb information is stored in SDL by E2Manager. Therefore, gNode information is stored in SDL's `e2Manager` namespace as protobuf serialized. Returns ------- List: (NbIdentity) Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ return self._get_rnib_info(Node.Type.Name(Node.GNB)) def get_list_enb_ids(self): """ Retrieves the list of eNodeb identity entities eNodeb information is stored in SDL by E2Manager. Therefore, eNode information is stored in SDL's `e2Manager` namespace as protobuf serialized. Returns ------- List: (NbIdentity) Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ return self._get_rnib_info(Node.Type.Name(Node.ENB)) """ Following RNIB methods are made to be inline of the go-lang based RNIB methods. Method names are same as in repository: gerrit.o-ran-sc.org/r/ric-plt/xapp-frame/pkg/rnib """ def GetNodeb(self, inventoryName): """ Returns nodeb info In RNIB SDL key is defined following way: RAN:<inventoryName> Parameters ---------- inventoryName: string Returns ------- NodebInfo() Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ nbid_string: Set[bytes] = self.sdl_get(sdl_namespaces.E2_MANAGER, 'RAN:' + inventoryName, usemsgpack=False) if nbid_string is not None: nbinfo = pb_nbi.NodebInfo() nbinfo.ParseFromString(nbid_string) return nbinfo return None def GetNodebByGlobalNbId(self, nodeType, plmnId, nbId): """ Returns nodeb identity based on type, plmn id and node id In RNIB SDL key is defined following way: <nodeType>:<plmnId>:<nbId> Parameters ---------- nodeType: string plmnId: string nbId: string Returns ------- NbIdentity() Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ nbid_string: Set[bytes] = self.sdl_get(sdl_namespaces.E2_MANAGER, nodeType + ':' + plmnId + ':' + nbId, usemsgpack=False) if nbid_string is not None: nbid = NbIdentity() nbid.ParseFromString(nbid_string) return nbid return None def GetCellList(self, inventoryName): """ Returns nodeb served cell list from the saved node data In RNIB SDL key is defined following way: RAN:<inventoryName> Parameters ---------- nodeType: string plmnId: string nbId: string Returns ------- ServedCellInfo() in case of ENB ServedNRCell() in case of GNB Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ nodeb = self.GetNodeb(inventoryName) if nodeb is not None: if nodeb.HasField('enb'): return nodeb.enb.served_cells elif nodeb.HasField('gnb'): return nodeb.gnb.served_nr_cells return None def GetCellById(self, cell_type, cell_id): """ Returns cell info by cell type and id. In RNIB SDL keys are defined based on the cell type: ENB type CELL:<cell_id> GNB type NRCELL:<cell_id> Parameters ---------- cell_type: string Available cell types - ENB - GNB Returns ------- Cell() Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ cellstr = None if cell_type == pb_cell.Cell.Type.Name(pb_cell.Cell.LTE_CELL): cellstr = 'CELL' elif cell_type == pb_cell.Cell.Type.Name(pb_cell.Cell.NR_CELL): cellstr = 'NRCELL' if cellstr is not None: cell_string: Set[bytes] = self.sdl_get(sdl_namespaces.E2_MANAGER, cellstr + ':' + cell_id, usemsgpack=False) if cell_string is not None: cell = pb_cell.Cell() cell.ParseFromString(cell_string) return cell return None def GetListNodebIds(self): """ Returns both enb and gnb NbIdentity list Returns ------- List: (NbIdentity) Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ nlist1 = self._get_rnib_info(Node.Type.Name(Node.ENB)) nlist2 = self._get_rnib_info(Node.Type.Name(Node.GNB)) for n in nlist2: nlist1.append(n) return nlist1 def GetCell(self, inventoryName, pci): """ Returns cell info using pci In RNIB SDL key is defined following way: PCI:<inventoryName>:<pci hex val> Parameters ---------- inventoryName: string pci: int Returns ------- Cell() Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ cell_string: Set[bytes] = self.sdl_get(sdl_namespaces.E2_MANAGER, 'PCI:{0:s}:{1:02x}'.format( inventoryName, pci), usemsgpack=False) if cell_string is not None: cell = pb_cell.Cell() cell.ParseFromString(cell_string) return cell return None def GetRanFunctionDefinition(self, inventoryName, ran_function_oid): """ Returns GNB ran function definition list based on the ran_function_oid In RNIB SDL key is defined following way: RAN:<inventoryName> Parameters ---------- inventoryName: string ran_function_oid: int Returns ------- array of ran_function_definition matching to ran_function_oid Raises ------ SdlTypeError: If function's argument is of an inappropriate type. NotConnected: If SDL is not connected to the backend data storage. RejectedByBackend: If backend data storage rejects the request. BackendError: If the backend data storage fails to process the request. """ nodeb = self.GetNodeb(inventoryName) if nodeb is not None: if nodeb.HasField('gnb') and nodeb.gnb.ran_functions is not None: ranFDList = [] for rf in nodeb.gnb.ran_functions: if rf.ran_function_oid == ran_function_oid: ranFDList.append(rf.ran_function_definition) return ranFDList return None def healthcheck(self): """ this needs to be understood how this is supposed to work """ return self._rmr_loop.healthcheck() and self.sdl.healthcheck() # Convenience function for discovering config change events def config_check(self, timeout=0): """ Checks the watcher for configuration-file events. The watcher prerequisites and event mask are documented in __init__(). Parameters ---------- timeout: int (optional) Number of seconds to wait for a configuration-file event, default 0. Returns ------- List of Events, possibly empty An event is a tuple with objects wd, mask, cookie and name. For example:: Event(wd=1, mask=1073742080, cookie=0, name='foo') """ if not self._inotify: return [] events = self._inotify.read(timeout=timeout) return list(events) def stop(self): """ cleans up and stops the xapp rmr thread (currently). This is critical for unit testing as pytest will never return if the thread is running. TODO: can we register a ctrl-c handler so this gets called on ctrl-c? Because currently two ctrl-c are needed to stop. """ if self._appthread is not None: self._appthread.join() self.xapp_shutdown() self._rmr_loop.stop()
# constants INSTANCE_DELETE_NO_RESP_TTL = int( os.environ.get("INSTANCE_DELETE_NO_RESP_TTL", 5)) INSTANCE_DELETE_RESP_TTL = int(os.environ.get("INSTANCE_DELETE_RESP_TTL", 5)) USE_FAKE_SDL = bool( distutils.util.strtobool(os.environ.get("USE_FAKE_SDL", "False"))) A1NS = "A1m_ns" TYPE_PREFIX = "a1.policy_type." INSTANCE_PREFIX = "a1.policy_instance." METADATA_PREFIX = "a1.policy_inst_metadata." HANDLER_PREFIX = "a1.policy_handler." mdc_logger = Logger(name=__name__) if USE_FAKE_SDL: mdc_logger.debug("Using fake SDL") SDL = SDLWrapper(use_fake_sdl=USE_FAKE_SDL) # Internal helpers def _generate_type_key(policy_type_id): """ generate a key for a policy type """ return "{0}{1}".format(TYPE_PREFIX, policy_type_id) def _generate_instance_key(policy_type_id, policy_instance_id): """ generate a key for a policy instance """
class _BaseXapp: """ This class initializes RMR, starts a thread that checks for incoming messages, provisions an SDL object and optionally creates a config-file watcher. This private base class should not be instantiated by clients directly, but it defines many public methods that may be used by clients. If environment variable CONFIG_FILE is defined, and that variable contains a path to an existing file, a watcher is defined to monitor modifications (writes) to that file using the Linux kernel's inotify feature. The watcher must be polled by calling method config_check(). Parameters ---------- rmr_port: int (optional, default is 4562) Port on which the RMR library listens for incoming messages. rmr_wait_for_ready: bool (optional, default is True) If this is True, then init waits until RMR is ready to send, which includes having a valid routing file. This can be set to False if the client wants to *receive only*. use_fake_sdl: bool (optional, default is False) if this is True, it uses the DBaaS "fake dict backend" instead of Redis or other backends. Set this to True when developing an xapp or during unit testing to eliminate the need for DBaaS. post_init: function (optional, default is None) Runs this user-provided function at the end of the init method; its signature should be post_init(self) """ def __init__(self, rmr_port=4562, rmr_wait_for_ready=True, use_fake_sdl=False, post_init=None): """ Documented in the class comment. """ # PUBLIC, can be used by xapps using self.(name): self.logger = Logger(name=__name__) # Start rmr rcv thread self._rmr_loop = xapp_rmr.RmrLoop(port=rmr_port, wait_for_ready=rmr_wait_for_ready) self._mrc = self._rmr_loop.mrc # for convenience # SDL self.sdl = SDLWrapper(use_fake_sdl) # Config # The environment variable specifies the path to the Xapp config file self._config_path = os.environ.get(CONFIG_FILE_ENV, None) if self._config_path and os.path.isfile(self._config_path): self._inotify = inotify_simple.INotify() self._inotify.add_watch(self._config_path, inotify_simple.flags.MODIFY) self.logger.debug("__init__: watching config file {}".format(self._config_path)) else: self._inotify = None self.logger.warning("__init__: NOT watching any config file") # run the optionally provided user post init if post_init: post_init(self) # Public rmr methods def rmr_get_messages(self): """ Returns a generator iterable over all items in the queue that have not yet been read by the client xapp. Each item is a tuple (S, sbuf) where S is a message summary dict and sbuf is the raw message. The caller MUST call rmr.rmr_free_msg(sbuf) when finished with each sbuf to prevent memory leaks! """ while not self._rmr_loop.rcv_queue.empty(): (summary, sbuf) = self._rmr_loop.rcv_queue.get() yield (summary, sbuf) def rmr_send(self, payload, mtype, retries=100): """ Allocates a buffer, sets payload and mtype, and sends Parameters ---------- payload: bytes payload to set mtype: int message type retries: int (optional) Number of times to retry at the application level before excepting RMRFailure Returns ------- bool whether or not the send worked after retries attempts """ sbuf = rmr.rmr_alloc_msg(vctx=self._mrc, size=len(payload), payload=payload, gen_transaction_id=True, mtype=mtype) for _ in range(retries): sbuf = rmr.rmr_send_msg(self._mrc, sbuf) if sbuf.contents.state == 0: self.rmr_free(sbuf) return True self.rmr_free(sbuf) return False def rmr_rts(self, sbuf, new_payload=None, new_mtype=None, retries=100): """ Allows the xapp to return to sender, possibly adjusting the payload and message type before doing so. This does NOT free the sbuf for the caller as the caller may wish to perform multiple rts per buffer. The client needs to free. Parameters ---------- sbuf: ctypes c_void_p Pointer to an rmr message buffer new_payload: bytes (optional) New payload to set new_mtype: int (optional) New message type (replaces the received message) retries: int (optional, default 100) Number of times to retry at the application level Returns ------- bool whether or not the send worked after retries attempts """ for _ in range(retries): sbuf = rmr.rmr_rts_msg(self._mrc, sbuf, payload=new_payload, mtype=new_mtype) if sbuf.contents.state == 0: return True self.logger.warning("RTS Failed! Summary: {}".format(rmr.message_summary(sbuf))) return False def rmr_free(self, sbuf): """ Frees an rmr message buffer after use Note: this does not need to be a class method, self is not used. However if we break it out as a function we need a home for it. Parameters ---------- sbuf: ctypes c_void_p Pointer to an rmr message buffer """ rmr.rmr_free_msg(sbuf) # Convenience (pass-thru) function for invoking SDL. def sdl_set(self, namespace, key, value, usemsgpack=True): """ ** Deprecate Warning ** ** Will be removed in a future function ** Stores a key-value pair to SDL, optionally serializing the value to bytes using msgpack. Parameters ---------- namespace: string SDL namespace key: string SDL key value: Object or byte array to store. See the `usemsgpack` parameter. usemsgpack: boolean (optional, default is True) Determines whether the value is serialized using msgpack before storing. If usemsgpack is True, the msgpack function `packb` is invoked on the value to yield a byte array that is then sent to SDL. Stated differently, if usemsgpack is True, the value can be anything that is serializable by msgpack. If usemsgpack is False, the value must be bytes. """ self.sdl.set(namespace, key, value, usemsgpack) def sdl_get(self, namespace, key, usemsgpack=True): """ ** Deprecate Warning ** ** Will be removed in a future function ** Gets the value for the specified namespace and key from SDL, optionally deserializing stored bytes using msgpack. Parameters ---------- namespace: string SDL namespace key: string SDL key usemsgpack: boolean (optional, default is True) If usemsgpack is True, the byte array stored by SDL is deserialized using msgpack to yield the original object that was stored. If usemsgpack is False, the byte array stored by SDL is returned without further processing. Returns ------- Value See the usemsgpack parameter for an explanation of the returned value type. Answers None if the key is not found. """ return self.sdl.get(namespace, key, usemsgpack) def sdl_find_and_get(self, namespace, prefix, usemsgpack=True): """ ** Deprecate Warning ** ** Will be removed in a future function ** Gets all key-value pairs in the specified namespace with keys that start with the specified prefix, optionally deserializing stored bytes using msgpack. Parameters ---------- nnamespaces: string SDL namespace prefix: string the key prefix usemsgpack: boolean (optional, default is True) If usemsgpack is True, the byte array stored by SDL is deserialized using msgpack to yield the original value that was stored. If usemsgpack is False, the byte array stored by SDL is returned without further processing. Returns ------- Dictionary of key-value pairs Each key has the specified prefix. The value object (its type) depends on the usemsgpack parameter, but is either a Python object or raw bytes as discussed above. Answers an empty dictionary if no keys matched the prefix. """ return self.sdl.find_and_get(namespace, prefix, usemsgpack) def sdl_delete(self, namespace, key): """ ** Deprecate Warning ** ** Will be removed in a future function ** Deletes the key-value pair with the specified key in the specified namespace. Parameters ---------- namespace: string SDL namespace key: string SDL key """ self.sdl.delete(namespace, key) # Health def healthcheck(self): """ this needs to be understood how this is supposed to work """ return self._rmr_loop.healthcheck() and self.sdl.healthcheck() # Convenience function for discovering config change events def config_check(self, timeout=0): """ Checks the watcher for configuration-file events. The watcher prerequisites and event mask are documented in __init__(). Parameters ---------- timeout: int (optional) Number of seconds to wait for a configuration-file event, default 0. Returns ------- List of Events, possibly empty An event is a tuple with objects wd, mask, cookie and name. For example:: Event(wd=1, mask=1073742080, cookie=0, name='foo') """ if not self._inotify: return [] events = self._inotify.read(timeout=timeout) return list(events) def stop(self): """ cleans up and stops the xapp rmr thread (currently). This is critical for unit testing as pytest will never return if the thread is running. TODO: can we register a ctrl-c handler so this gets called on ctrl-c? Because currently two ctrl-c are needed to stop. """ self._rmr_loop.stop()