cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
698
Views
5
Helpful
11
Replies

LSA RFS to CFS CUSTOM NETCONF NOTIFICATION

mustafabehadir
Level 1
Level 1

Hi,

I try to create a custom netconf notification from RFS to CFS in a LSA environment. But I could not understand some parts and stucked. In the notification I just want to send device names in the messages. I try to write all my questions in my head as below?

1. Why I need this yang part at RFS level as my understanding just to create a netconf subscription from CFS to RFS that CFS is using as an endpoint?

module ctn-rfs-custom-notifications {
  notification sri-event-base-sync {
    leaf device {
      description
        "Name of the device that has a sub-interface resource change.";
      type string;
    }
  }
}
 
2. The notification name in the yang and stream name in the python code is completely separate things or has to be same?
3. The data in the stream and the data in the yang must be the same? Like in the stream how should this data be handled like below?

 

<sri-event-base-sync xlmns='http://ses.com/ctn-rfs-custom-notifications'>
  <device>rtr-x</device>
</sri-event-base-sync>

 

4. I saw that this payload is creating automatically. Is has to be created programmatically like this or we can create a string xml and use it in the code with proper formatting?

 

def get_action_tag_value(device):
    """Create xml tag and value."""
    action_tag = _ncs.XmlTag(ns_ncs.hash, ns_ncs.ncs_action)  # pylint: disable=no-member
    action_val = _ncs.Value(device, _ncs.C_BUF)  # pylint: disable=no-member
    return _ncs.TagValue(xmltag=action_tag, v=action_val)  # pylint: disable=no-member

 

5. Custom notifications are handled at CFS level with device-notifications? Should I put this stream in RFS ncs.conf if yes where to put it?

6. Is there any good documentation for it as a reference?

Thanks and Best regards ...

1 Accepted Solution

Accepted Solutions

Hi Mustafa,

the issue here is clear you can't use _ncs.str2hash(...) call in global context. If you do so the hash value will be == 0 what is obviously wrong

You must call _ncs.str2hash(...) i.e. inside your Action callback method like so:

class SendNotification(Action):
    @Action.action
    def cb_action(self, uinfo, name, kp, action_input, action_output, trans):
        NS_HASH = _ncs.str2hash("http://ses.com/rfs-ctn-custom-notifications")
        self.log.debug(f"Action={name} was called with kp={kp}")
        send_nso_sri_sync_notification(NS_HASH, get_device_name_from_kpath(), self.log)

This worked for me without any issue.

Let me know if it works.

Best Regards
Jakub

View solution in original post

11 Replies 11

JakubHolcman
Level 1
Level 1

Hi Mustafa,

I will try to answer your questions as good as I can.

1. You need YANG definition of the notification on your RFS node and on your CFS node as well. For CFS you need to generate NED package out of your RFS package containing YANG definition of the notification.

2. Notification name has nothing to do with the stream name. You can use any stream name. BUT you need to extend your RFS and CFS ncs.conf file with the definition of new stream that you want to use.

Example:

 

<?xml version="1.0"?>
<!-- -*- nxml -*- -->
<!-- Example configuration file for ncs. -->
<ncs-config xmlns="http://tail-f.com/yang/tailf-ncs-config">
  <notifications>
    <event-streams>
      <!-- Your custom stream definition -->
      <stream>
        <name>my-example-stream</name>
        <description>Your description</description>
        <replay-support>false</replay-support>
        <builtin-replay-store>
          <enabled>false</enabled>
          <dir>${NCS_RUN_DIR}/state</dir>
          <max-size>S10M</max-size>
          <max-files>50</max-files>
        </builtin-replay-store>
      </stream>
    </event-streams>
  </notifications>
</ncs-config>

 

3. Data that will be send with notification must follow the YANG schema definition.

4. You need to use _ncs methods to create your payload because it will be internally converted to C-API calls of confd/ncs. You can create abstraction on your own that will convert xml string into _ncs calls if you want.

5. It depends you can try to use device-notifications but I had some problems with it I would recommend to use custom streams and yes you need to specify them in ncs.conf like described above. You can find your ncs.conf typically under /etc/ncs/ncs.conf but it depends on your NSO installation.

6. Unfortunately there is no documentation (as far as I know) on this specific topic.

Tip: Using Java API seems to be a bit easier but I haven't try it. Here is example but in japanese Link
Some NSO java docs: Link 1 Link 2 

Best Regards
Jakub

mustafabehadir
Level 1
Level 1

Hi Jakub,
Thanks a lot for the explanation.
Where are we correlating these notification yang and stream xml. Because what I understood is, we are creating a daemon in python and register the stream like below.

