nemith / netconf

NETCONF implementation in Go.
Other
29 stars 7 forks source link

proposal: Notifications support - RFC 5277 #51

Closed GiacomoCortesi closed 1 year ago

GiacomoCortesi commented 1 year ago

Implementation design proposal - NETCONF notifications

NETCONF notifications mechanism is described in RFC5277

Implementation items:

Support \<create-subscription> operation

Notifications are sent to a NETCONF client only after the NETCONF client performs some kind of NETCONF subscription. To perform the subscription operation the NETCONF client sends a \<create-subscription> RPC. We therefore need to implement the \<create-subscription> operation in ops.go. \<create-subscription> operation allows for 4 optional parameters: stream, filter, start time, end time. We shall therefore define a struct like:

type createSubscriptionReq struct {
    ... parameters and xml definitions here similarly as for other operations parameters
}

And a method on the Session object such as:

func (s *Session) CreateSubscription(ctx context.Context, opts ...CreateSubscriptionOption) error {
    var req createSubscriptionReq
    for _, opt := range opts {
        opt.apply(&req)
    }

    ... eventual custom notifications rpc logic, e.g. create subscription only if notification capability is present

    var resp OKResp
    return s.Call(ctx, &req, &resp)
}

NOTE: If we switch to this proposal: https://github.com/nemith/netconf/issues/44 CreateSubscription method will be replaced with a user exported CreateSubscription message that can be passed in to the Call method.

Support \<notification> message

On the other hand the NETCONF client shall be able to handle notification messages after a successful subscription. Notifications messages are streamed from the NETCONF server through a \<notification> message. A NotificationMsg struct is already defined in msg.go

The \<notification> messages must then be recognized upon reception, for that we can just use the code from the recv() method of the Session object.

NOTE: Apart from the NETCONF stream that is defined in RFC5277, other streams are user defined and let to the vendor implementation. It may therefore be useful provide a method to retrieve available streams information from a NETCONF device. Just a wrapper to run a call like below:

<rpc message-id="101"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<get>
<filter type="subtree">
<netconf xmlns="urn:ietf:params:xml:ns:netmod:notification">
<streams/>
</netconf>
</filter>
</get>
</rpc>

that returns an array of NETCONF streams similar to the following:

<rpc-reply message-id="101"
xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<netconf xmlns="urn:ietf:params:xml:ns:netmod:notification">
<streams>
<stream>
<name>NETCONF</name>
<description>default NETCONF event stream
</description>
<replaySupport>true</replaySupport>
<replayLogCreationTime>
2007-07-08T00:00:00Z
</replayLogCreationTime>
</stream>
<stream>
<name>SNMP</name>
<description>SNMP notifications</description>
<replaySupport>false</replaySupport>
</stream>
<stream>
<name>syslog-critical</name>
<description>Critical and higher severity
</description>
<replaySupport>true</replaySupport>
<replayLogCreationTime>
2007-07-01T00:00:00Z
</replayLogCreationTime>
</stream>
</streams>
</netconf>
</data>
</rpc-reply>

This said, it is probably too high level and can be left outside the library.

Use callback functions to work with received notifications

So far so good, now we need to focus on how to provide some Handler function to the user in order to be able to work with/unmarshal received notifications.

We could add a NotificationHandler func type as follows: type NotificationHandler func(NotificationMsg)

NotificationHandler function can be passed as a SessionOption at Session creation time. If NotificationHandler function is nil notifications are ignored. If NotificationHandler functions is not nil, all received notifications in recv() method are passed in to the defined NotificationHandler callback.

Most of the times, the user wants to retrieve the notification data and store it / send it somewhere.

In order to retrieve the data from the notification the consumer could build an handler with a closure and pass a channel to it. Something as below:

// GetNotificationsHandler returns a simple NotificationsHandler that can be used to retrieve notifications data from the input channel
func GetNotificationsHandler(c chan string) netconf.NotificationsHandler {
    return func(nm NotificationMsg) {
        // just send the raw notification data to the channel
        c <- nm
    }
}

With a NotificationsHandler defined type in the netconf package:
type NotificationsHandler func(NotificationMsg)

Than a simple main function would look as:

func main() {

    ... create transport and stuff

    // create a notification channel onto which receive notifications
    notificationChannel := make(chan string)
    // pass the channel to the notifications handler function
    nc := GetNotificationsHandler(notificationChannel)

    session, err := netconf.Open(transport, WithNotificationsHandler(nc))
    if err != nil {
        panic(err)
    }
    for notification := range notificationChannel {
        fmt.Println(notification)
    }
}

Minor side point, not sure I like Handler terminology, maybe is nicer to call it Callback?

nemith commented 1 year ago

It may therefore be useful provide a method to retrieve available streams information from a NETCONF device. Just a wrapper to run a call like below:

I think we can wait off on this. I do think it's a good idea, but we need to figure out how to best support the RFC defined rpc methods first before we start with custom ones but i can imagine where we could provide vendor specific methods at some point in the future.

Minor side point, not sure I like Handler terminology, maybe is nicer to call it Callback?

Handler is more in like with Go and net/http. Callbacks are rarely (never?) used in the standard library and many other large go projects. I like NotificationHander.

We may want to concider doing the same as http.Handler as well where it is an interface as well as a function that implements that interface. However i guess that is probably overthinking things a bit. I like the simple nature of a function.

I think this is good to go ahead with code? Do you want to code it up?

GiacomoCortesi commented 1 year ago

I think we can wait off on this. I do think it's a good idea, but we need to figure out how to best support the RFC defined rpc methods first before we start with custom ones but i can imagine where we could provide vendor specific methods at some point in the future.

I agree to leave it out of the scope

Handler is more in like with Go and net/http. Callbacks are rarely (never?) used in the standard library and many other large go projects. I like NotificationHander.

Naming is my personal pain, It's ok to stick with Handler terminology.

I think this is good to go ahead with code? Do you want to code it up?

Yep sure, it shouldn't take too long to implement

nemith commented 1 year ago

Closed via #52