= Developing generic plugins

link:release_notes.html[< Home]

Since Honeycomb is a generic agent. Any plugin (translation code) can be injected into the framework, creating a custom agent providing RESTCONF/NETCONF northbound interfaces out-of-box.

== Developing plugin code

Honeycomb provides a maven archetype to generate a plugin skeleton. To use that archetype, run maven:

[subs="+attributes"]
 mvn -X archetype:generate -DarchetypeGroupId=io.fd.honeycomb.tools -DarchetypeArtifactId=honeycomb-plugin-archetype -DarchetypeVersion={project-version}

Fill in the parameters e.g.

[subs="+attributes"]
 groupId: io.fd.honeycomb.tutorial
 artifactId: sample-plugin
 version: {project-version}
 package: io.fd.honeycomb.tutorial

And following structure should be created:

[source]
----
sample-plugin/
├── pom.xml
├── sample-plugin-api
│   ├── pom.xml
│   └── src
│       └── main
│           ├── java
│           └── yang
│               └── sample-plugin.yang
└── sample-plugin-impl
 ├── pom.xml
 ├── Readme.adoc
 └── src
     ├── main
     │   └── java
     │       └── io
     │           └── fd
     │               └── honeycomb
     │                   └── tutorial
     │                       ├── CrudService.java
     │                       ├── ElementCrudService.java
     │                       ├── init
     │                       │   └── ConfigDataInitializer.java
     │                       ├── ModuleConfiguration.java
     │                       ├── Module.java
     │                       ├── read
     │                       │   ├── ElementStateCustomizer.java
     │                       │   └── ModuleStateReaderFactory.java
     │                       └── write
     │                           ├── ElementCustomizer.java
     │                           └── ModuleWriterFactory.java
     └── test
         └── java
----

There are 2 modules:

* sample-plugin-api - Contains YANG models and generates Java APIs from the models.
* sample-plugin-impl - Contains: Readers, Writers, Initializers, Notification producers (not yet), Configuration and Wiring.

There is plenty of comments within the code, so its is advised to import the code into an IDE and take a look around.

*The archetype generates a plugin that is fully working right from the start*. Since it contains all the components necessary, works on a sample yang model and provides some sample values.

== Building the code

To build the code, just execute maven:

 mvn clean install

And that's it. This is a working Honeycomb plugin.

== Adding notifications

No notification producer is generated by the archetype, but it is pretty straightforward to add one.

First, the notification has to be defined in YANG (sample-plugin-api/src/main/yang/sample-plugin.yang) e.g.

[source,yang]
----
notification sample-notification {
 leaf content {
     type string;
 }
}
----

Now rebuild the plugin to generate new APIs for our notification.

Next part is implementing the Notification producer. First thing to do is to add a dependency on notification-api, since it's not included by default. Update sample-plugin-impl's pom file with:

[source,xml]
----
<dependency>
    <groupId>io.fd.honeycomb</groupId>
    <artifactId>notification-api</artifactId>
    <version>${honeycomb.infra.version}</version>
</dependency>
----

Now, the producer code can be added:

[source,java]
----
/*
 * Copyright (c) 2016 Cisco and/or its affiliates.
 *
 * 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.
 */

package io.fd.honeycomb.tutorial.notif;

import io.fd.honeycomb.notification.ManagedNotificationProducer;
import io.fd.honeycomb.notification.NotificationCollector;
import java.util.Collection;
import java.util.Collections;
import javax.annotation.Nonnull;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.sample.plugin.rev160918.SampleNotification;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.sample.plugin.rev160918.SampleNotificationBuilder;
import org.opendaylight.yangtools.yang.binding.Notification;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Notification producer for sample plugin
 */
public class SampleNotificationProducer implements ManagedNotificationProducer {

    private static final Logger LOG = LoggerFactory.getLogger(SampleNotificationProducer.class);

    private Thread thread;

    @Override
    public void start(@Nonnull final NotificationCollector collector) {
        LOG.info("Starting notification stream for interfaces");

        // Simulating notification producer
        thread = new Thread(() -> {
            while(true) {
                if (Thread.currentThread().isInterrupted()) {
                    return;
                }

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }

                final SampleNotification notification = new SampleNotificationBuilder()
                        .setContent("Hello world " + System.currentTimeMillis())
                        .build();
                LOG.info("Emitting notification: {}", notification);
                collector.onNotification(notification);
            }
        }, "NotificationProducer");
        thread.setDaemon(true);
        thread.start();
    }

    @Override
    public void stop() {
        if(thread != null) {
            thread.interrupt();
        }
    }

    @Nonnull
    @Override
    public Collection<Class<? extends Notification>> getNotificationTypes() {
        // Producing only this single type of notification
        return Collections.singleton(SampleNotification.class);
    }

    @Override
    public void close() throws Exception {
        stop();
    }
}
----