ctx = self.register_notification_stream(state, None, ncs.dp.take_worker_socket(state, "ctn-nso-sri-notif-name", "ctn-nso-sri-notif-key"), "sri-event-base-sync-stream")

But in anywhere we have to correlate the notification in the yang and stream in python? Or just xml that we are creating should be align with the YANG is enough?

Hi Mustafa,

Step 1.
Register stream in your python code with following call (you need to import _ncs):

 

# stream_name is the name you defined in your ncs.conf file
lctx = _ncs.dp.register_notification_stream(daemon_ctx, None, notif_socket, stream_name)

 

 Step 2.
Send the notification with following call:

 

# message contains all needed info (notificaton name + data following YANG schema)
_ncs.dp.notification_send(lctx, time, message)

 

 

Tip: while using _ncs (low level API) package it is useful to take a look at C-API docs because _ncs package is just a wrapper around the original C-API

Best Regards
Jakub

mustafabehadir
Level 1
Level 1

Hi Jakub,

I tried to create the logic. What I see is I can subsribe the stream via netconf-console but I can not send the stream to north-bound.
I am suspicious about it can be related with my notification XML. But I have a very simple one now and could not find where is wrong. Could you please check my code that you can realize a problem. I am creating the xml via get_nso_sri_sync_notif_message function. I can see that I am sending the stream without any error in package log but I can not see in netconf.log.

root@cd5b869559bc:/# netconf-console --host 127.0.0.1 --port 2022 --create-subscription sri-event-base-sync
<?xml version="1.0" encoding="UTF-8"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="1">
<ok/>
</rpc-reply>

 

mustafabehadir
Level 1
Level 1
root@cd5b869559bc:/# tail -f /var/log/ncs/ncs-python-vm-rfs-ctn-custom-notifications.log 
<INFO> 17-Mar-2024::12:53:35.429 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - kp=/ncs:devices/device{rtr-vpe01.rig}/config/cisco-ios-xr:interface/GigabitEthernet-subinterface/GigabitEthernet{0/0/0/1.2}/ethernet/cfm/mep/domain{SES-L6}/service, op=MOP_ATTR_SET, newv=(<_ncs.Value type=C_UINT32(12) value='2147483650'>, <_ncs.Value type=C_NOEXISTS(1) value='NOEXISTS'>)
<INFO> 17-Mar-2024::12:53:35.429 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - kp=/ncs:devices/device{rtr-vpe01.rig}/config/cisco-ios-xr:interface/GigabitEthernet-subinterface/GigabitEthernet{0/0/0/1.2}/ethernet/cfm/mep/domain{SES-L6}/service, op=MOP_ATTR_SET, newv=(<_ncs.Value type=C_UINT32(12) value='2147483653'>, <_ncs.Value type=C_NOEXISTS(1) value='NOEXISTS'>)
<INFO> 17-Mar-2024::12:53:35.430 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - kp=/ncs:devices/device{rtr-vpe01.rig}/config/cisco-ios-xr:interface/GigabitEthernet-subinterface/GigabitEthernet{0/0/0/1.2}/ethernet/cfm/mep/domain{SES-L6}/mep-id, op=MOP_ATTR_SET, newv=(<_ncs.Value type=C_UINT32(12) value='2147483650'>, <_ncs.Value type=C_NOEXISTS(1) value='NOEXISTS'>)
<INFO> 17-Mar-2024::12:53:35.430 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - kp=/ncs:devices/device{rtr-vpe01.rig}/config/cisco-ios-xr:interface/GigabitEthernet-subinterface/GigabitEthernet{0/0/0/1.2}/ethernet/cfm/mep/domain{SES-L6}/mep-id, op=MOP_ATTR_SET, newv=(<_ncs.Value type=C_UINT32(12) value='2147483653'>, <_ncs.Value type=C_NOEXISTS(1) value='NOEXISTS'>)
<INFO> 17-Mar-2024::12:53:35.430 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - kp=/ncs:devices/device{rtr-vpe01.rig}/config/cisco-ios-xr:interface/GigabitEthernet-subinterface/GigabitEthernet{0/0/0/1.2}/mtu, op=MOP_ATTR_SET, newv=(<_ncs.Value type=C_UINT32(12) value='2147483650'>, <_ncs.Value type=C_NOEXISTS(1) value='NOEXISTS'>)
<INFO> 17-Mar-2024::12:53:35.430 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - kp=/ncs:devices/device{rtr-vpe01.rig}/config/cisco-ios-xr:interface/GigabitEthernet-subinterface/GigabitEthernet{0/0/0/1.2}/mtu, op=MOP_ATTR_SET, newv=(<_ncs.Value type=C_UINT32(12) value='2147483653'>, <_ncs.Value type=C_NOEXISTS(1) value='NOEXISTS'>)
<INFO> 17-Mar-2024::12:53:35.430 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Generating notification message...
<INFO> 17-Mar-2024::12:53:35.431 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Notification message is set to=[_ncs.TagValue(tag=1870939232, ns=0), _ncs.TagValue(tag=669279939, ns=0), _ncs.TagValue(tag=1870939232, ns=0)]
<INFO> 17-Mar-2024::12:53:35.431 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Sending notification with=ctx=_ncs.dp.NotificationCtxRef, notif_time=_ncs.DateTime(year=2024, month=3, day=17, hour=12, min=53, sec=35, micro=431065, timezone=0, timezone_minutes=0), msg=[_ncs.TagValue(tag=1870939232, ns=0), _ncs.TagValue(tag=669279939, ns=0), _ncs.TagValue(tag=1870939232, ns=0)]...
<INFO> 17-Mar-2024::12:53:35.431 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Notification sent...

 

