Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
def rmr_rcvall_msgs(mrc, pass_filter=None, timeout=0):
    """
    Assembles an array of all messages which can be received without blocking
    (but see the timeout parameter).  Effectively drains the message queue if
    RMR is started in mt-call mode, or draining any waiting TCP buffers.  If
    the pass_filter parameter is supplied, it is treated as one or more
    message types to accept (pass through). Using the default, an empty list,
    results in capturing all messages. If the timeout parameter is supplied
    and is not zero, this call may block up to that number of milliseconds
    waiting for a message to arrive. Using the default, zero, results in
    non-blocking no-wait behavior.

    Parameters
    ----------
        mrc: ctypes c_void_p
            Pointer to the RMR context

        pass_filter: list (optional)
            The message type(s) to capture.

        timeout: int (optional)
            The number of milliseconds to wait for a message to arrive.

    Returns
    -------
        List of message summaries (dict), one for each message received; may be empty.
    """

    new_messages = []
    mbuf = rmr.rmr_alloc_msg(
        mrc, 4096)  # allocate and reuse a single buffer for RMR

    while True:
        mbuf = rmr.rmr_torcv_msg(
            mrc, mbuf, timeout)  # first call may have non-zero timeout
        timeout = 0  # reset so subsequent calls do not wait
        summary = rmr.message_summary(mbuf)
        if summary[
                rmr.
                RMR_MS_MSG_STATUS] != "RMR_OK":  # ok indicates msg received, stop on all other states
            break

        if pass_filter is None or len(pass_filter) == 0 or summary[
                rmr.
                RMR_MS_MSG_TYPE] in pass_filter:  # no filter, or passes; capture it
            new_messages.append(summary)

    rmr.rmr_free_msg(mbuf)  # free the single buffer to avoid leak
    return new_messages
Exemplo n.º 3
0
def rmr_rcvall_msgs_raw(mrc, pass_filter=None, timeout=0):
    """
    Same as rmr_rcvall_msgs, but answers tuples with the raw sbuf.
    Useful if return-to-sender (rts) functions are required.

    Parameters
    ----------
        mrc: ctypes c_void_p
            Pointer to the RMR context

        pass_filter: list (optional)
            The message type(s) to capture.

        timeout: int (optional)
            The number of milliseconds to wait for a message to arrive.

    Returns
    -------
    list of tuple:
        List of tuples [(S, sbuf),...] where S is a message summary (dict), and sbuf is the raw message; may be empty.
        The caller MUST call rmr.rmr_free_msg(sbuf) when finished with each sbuf to prevent memory leaks!
    """

    new_messages = []

    while True:
        mbuf = rmr.rmr_alloc_msg(
            mrc, 4096)  # allocate a new buffer for every message
        mbuf = rmr.rmr_torcv_msg(
            mrc, mbuf, timeout)  # first call may have non-zero timeout
        timeout = 0  # reset so subsequent calls do not wait
        summary = rmr.message_summary(mbuf)
        if summary[rmr.RMR_MS_MSG_STATUS] != "RMR_OK":
            rmr.rmr_free_msg(mbuf)  # free the failed-to-receive buffer
            break

        if pass_filter is None or len(
                pass_filter
        ) == 0 or mbuf.contents.mtype in pass_filter:  # no filter, or passes; capture it
            new_messages.append(
                (summary,
                 mbuf))  # caller is responsible for freeing the buffer
        else:
            rmr.rmr_free_msg(mbuf)  # free the filtered-out message buffer

    return new_messages
Exemplo n.º 4
0
    def _send_msg(self, pay, mtype, subid):
        """
        Creates and sends a message via RMR's send-message feature with the specified payload
        using the specified message type and subscription ID.
        """
        sbuf = rmr.rmr_alloc_msg(self.mrc, len(pay), payload=pay, gen_transaction_id=True, mtype=mtype, sub_id=subid)
        sbuf.contents.sub_id = subid
        pre_send_summary = rmr.message_summary(sbuf)
        for _ in range(0, RETRY_TIMES):
            mdc_logger.debug("_send_msg: sending: {}".format(pre_send_summary))
            sbuf = rmr.rmr_send_msg(self.mrc, sbuf)
            msg_state = self._assert_good_send(sbuf, pre_send_summary)
            mdc_logger.debug("_send_msg: result message state: {}".format(msg_state))
            if msg_state != rmr.RMR_ERR_RETRY:
                break

        rmr.rmr_free_msg(sbuf)
        if msg_state != rmr.RMR_OK:
            mdc_logger.warning("_send_msg: failed after {} retries".format(RETRY_TIMES))