This is placed sample-plugin/sample-plugin-impl/src/main/java/io/fd/honeycomb/tutorial/notif/SampleNotificationProducer.java.

NOTE: This is a sample producer, that creates a thread to periodically emit a sample notification

Now it needs to be exposed from the plugin. The configure method in Module class needs to be updated with:

[source,java]
----
    Multibinder.newSetBinder(binder(), ManagedNotificationProducer.class).addBinding().to(SampleNotificationProducer.class);
----

Plugin needs to be rebuilt, but that's it for notification producers.

== Creating custom distribution

The plugin is now ready to have a Honeycomb distribution for it. This section will provides information on how to create a custom Honeycomb distribution.

A new maven module needs to be created. So in sample-plugin folder:

 mkdir sample-distribution
 cd sample-distribution

Then create the pom.xml:

[source,xml,subs="+attributes"]
----
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <parent>
    <groupId>io.fd.honeycomb.common</groupId>
    <artifactId>minimal-distribution-parent</artifactId>
    <version>{project-version}</version>
  </parent>

  <modelVersion>4.0.0</modelVersion>
  <groupId>io.fd.honeycomb.tutorial</groupId>
  <artifactId>sample-distribution</artifactId>
  <version>{project-version}</version>

  <properties>
    <exec.parameters>-Xms128m -Xmx128m</exec.parameters>
    <main.class>io.fd.honeycomb.infra.distro.Main</main.class>
    <interfaces.mapping.version>{project-version}</interfaces.mapping.version>
    <honeycomb.min.distro.version>{project-version}</honeycomb.min.distro.version>
    <!--
       Defines list of specific modules provided by the distribution
       (adds them to base modules like NetconfModule and RestconfModule).

       Module configuration is placed in 'modules' subdir of the target distro folder.
       Modules can be disabled by commenting them out in the pom.xml
       or modules configuration file.
     -->
    <distribution.modules>
      io.fd.honeycomb.tutorial.Module
      // some.module.DisabledByDefault
    </distribution.modules>
  </properties>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.codehaus.gmaven</groupId>
        <artifactId>groovy-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-jar-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
      </plugin>
      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

  <dependencies>
    <!-- Dependency on sample plugin -->
    <dependency>
      <groupId>io.fd.honeycomb.tutorial</groupId>
      <artifactId>sample-plugin-impl</artifactId>
      <version>${interfaces.mapping.version}</version>
    </dependency>
    <!-- Dependency on distribution base -->
    <dependency>
      <groupId>io.fd.honeycomb</groupId>
      <artifactId>minimal-distribution</artifactId>
      <version>${honeycomb.min.distro.version}</version>
    </dependency>

  </dependencies>
</project>
----

Last thing to do is to update sample-plugin/pom.xml with:
[source,xml]
----
 <module>sample-distribution</module>
----

Another rebuild and the distribution should be created in sample-distribution/target.

=== Adding existing plugins to the mix

In previous section, a custom Honeycomb distribution was created. This section will show how to add existing plugins to the new distribution.

So in order to add another existing sample (sample interface plugin from Honeycomb) into the distribution, update the sample-plugin/sample-distribution/pom.xml with:

[source,xml]
----
<dependency>
  <groupId>io.fd.honeycomb.samples.interfaces</groupId>
  <artifactId>interfaces-mapping</artifactId>
  <version>${interfaces.mapping.version}</version>
</dependency>
----

Now in sample-distribution/pom.xml, add this line to distribution.modules:

[source,java]
----
      io.fd.honeycomb.samples.interfaces.mapping.SampleInterfaceModule
----

That's it, just rebuild.

== Verifying distribution
The distribution with this sample plugin and sample interface plugin is now available and can be tested.

Distribution can now be found in sample-plugin/sample-distribution/target as:

zip archive
tar.gz archive
folder
The distribution can be started by:

[subs="attributes"]
 sudo ./sample-distribution/target/sample-distribution-{project-version}-hc/sample-distribution-{project-version}/honeycomb

NOTE: honeycomb-start script is the background alternative