mustafabehadir
Level 1
Level 1

Hi Jakub,
I think I did the necessary things but it is not working for me. What I see is I am sending notification without any issue in package logs. But I can not see that I am sending in netconf logs. Also I can subscribe the stream, but as expected it can not get any notification. I have a very simple notification but may be I am doing a mistake when I am preparing the payload. Can you have a look at my code. I am preparing XML at get_nso_sri_sync_notif_message function.

module rfs-ctn-custom-notifications {

  yang-version 1.1;
  namespace "http://ses.com/rfs-ctn-custom-notifications";
  prefix rfs-ctn-custom-notifications;

...
  notification sri-event-base-sync {
    leaf device-id {
      description
        "Name of the device that has a sub-interface resource change.";
      type string;
    }
  }
...
}
"""Main ctn-nso-sri-notification code."""
import socket
from datetime import datetime, timezone
from typing import List

import _ncs
import ncs
from ncs.dp import Action

NOTIFCTX = None

OPER = {
    1: "MOP_CREATED",
    2: "MOP_DELETED",
    3: "MOP_MODIFIED",
    4: "MOP_VALUE_SET",
    5: "MOP_MOVED_AFTER",
    6: "MOP_ATTR_SET",
}

NS_HASH = _ncs.str2hash("http://ses.com/rfs-ctn-custom-notifications")


def get_device_name_from_kpath(kpath: str) -> str:
    """Get device name from kpath."""
    kpath = str(kpath)
    device = kpath[kpath.find("{") + 1:kpath.find("}")]
    return device


def send_nso_sri_sync_notification(device: str, log: ncs.log.Log) -> None:
    """Send netconf-notification to CFS node."""
    try:
        notif_socket = socket.socket()
        notif_daemon = _ncs.dp.init_daemon("netconf-notification-sri-event-base-sync")
        _ncs.dp.connect(dx=notif_daemon, sock=notif_socket, type=_ncs.dp.CONTROL_SOCKET, ip="127.0.0.1", port=_ncs.PORT)
        live_ctx = _ncs.dp.register_notification_stream(notif_daemon, None, notif_socket, "sri-event-base-sync")
        log.info("Generating notification message...")
        notif_message = get_nso_sri_sync_notif_message(device)
        log.info(f"Notification message is set to={notif_message}")
        send_notifation(live_ctx, notif_message, log)

    except Exception as err:  # pylint: disable=broad-except
        log.error(err)

    finally:
        notif_socket.close()


def get_nso_sri_sync_notif_message(device_name: str):
    """Create notification payload."""
    notif_values = [get_device_tag_value(device_name)]
    return create_nso_sri_sync_notif_message(notif_values)


def get_device_tag_value(device_name) -> _ncs.TagValue:
    """Create xml tag and value."""
    device_tag_hash = _ncs.str2hash("device-id")
    device_tag = _ncs.XmlTag(NS_HASH, device_tag_hash)
    device_val = _ncs.Value(device_name, _ncs.C_BUF)
    return _ncs.TagValue(xmltag=device_tag, v=device_val)


def create_nso_sri_sync_notif_message(values) -> List[_ncs.TagValue]:
    """Create sri-event-base-sync notification payload xml."""
    notification_tag_hash = _ncs.str2hash("sri-event-base-sync")
    xml_begin = _ncs.Value((notification_tag_hash, NS_HASH), _ncs.C_XMLBEGIN)
    xml_end = _ncs.Value((notification_tag_hash, NS_HASH), _ncs.C_XMLEND)
    notif_tag = _ncs.XmlTag(NS_HASH, notification_tag_hash)
    start = _ncs.TagValue(xmltag=notif_tag, v=xml_begin)
    end = _ncs.TagValue(xmltag=notif_tag, v=xml_end)
    return [start] + values + [end]