Exemplo n.º 5
0
    def send_report(self, msg: MetricsReport):
        """
        Serializes the MetricsReport dict to JSON and sends the result via RMR.
        Raises an exception if the report has no MetricsData items.

        Parameters
        ----------
        msg: MetricsReport (required)
            Dictionary with measurement data to encode and send

        Returns
        -------
        bool
            True if the send succeeded (possibly with retries), False otherwise
        """
        if KEY_DATA not in msg or len(msg[KEY_DATA]) == 0:
            raise EmptyReport
        payload = json.dumps(msg).encode()
        mdc_logger.debug("send_report: payload is {}".format(payload))
        sbuf = rmr.rmr_alloc_msg(vctx=self.vctx,
                                 size=len(payload),
                                 payload=payload,
                                 mtype=RIC_METRICS,
                                 gen_transaction_id=True)

        for _ in range(0, RETRIES):
            sbuf = rmr.rmr_send_msg(self.vctx, sbuf)
            post_send_summary = rmr.message_summary(sbuf)
            mdc_logger.debug("send_report: try {0} result is {1}".format(
                _, post_send_summary[rmr.RMR_MS_MSG_STATE]))
            # stop trying if RMR does not indicate retry
            if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_ERR_RETRY:
                break

        rmr.rmr_free_msg(sbuf)
        if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_OK:
            mdc_logger.warning(
                "send_report: failed after {} retries".format(RETRIES))
            return False

        return True
Exemplo n.º 6
0
    def _rmr_send_alarm(self, msg: dict):
        """
        Serializes the dict and sends the result via RMR using a predefined message
        type to the wormhole initialized at start.

        Parameters
        ----------
        msg: dict
            Dictionary with alarm message to encode and send

        Returns
        -------
        bool
            True if the send succeeded (possibly with retries), False otherwise
        """
        payload = json.dumps(msg).encode()
        mdc_logger.debug("_rmr_send_alarm: payload is {}".format(payload))
        sbuf = rmr.rmr_alloc_msg(vctx=self.vctx,
                                 size=len(payload),
                                 payload=payload,
                                 mtype=RIC_ALARM_UPDATE,
                                 gen_transaction_id=True)

        for _ in range(0, RETRIES):
            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
            if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_ERR_RETRY:
                break

        rmr.rmr_free_msg(sbuf)
        if post_send_summary[rmr.RMR_MS_MSG_STATE] != rmr.RMR_OK:
            mdc_logger.warning(
                "_rmr_send_alarm: failed after {} retries".format(RETRIES))
            return False

        return True
Exemplo n.º 7
0
def test_rcv_all():
    """
    test the ability to receive a batch of queued messages.
    """
    pay_fmt = "send to ring msg: %d"  # dynamic message format with counter

    send_burst(MRC_SEND,
               pay_fmt)  # send a bunch of 13 messages that should queue
    time.sleep(1)  # ensure underlying transport gets cycles to send/receive

    bundle = helpers.rmr_rcvall_msgs(
        MRC_BUF_RCV
    )  # use the buffered receiver to read all with a single call
    assert len(bundle) == 13

    for i, ms in enumerate(bundle):
        ms = bundle[
            i]  # validate each summary returned, and ordering preserved
        assert ms[rmr.RMR_MS_MSG_STATE] == rmr.RMR_OK
        expected_pay = bytes(pay_fmt % i, "UTF-8")
        assert ms[rmr.RMR_MS_PAYLOAD] == expected_pay

    send_burst(
        MRC_SEND, pay_fmt, mtype=1,
        num=10)  # send a second round with msg types 1 and 2 to test filter
    send_burst(MRC_SEND, pay_fmt, mtype=2, num=8)
    send_burst(MRC_SEND, pay_fmt, mtype=1, num=5)
    send_burst(MRC_SEND, pay_fmt, mtype=2, num=4,
               counter=8)  # total of 12 messages with type 2 should be queued
    time.sleep(1)  # ensure underlying transport gets cycles to send/receive

    bundle = helpers.rmr_rcvall_msgs_raw(
        MRC_BUF_RCV, [2])  # receive only message type 2 messages
    assert len(
        bundle) == 12  # we should only get the type 2 batch of 12 messages

    for i, (ms, sbuf) in enumerate(bundle):  # test the raw version
        test_summary = rmr.message_summary(sbuf)
        assert test_summary == ms
        assert ms[rmr.RMR_MS_MSG_STATE] == rmr.RMR_OK  # all should be OK
        assert ms[
            rmr.RMR_MS_MSG_TYPE] == 2  # only mtype 2 should have been received
        expected_pay = bytes(
            pay_fmt % i,
            "UTF-8")  # ordering should still jive with the counter
        assert ms[rmr.RMR_MS_PAYLOAD] == expected_pay
        rmr.rmr_free_msg(sbuf)

    # check the timeout scenarios
    start_rcv_sec = time.time()
    bundle = helpers.rmr_rcvall_msgs(
        MRC_RCV, timeout=1001)  # non-zero timeout means wait
    assert len(bundle) == 0  # we should get none
    assert (time.time() - start_rcv_sec > 1
            )  # test duration should be longer than 1 second

    start_rcv_sec = time.time()
    bundle = helpers.rmr_rcvall_msgs_raw(
        MRC_RCV, timeout=1002)  # non-zero timeout means wait
    assert len(bundle) == 0  # we should get none
    assert (time.time() - start_rcv_sec > 1
            )  # test duration should be longer than 1 second