Honeycomb will display following message in the log:

 2016-09-02 13:20:30.424 CEST [main] INFO  io.fd.honeycomb.infra.distro.Main - Honeycomb started successfully!

and that means Honeycomb was started successfully.

=== Testing over RESTCONF
Reading sample-plugin operational data:

 curl -u admin:admin http://localhost:8183/restconf/operational/sample-plugin:sample-plugin-state

Writing sample-plugin operational data:

 Not possible from YANG spec. Operational data is only for read.

Writing sample-plugin config data:

 curl -H 'Content-Type: application/json' -H 'Accept: application/json' -u admin:admin -X PUT -d '{"sample-plugin":{"element":[{"id":10,"description":"This is a example of loaded data"}]}}' http://localhost:8183/restconf/config/sample-plugin:sample-plugin

Reading sample-plugin config data:

 curl -u admin:admin http://localhost:8183/restconf/config/sample-plugin:sample-plugin

=== Testing over NETCONF
Netconf northbound can be easily tested manually using CLI SSH client. Initialize SSH connection by invoking:

 ssh admin@localhost -p 2831 -s netconf

NOTE: Using default credentials admin/admin, default port 2831 and netconf SSH channel.
Note: "Are you sure you want to continue connecting (yes/no)?". Answer yes

Next thing to do is to provide client hello message to initialize netconf session.
Following content must be copy&pasted into SSH session + hit enter:

[source,xml]
----
<hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
    <capabilities>
        <capability>urn:ietf:params:netconf:base:1.0</capability>
    </capabilities>
</hello>
]]>]]>
----

This initializes netconf session silently. No response from Honeycomb will be provided

To get all the configuration data using Honeycomb's netconf northbound interface,
following content must be copy&pasted into SSH session + hit enter:

[source,xml]
----
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
    <get-config>
        <source>
            <running/>
        </source>
    </get-config>
</rpc>
]]>]]>
----

Honeycomb will respond will all the data currently configured, e.g.:

[source,xml]
----
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="101">
    <data>
        <sample-plugin xmlns="urn:opendaylight:params:xml:ns:yang:sample-plugin">
            <element>
                <id>10</id>
                <description>This is a example of loaded data</description>
            </element>
        </sample-plugin>
    </data>
</rpc-reply>
]]>]]>
----

Next step is to get all the operational data using Honeycomb's netconf northbound interface.
Following content must be copy&pasted into SSH session + hit enter:

[source,xml]
----
<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="102">
    <get/>
</rpc>
]]>]]>
----

Honeycomb will respond will all operational data present,
including interfaces-state from sample interface plugin we included in the distro:

[source,xml]
----
<interfaces-state xmlns="io:fd:honeycomb:samples:interfaces">
    <interface>
        <interface-id>ifc2</interface-id>
        <counters>
            <total-packets>500</total-packets>
            <dropped-packets>50</dropped-packets>
        </counters>
        <mtu>66</mtu>
    </interface>
    <interface>
        <interface-id>ifc1</interface-id>
        <counters>
            <total-packets>500</total-packets>
            <dropped-packets>50</dropped-packets>
        </counters>
        <mtu>66</mtu>
    </interface>
</interfaces-state>
----


==== Listening for notifications

Notifications over NETCONF are supported by Honeycomb.
To test it out, open ssh NETCONF session and send hello message.
Exactly as detailed above.

Next thing to do is to activate honeycomb notification stream over NETCONF.
So just send this rpc over ssh session:

[source,xml]
----
<netconf:rpc netconf:message-id="101" xmlns:netconf="urn:ietf:params:xml:ns:netconf:base:1.0">
<create-subscription xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0">
   <stream>honeycomb</stream>
</create-subscription>
</netconf:rpc>
]]>]]>
----

From now on, all notifications from honeycomb will appear in the netconf session.


If you added link:devel_plugin_tutorial.html#_adding_notifications[SampleNotificationProducer],
notification should appear in opened NETCONF session every 2 seconds:

[source,xml]
----
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<notification xmlns="urn:ietf:params:netconf:capability:notification:1.0">
    <sample-notification xmlns="urn:opendaylight:params:xml:ns:yang:sample-plugin">
        <content>Hello world 1501081512690</content>
    </sample-notification>
    <eventTime>2017-07-26T17:05:12+02:00</eventTime>
</notification>
]]>]]>
----


== Full working example (outdated)
Full working example (1.16.9) on github: https://github.com/marosmars/honeycomb-samples