def send_notifation(ctx, msg, log) -> None:
    """Send netconf notification stream."""

    current_time = datetime.now(timezone.utc)
    notif_time = _ncs.DateTime(current_time.year, current_time.month, current_time.day, current_time.hour,
                               current_time.minute, current_time.second, current_time.microsecond,
                               int(current_time.strftime("%z")[:3]), int(current_time.strftime("%z")[3:]))
    log.info(f"Sending notification with={ctx=}, {notif_time=}, {msg=}...")
    _ncs.dp.notification_send(ctx, notif_time, msg)
    log.info("Notification sent...")


# ---------------
# Action CALLBACK
# ---------------
class CtnNsoSriNotification(Action):
    """SRI notification action based on changes in device resources."""

    def iterator(self, kp, op, oldv, newv):
        """Diff-iterate function."""
        self.log.info(f"kp={kp}, op={OPER[op]}, newv={newv}")
        return ncs.ITER_RECURSE

    @Action.action
    def cb_action(self, uinfo, name, kp, input, output, trans):
        """Create and send notification to cfs nso."""
        self.log.info('action name: ', name)
        _ncs.dp.action_set_timeout(uinfo, 1800)
        self.log.info(f"Triggering kicker {input.kicker_id}, for path {input.path}, tid: {input.tid}")
        with ncs.maapi.Maapi() as _maapi:
            trans = _maapi.attach(input.tid)
            trans.diff_iterate(self.iterator, ncs.ITER_WANT_ATTR)
            _maapi.detach(input.tid)
        send_nso_sri_sync_notification(get_device_name_from_kpath(kp), self.log)


class Main(ncs.application.Application):
    """ctn-nso-sri-notification main class."""

    def setup(self):
        """Register service and actions."""
        self.log.info("Main RUNNING")
        self.register_action("sri-event-base-sync-action", CtnNsoSriNotification)

    def teardown(self):
        """Teardown service and actions."""
        self.log.info("Main FINISHED")

 <INFO> 17-Mar-2024::12:53:35.430 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Generating notification message...
<INFO> 17-Mar-2024::12:53:35.431 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Notification message is set to=[_ncs.TagValue(tag=1870939232, ns=0), _ncs.TagValue(tag=669279939, ns=0), _ncs.TagValue(tag=1870939232, ns=0)]
<INFO> 17-Mar-2024::12:53:35.431 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Sending notification with=ctx=_ncs.dp.NotificationCtxRef, notif_time=_ncs.DateTime(year=2024, month=3, day=17, hour=12, min=53, sec=35, micro=431065, timezone=0, timezone_minutes=0), msg=[_ncs.TagValue(tag=1870939232, ns=0), _ncs.TagValue(tag=669279939, ns=0), _ncs.TagValue(tag=1870939232, ns=0)]...
<INFO> 17-Mar-2024::12:53:35.431 rfs-ctn-custom-notifications ncs-dp-103617-rfs-ctn-custom-notifications:main:0-1-usid-75-sri-event-base-sync-action: - Notification sent...

mustafabehadir
Level 1
Level 1
NS_HASH = _ncs.str2hash("http://ses.com/rfs-ctn-custom-notifications")

def get_nso_sri_sync_notif_message(device_name: str):
    """Create notification payload."""
    notif_values = [get_device_tag_value(device_name)]
    return create_nso_sri_sync_notif_message(notif_values)

def get_device_tag_value(device_name) -> _ncs.TagValue:
    """Create xml tag and value."""
    device_tag_hash = _ncs.str2hash("device-id")
    device_tag = _ncs.XmlTag(NS_HASH, device_tag_hash)
    device_val = _ncs.Value(device_name, _ncs.C_BUF)
    return _ncs.TagValue(xmltag=device_tag, v=device_val)

def create_nso_sri_sync_notif_message(values) -> List[_ncs.TagValue]:
    """Create sri-event-base-sync notification payload xml."""
    notification_tag_hash = _ncs.str2hash("sri-event-base-sync")
    xml_begin = _ncs.Value((notification_tag_hash, NS_HASH), _ncs.C_XMLBEGIN)
    xml_end = _ncs.Value((notification_tag_hash, NS_HASH), _ncs.C_XMLEND)
    notif_tag = _ncs.XmlTag(NS_HASH, notification_tag_hash)
    start = _ncs.TagValue(xmltag=notif_tag, v=xml_begin)
    end = _ncs.TagValue(xmltag=notif_tag, v=xml_end)
    return [start] + values + [end]

 