Exemplo n.º 8
0
    def loop(self):
        """
        This loop runs forever, and has 3 jobs:
        - send out any messages that have to go out (create instance, delete instance)
        - read a1s mailbox and update the status of all instances based on acks from downstream policy handlers
        - clean up the database (eg delete the instance) under certain conditions based on those statuses (NOT DONE YET)
        """
        # loop forever
        mdc_logger.debug("Work loop starting")
        while self.keep_going:

            # Update 3/20/2020
            # We now handle our sends in a thread (that will just exit when it's done) because there is a difference between how send works in SI95 vs NNG.
            # Send_msg via NNG formerly never blocked.
            # However under SI95 this send may block for some arbitrary period of time on the first send to an endpoint for which a connection is not established
            # If this send takes too long, this loop blocks, and the healthcheck will fail, which will cause A1s healthcheck to fail, which will cause Kubernetes to whack A1 and all kinds of horrible things happen.
            # Therefore, now under SI95, we thread this.
            Thread(target=self._handle_sends).start()

            # read our mailbox
            for (msg, sbuf) in self.rcv_func():
                # TODO: in the future we may also have to catch SDL errors
                try:
                    mtype = msg[rmr.RMR_MS_MSG_TYPE]
                except (KeyError, TypeError, json.decoder.JSONDecodeError):
                    mdc_logger.warning("Dropping malformed message: {0}".format(msg))

                if mtype == A1_POLICY_RESPONSE:
                    try:
                        # got a policy response, update status
                        pay = json.loads(msg[rmr.RMR_MS_PAYLOAD])
                        data.set_policy_instance_status(
                            pay["policy_type_id"], pay["policy_instance_id"], pay["handler_id"], pay["status"]
                        )
                        mdc_logger.debug("Successfully received status update: {0}".format(pay))
                    except (PolicyTypeNotFound, PolicyInstanceNotFound):
                        mdc_logger.warning("Received a response for a non-existent type/instance: {0}".format(msg))
                    except (KeyError, TypeError, json.decoder.JSONDecodeError):
                        mdc_logger.warning("Dropping malformed policy response: {0}".format(msg))

                elif mtype == A1_POLICY_QUERY:
                    try:
                        # got a query, do a lookup and send out all instances
                        pti = json.loads(msg[rmr.RMR_MS_PAYLOAD])["policy_type_id"]
                        instance_list = data.get_instance_list(pti)  # will raise if a bad type
                        mdc_logger.debug("Received a query for a known policy type: {0}".format(msg))
                        for pii in instance_list:
                            instance = data.get_policy_instance(pti, pii)
                            payload = json.dumps(messages.a1_to_handler("CREATE", pti, pii, instance)).encode("utf-8")
                            sbuf = self._rts_msg(payload, sbuf, A1_POLICY_REQUEST)
                    except (PolicyTypeNotFound):
                        mdc_logger.warning("Received a policy query for a non-existent type: {0}".format(msg))
                    except (KeyError, TypeError, json.decoder.JSONDecodeError):
                        mdc_logger.warning("Dropping malformed policy query: {0}".format(msg))

                elif mtype == A1_EI_QUERY_ALL:
                    mdc_logger.debug("Received messaage {0}".format(msg))

                    # query A1-EI co-ordinator service to get the EI-types
                    resp = requests.get(ESC_EI_TYPE_PATH)
                    if resp.status_code != 200:
                        mdc_logger.warning("Received no reponse from A1-EI service")

                    mdc_logger.debug("response from A1-EI service : {0}".format(resp.json()))

                    # send the complete list of EI-types to xApp
                    sbuf = self._rts_msg(resp.content, sbuf, AI_EI_QUERY_ALL_RESP)

                elif mtype == A1_EI_CREATE_JOB:
                    mdc_logger.debug("Received message {0}".format(msg))
                    payload = json.loads(msg[rmr.RMR_MS_PAYLOAD])
                    mdc_logger.debug("Payload: {0}".format(payload))

                    uuidStr = payload["job-id"]
                    del payload["job-id"]

                    mdc_logger.debug("Payload after removing job-id: {0}".format(payload))

                    # 1. send request to A1-EI Service to create A1-EI JOB
                    headers = {'Content-type': 'application/json'}
                    r = requests.put(ECS_EI_JOB_PATH + uuidStr, data=json.dumps(payload), headers=headers)
                    if (r.status_code != 201) and (r.status_code != 200):
                        mdc_logger.warning("failed to create EIJOB : {0}".format(r))
                    else:
                        # 2. inform xApp for Job status
                        mdc_logger.debug("received successful response (ei-job-id) :{0}".format(uuidStr))
                        rmr_data = """{{
                                "ei_job_id": "{id}"
                                }}""".format(id=uuidStr)
                        mdc_logger.debug("rmr_Data to send: {0}".format(rmr_data))
                        sbuf = self._rts_msg(str.encode(rmr_data), sbuf, A1_EI_CREATE_JOB_RESP)

                else:
                    mdc_logger.warning("Received message type {0} but A1 does not handle this".format(mtype))

                # we must free each sbuf
                rmr.rmr_free_msg(sbuf)

            self.last_ran = time.time()
            time.sleep(1)

        mdc_logger.debug("RMR Thread Ending!")