Hi Mustafa,

as far as I can see your "def create_nso_sri_sync_notif_message(value)" function does not follow the documentation
The part that is wrong is where you specify "_ncs.Value(...)" for xml_begin and xml_end variables. According to documentation the first argument of this call should be a tuple of namespace hash and notification tag hash in this order (ns_hash, notifi_hash) but you changed the order :D.

Quote from docs: "For types C_XMLTAG, C_XMLBEGIN and C_XMLEND the init argument must be a 2-tuple which specifies the ns and tag values like this: (ns, tag)." Link

Correct implementation would be:

 

def create_nso_sri_sync_notif_message(values) -> List[_ncs.TagValue]:
    """Create sri-event-base-sync notification payload xml."""
    # notifi_tag_hash
    notification_tag_hash = _ncs.str2hash("sri-event-base-sync")

    # common xml tag
    notif_tag = _ncs.XmlTag(NS_HASH, notification_tag_hash)

    # begin and end tag values
    xml_begin = _ncs.Value((NS_HASH, notification_tag_hash), _ncs.C_XMLBEGIN)
    xml_end = _ncs.Value((NS_HASH, notification_tag_hash), _ncs.C_XMLEND)
    
    # generate tags value pairs
    start = _ncs.TagValue(notif_tag, xml_begin)
    end = _ncs.TagValue(notif_tag, xml_end)

    return [start] + values + [end]

 

 

Let me know if this works for you.

Best Regards
Jakub

mustafabehadir
Level 1
Level 1

Hi Jakub,

Thanks a lot but unfortunately not worked.

def get_nso_sri_sync_notif_message(device_name: str):
    """Create notification payload."""
    notif_values = [get_device_tag_value(device_name)]
    return create_nso_sri_sync_notif_message(notif_values)


def get_device_tag_value(device_name) -> _ncs.TagValue:
    """Create xml tag and value."""
    device_tag_hash = _ncs.str2hash("device-id")
    device_tag = _ncs.XmlTag(NS_HASH, device_tag_hash)
    device_val = _ncs.Value(device_name, _ncs.C_BUF)
    return _ncs.TagValue(xmltag=device_tag, v=device_val)


def create_nso_sri_sync_notif_message(values) -> List[_ncs.TagValue]:
    """Create sri-event-base-sync notification payload xml."""
    # notif_tag_hash
    notification_tag_hash = _ncs.str2hash("sri-event-base-sync")

    # notification xml tag
    notif_tag = _ncs.XmlTag(NS_HASH, notification_tag_hash)

    # begin and end tag values
    xml_begin = _ncs.Value((NS_HASH, notification_tag_hash), _ncs.C_XMLBEGIN)
    xml_end = _ncs.Value((NS_HASH, notification_tag_hash), _ncs.C_XMLEND)

    # generate tag-value pairs
    start = _ncs.TagValue(notif_tag, xml_begin)
    end = _ncs.TagValue(notif_tag, xml_end)
    return [start] + values + [end]


def send_notifation(ctx, msg, log) -> None:
    """Send netconf notification stream."""

    current_time = datetime.now(timezone.utc)
    notif_time = _ncs.DateTime(current_time.year, current_time.month, current_time.day, current_time.hour,
                               current_time.minute, current_time.second, current_time.microsecond,
                               int(current_time.strftime("%z")[:3]), int(current_time.strftime("%z")[3:]))
    log.info(f"Sending notification with={ctx=}, {notif_time=}, {msg=}")
    _ncs.dp.notification_send(ctx, notif_time, msg)
    log.info("Notification sent...")



Best Regards ...

Hi Mustafa,

the issue here is clear you can't use _ncs.str2hash(...) call in global context. If you do so the hash value will be == 0 what is obviously wrong

You must call _ncs.str2hash(...) i.e. inside your Action callback method like so:

class SendNotification(Action):
    @Action.action
    def cb_action(self, uinfo, name, kp, action_input, action_output, trans):
        NS_HASH = _ncs.str2hash("http://ses.com/rfs-ctn-custom-notifications")
        self.log.debug(f"Action={name} was called with kp={kp}")
        send_nso_sri_sync_notification(NS_HASH, get_device_name_from_kpath(), self.log)

This worked for me without any issue.

Let me know if it works.

Best Regards
Jakub

mustafabehadir
Level 1
Level 1

Thanks for all the helps Jakub worked.

Best Regards ...