[ONOS-7826] Adding a TAPI 2.1 Flow Rule programmable behaviour

Change-Id: I0b0070c4ca2a8c175b1fc6a4107566bb503e5b04
diff --git a/apps/odtn/api/src/test/resources/tapi-ols.json b/apps/odtn/api/src/test/resources/tapi-ols.json
new file mode 100644
index 0000000..a49ca8c
--- /dev/null
+++ b/apps/odtn/api/src/test/resources/tapi-ols.json
@@ -0,0 +1,18 @@
+{
+  "devices": {
+    "rest:127.0.0.1:1234": {
+      "rest": {
+        "ip": "127.0.0.1",
+        "port": 1234,
+        "protocol": "http",
+        "testUrl":"/data/context/",
+        "manufacturer": "tapi-swagger",
+        "hwVersion": "0",
+        "swVersion": "2.1"
+      },
+      "basic": {
+        "driver": "ols"
+      }
+    }
+  }
+}
\ No newline at end of file
diff --git a/drivers/odtn-driver/BUILD b/drivers/odtn-driver/BUILD
index fb1dbfc..5c65159 100644
--- a/drivers/odtn-driver/BUILD
+++ b/drivers/odtn-driver/BUILD
@@ -1,12 +1,12 @@
-COMPILE_DEPS = CORE_DEPS + [
+COMPILE_DEPS = CORE_DEPS + JACKSON + [
     "@commons_jxpath//jar",
+    "@javax_ws_rs_api//jar",
+    "@httpcore_osgi//jar",
     "//core/store/serializers:onos-core-serializers",
     "//drivers/utilities:onos-drivers-utilities",
     "//protocols/netconf/api:onos-protocols-netconf-api",
     "//protocols/rest/api:onos-protocols-rest-api",
     "//apps/odtn/api:onos-apps-odtn-api",
-    "@jackson_databind//jar",
-    "@javax_ws_rs_api//jar",
     "//apps/optical-model:onos-apps-optical-model",
     "//drivers/optical:onos-drivers-optical",
 ]
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/DeviceConnectionCache.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/DeviceConnectionCache.java
index ba0ea76..827ebd4 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/DeviceConnectionCache.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/impl/DeviceConnectionCache.java
@@ -101,16 +101,15 @@
      * @param flowId flow id
      * @return the flow rule
      */
-    public FlowRule get(DeviceId did, FlowId flowId) {
+    public DeviceConnection get(DeviceId did, FlowId flowId) {
         if (!flowCache.containsKey(did)) {
             return null;
         }
         Set<DeviceConnection> set = flowCache.get(did);
-        DeviceConnection connection = set.stream()
+        return set.stream()
                 .filter(c -> c.getFlowRule().id() == flowId)
                 .findFirst()
                 .orElse(null);
-        return connection != null ? connection.getFlowRule() : null;
     }
 
     /**
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceDescriptionDiscovery.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceDescriptionDiscovery.java
index 4bc7e69..c106202 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceDescriptionDiscovery.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceDescriptionDiscovery.java
@@ -46,7 +46,24 @@
 import java.util.List;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.*;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LAYER_PROTOCOL_NAME;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.MEDIA_CHANNEL_SERVICE_INTERFACE_POINT_SPEC;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.AVAILABLE_SPECTRUM;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.ADJUSTMENT_GRANULARITY;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.BASE_FREQUENCY;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.FREQUENCY_CONSTRAINT;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.GRID_TYPE;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LOWER_FREQUENCY;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.MC_POOL;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.PHOTONIC_LAYER_QUALIFIER_NMC;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.PHOTONIC_MEDIA;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_INTERFACE_POINT;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SUPPORTED_LAYER_PROTOCOL_QUALIFIER;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.UPPER_FREQUENCY;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.UUID;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.toMbpsFromHz;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.getChannelSpacing;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.getSlotGranularity;
 import static org.onosproject.net.optical.device.OchPortHelper.ochPortDescription;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -142,7 +159,7 @@
                 OchSignal ochSignal = getOchSignal(mcPool);
                 synchronized (N_PORT_LOCK) {
                     //annotations(portNumber-uuid)
-                    annotations.set(nPort.toString(), uuid);
+                    annotations.set(UUID, uuid);
 
                     //add och port
                     ports.add(ochPortDescription(nPort, true, OduSignalType.ODU4,
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceHelper.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceHelper.java
index 11bf38c..d4e9c06 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceHelper.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceHelper.java
@@ -40,6 +40,16 @@
     public static final String LOWER_FREQUENCY = "lower-frequency";
     public static final String AVAILABLE_SPECTRUM = "available-spectrum";
     public static final long BASE_FREQUENCY = 193100000;   //Working in Mhz
+    public static final String TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE = "tapi-connectivity:connectivity-service";
+    public static final String END_POINT = "end-point";
+    public static final String SERVICE_LAYER = "service-layer";
+    public static final String SERVICE_TYPE = "service-type";
+    public static final String POINT_TO_POINT_CONNECTIVITY = "POINT_TO_POINT_CONNECTIVITY";
+    public static final String LOCAL_ID = "local-id";
+    public static final String LAYER_PROTOCOL_QUALIFIER = "layer-protocol-qualifier";
+    public static final String TAPI_PHOTONIC_MEDIA_PHOTONIC_LAYER_QUALIFIER_NMC =
+            "tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC";
+    public static final String SERVICE_INTERFACE_POINT_UUID = "service-interface-point-uuid";
 
     private TapiDeviceHelper(){}
 
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceLambdaQuery.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceLambdaQuery.java
index 0127f43..fa46b87 100644
--- a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceLambdaQuery.java
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiDeviceLambdaQuery.java
@@ -48,6 +48,7 @@
 import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LOWER_FREQUENCY;
 import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.MC_POOL;
 import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.UPPER_FREQUENCY;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.UUID;
 import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.toMbpsFromHz;
 import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.getChannelSpacing;
 import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.getSlotGranularity;
@@ -92,7 +93,7 @@
             log.error("Port {} does not exist", port);
             return ImmutableSet.of();
         }
-        String uuid = p.annotations().value(port.toString());
+        String uuid = p.annotations().value(UUID);
 
         try {
             InputStream inputStream = controller.get(deviceId, SIP_REQUEST_DATA_API + uuid,
diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiFlowRuleProgrammable.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiFlowRuleProgrammable.java
new file mode 100644
index 0000000..c0bea12
--- /dev/null
+++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/tapi/TapiFlowRuleProgrammable.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * 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.
+ *
+ * This work was partially supported by EC H2020 project METRO-HAUL (761727).
+ */
+package org.onosproject.drivers.odtn.tapi;
+
+import com.fasterxml.jackson.core.JsonEncoding;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.google.common.collect.ImmutableList;
+import org.apache.http.HttpStatus;
+import org.onosproject.drivers.odtn.impl.DeviceConnection;
+import org.onosproject.drivers.odtn.impl.DeviceConnectionCache;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.protocol.rest.RestSBController;
+import org.slf4j.Logger;
+
+import javax.ws.rs.core.MediaType;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.END_POINT;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LAYER_PROTOCOL_NAME;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LAYER_PROTOCOL_QUALIFIER;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.LOCAL_ID;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.PHOTONIC_MEDIA;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.POINT_TO_POINT_CONNECTIVITY;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_INTERFACE_POINT;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_INTERFACE_POINT_UUID;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_LAYER;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.SERVICE_TYPE;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE;
+import static org.onosproject.drivers.odtn.tapi.TapiDeviceHelper.TAPI_PHOTONIC_MEDIA_PHOTONIC_LAYER_QUALIFIER_NMC;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Driver Implementation of the DeviceDescrption discovery for ONF Transport-API (TAPI) v2.1 based
+ * open line systems (OLS).
+ */
+
+public class TapiFlowRuleProgrammable extends AbstractHandlerBehaviour
+        implements FlowRuleProgrammable {
+
+    private static final Logger log = getLogger(TapiFlowRuleProgrammable.class);
+    private static final String CONN_REQ_POST_API = "/restconf/data/tapi-common:context/" +
+            "tapi-connectivity:connectivity-context/";
+    private static final String CONN_REQ_REMOVE_DATA_API = "/restconf/data/tapi-common:context/" +
+            "tapi-connectivity:connectivity-context/connectivity-service=";
+    private static final String CONN_REQ_GET_API = "/restconf/data/tapi-common:context/" +
+            "tapi-connectivity:connectivity-context/";
+
+
+    @Override
+    public Collection<FlowEntry> getFlowEntries() {
+        DeviceId deviceId = did();
+        RestSBController controller = checkNotNull(handler().get(RestSBController.class));
+        ObjectMapper om = new ObjectMapper();
+        final ObjectReader reader = om.reader();
+        // all waveserver responses contain data node, which contains the requested data
+        InputStream response = controller.get(deviceId, CONN_REQ_GET_API, MediaType.APPLICATION_JSON_TYPE);
+        JsonNode jsonNode = null;
+        try {
+            jsonNode = reader.readTree(response);
+            if (jsonNode == null) {
+                log.error("JsonNode is null for response {}", response);
+                return ImmutableList.of();
+            }
+            Set<String> uuids = parseTapiGetConnectivityRequest(jsonNode);
+            DeviceConnectionCache cache = getConnectionCache();
+            if (cache.get(deviceId) == null) {
+                return ImmutableList.of();
+            }
+            List<FlowEntry> entries = new ArrayList<>();
+            uuids.forEach(uuid -> {
+                FlowRule rule = cache.get(deviceId, uuid);
+                if (rule != null) {
+                    entries.add(new DefaultFlowEntry(rule, FlowEntry.FlowEntryState.ADDED, 0, 0, 0));
+                } else {
+                    log.info("Non existing rule for uuid {}", uuid);
+                }
+            });
+            return entries;
+        } catch (IOException e) {
+            return ImmutableList.of();
+        }
+    }
+
+    @Override
+    public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+        DeviceId deviceId = handler().data().deviceId();
+        RestSBController controller = checkNotNull(handler().get(RestSBController.class));
+        ImmutableList.Builder<FlowRule> added = ImmutableList.builder();
+        rules.forEach(flowRule -> {
+            String uuid = createUuid();
+            ByteArrayOutputStream applyConnectivityRequest = createConnectivityRequest(uuid, flowRule);
+            if (applyConnectivityRequest.size() != 0) {
+                int result = controller.post(deviceId, CONN_REQ_POST_API,
+                        new ByteArrayInputStream(applyConnectivityRequest.toByteArray()),
+                        MediaType.APPLICATION_JSON_TYPE);
+                // TODO retrieve the UUID from the location and store with that identifier
+                // at the moment is implied that the sent one is the same used by the TAPI server.
+                if (result == HttpStatus.SC_CREATED) {
+                    getConnectionCache().add(deviceId, uuid, flowRule);
+                    added.add(flowRule);
+                }
+            }
+        });
+        return added.build();
+    }
+
+    @Override
+    public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+        DeviceId deviceId = handler().data().deviceId();
+        RestSBController controller = checkNotNull(handler().get(RestSBController.class));
+        ImmutableList.Builder<FlowRule> removed = ImmutableList.builder();
+        rules.forEach(flowRule -> {
+            DeviceConnection conn = getConnectionCache().get(deviceId, flowRule.id());
+            if (conn == null || conn.getId() == null) {
+                log.warn("Can't find associate device connection for flow {} and device {}",
+                        flowRule.id(), deviceId);
+                return;
+            }
+            int result = controller.delete(deviceId, CONN_REQ_REMOVE_DATA_API + conn.getId(),
+                    null, MediaType.APPLICATION_JSON_TYPE);
+            if (result == HttpStatus.SC_NO_CONTENT) {
+                getConnectionCache().remove(deviceId, flowRule);
+                removed.add(flowRule);
+            }
+        });
+        return removed.build();
+    }
+
+    /**
+     * Get the deviceId for which the methods apply.
+     *
+     * @return The deviceId as contained in the handler data
+     */
+    private DeviceId did() {
+        return handler().data().deviceId();
+    }
+
+    private DeviceConnectionCache getConnectionCache() {
+        return DeviceConnectionCache.init();
+    }
+
+    protected Set<String> parseTapiGetConnectivityRequest(JsonNode tapiConnectivityReply) {
+        /*
+         {
+            "tapi-connectivity:connectivity-service":[
+                {
+                    "uuid":"ffb006d4-349e-4d2f-817e-0906c88458d0",
+                    <other fields>
+                }
+            ]
+          }
+         */
+        Set<String> uuids = new HashSet<>();
+        if (tapiConnectivityReply.has(TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE)) {
+            tapiConnectivityReply.get(TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE).elements()
+                    .forEachRemaining(node -> uuids.add(node.get(TapiDeviceHelper.UUID).asText()));
+        } else {
+            log.warn("Cant retrieve connectivity UUID from {}", tapiConnectivityReply);
+        }
+        //This is only one uuid or empty in case of failures
+        return uuids;
+    }
+
+    ByteArrayOutputStream createConnectivityRequest(String uuid, FlowRule rule) {
+        /*
+        {
+            "tapi-connectivity:connectivity-service":[
+                {
+                    "uuid":"ffb006d4-349e-4d2f-817e-0906c88458d0",
+                    "service-layer":"PHOTONIC_MEDIA",
+                    "service-type":"POINT_TO_POINT_CONNECTIVITY",
+                    "end-point":[
+                        {
+                            "local-id":"1",
+                            "layer-protocol-name":"PHOTONIC_MEDIA",
+                            "layer-protocol-qualifier":"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC",
+                            "service-interface-point":{
+                                "service-interface-point-uuid":"0923962e-b83f-4702-9b16-a1a0db0dc1f9"
+                            }
+                        },
+                        {
+                            "local-id":"2",
+                            "layer-protocol-name":"PHOTONIC_MEDIA",
+                            "layer-protocol-qualifier":"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC",
+                            "service-interface-point":{
+                                "service-interface-point-uuid":"76be95de-5769-4e5d-b65e-62cb6c39cf6b "
+                            }
+                        }
+                    ]
+               }
+           ]
+        }
+        */
+        DeviceService deviceService = handler().get(DeviceService.class);
+        PortCriterion inputPortCriterion = (PortCriterion) checkNotNull(rule.selector()
+                .getCriterion(Criterion.Type.IN_PORT));
+        String inputPortUuid = deviceService.getPort(rule.deviceId(),
+                inputPortCriterion.port()).annotations().value(TapiDeviceHelper.UUID);
+
+        Instructions.OutputInstruction outInstruction = (Instructions.OutputInstruction) checkNotNull(rule.treatment()
+                .allInstructions().stream().filter(instr -> instr.type().equals(Instruction.Type.OUTPUT))
+                .findFirst().orElse(null));
+        String outputPortUuid = deviceService.getPort(rule.deviceId(),
+                outInstruction.port()).annotations().value(TapiDeviceHelper.UUID);
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        try {
+            JsonGenerator generator = getJsonGenerator(stream);
+            generator.writeStartObject();
+            generator.writeArrayFieldStart(TAPI_CONNECTIVITY_CONNECTIVITY_SERVICE);
+            generator.writeStartObject();
+            generator.writeStringField(TapiDeviceHelper.UUID, uuid);
+            generator.writeStringField(SERVICE_LAYER, PHOTONIC_MEDIA);
+            generator.writeStringField(SERVICE_TYPE, POINT_TO_POINT_CONNECTIVITY);
+            generator.writeArrayFieldStart(END_POINT);
+            addEndPoint(generator, inputPortUuid);
+            addEndPoint(generator, outputPortUuid);
+            generator.writeEndArray();
+            generator.writeEndObject();
+            generator.writeEndArray();
+            generator.writeEndObject();
+            generator.close();
+            return stream;
+        } catch (IOException e) {
+            log.error("Cant' create json", e);
+        }
+        return stream;
+    }
+
+    private JsonGenerator getJsonGenerator(ByteArrayOutputStream stream) throws IOException {
+        JsonFactory factory = new JsonFactory();
+        return factory.createGenerator(stream, JsonEncoding.UTF8);
+    }
+    /*
+    {
+          "local-id":"1",
+          "layer-protocol-name":"PHOTONIC_MEDIA",
+          "layer-protocol-qualifier":"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC",
+          "service-interface-point":{
+                "service-interface-point-uuid":"0923962e-b83f-4702-9b16-a1a0db0dc1f9"
+         }
+     }
+     */
+    private void addEndPoint(JsonGenerator generator, String sipUuid) throws IOException {
+        generator.writeStartObject();
+        generator.writeStringField(LOCAL_ID, sipUuid);
+        generator.writeStringField(LAYER_PROTOCOL_NAME, PHOTONIC_MEDIA);
+        generator.writeStringField(LAYER_PROTOCOL_QUALIFIER,
+                TAPI_PHOTONIC_MEDIA_PHOTONIC_LAYER_QUALIFIER_NMC);
+        generator.writeObjectFieldStart(SERVICE_INTERFACE_POINT);
+        generator.writeStringField(SERVICE_INTERFACE_POINT_UUID, sipUuid);
+        generator.writeEndObject();
+        generator.writeEndObject();
+    }
+
+    private String createUuid() {
+        return UUID.randomUUID().toString();
+    }
+}
diff --git a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
index 2472385..510abb7 100644
--- a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
+++ b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml
@@ -20,8 +20,8 @@
                    impl="org.onosproject.drivers.odtn.tapi.TapiDeviceDescriptionDiscovery"/>
         <behaviour api ="org.onosproject.net.behaviour.LambdaQuery"
                    impl="org.onosproject.drivers.odtn.tapi.TapiDeviceLambdaQuery"/>
-        <!--<behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
-                   impl="org.onosproject.drivers.odtn.TapiFlowRuleProgrammable-->
+        <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+                   impl="org.onosproject.drivers.odtn.tapi.TapiFlowRuleProgrammable"/>
     </driver>
     <driver name="odtn" manufacturer="" hwVersion="" swVersion="">
         <behaviour api="org.onosproject.net.device.DeviceDescriptionDiscovery"
diff --git a/drivers/odtn-driver/src/test/java/org/onosproject/drivers/odtn/tapi/TapiFlowRuleProgrammableTest.java b/drivers/odtn-driver/src/test/java/org/onosproject/drivers/odtn/tapi/TapiFlowRuleProgrammableTest.java
new file mode 100644
index 0000000..a3128af
--- /dev/null
+++ b/drivers/odtn-driver/src/test/java/org/onosproject/drivers/odtn/tapi/TapiFlowRuleProgrammableTest.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2019-present Open Networking Foundation
+ *
+ * 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.
+ *
+ * This work was partially supported by EC H2020 project METRO-HAUL (761727).
+ */
+package org.onosproject.drivers.odtn.tapi;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.GridType;
+import org.onosproject.net.Lambda;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.provider.ProviderId;
+
+import java.io.IOException;
+import java.util.Set;
+
+import static junit.framework.TestCase.assertEquals;
+
+/**
+ * Tests for the TAPI Flow Rule Programmable.
+ */
+public class TapiFlowRuleProgrammableTest {
+
+    private static final DeviceId DEVICE_ID = DeviceId.deviceId("rest:127.0.0.1:8080");
+    private static final ApplicationId APP_ID = new DefaultApplicationId(1, "test_app_id");
+
+    private static final ProviderId PID = new ProviderId("rest", "foo");
+    private static final DefaultAnnotations DEVICE_ANNOTATIONS =
+            DefaultAnnotations.builder().set(AnnotationKeys.DRIVER, "foo").build();
+    private static final Device DEV =
+            new DefaultDevice(PID, DEVICE_ID, Device.Type.OLS, "", "", "", "", null, DEVICE_ANNOTATIONS);
+
+    private static final DefaultAnnotations PORT_ANNOTATIONS = DefaultAnnotations.builder()
+            .set(TapiDeviceHelper.UUID, "76be95de-5769-4e5d-b65e-62cb6c39cf6b").build();
+
+    private static final DefaultAnnotations PORT_ANNOTATIONS_2 = DefaultAnnotations.builder()
+            .set(TapiDeviceHelper.UUID, "0923962e-b83f-4702-9b16-a1a0db0dc1f9").build();
+
+    private static final PortNumber ONE = PortNumber.portNumber(1);
+    private static final PortNumber TWO = PortNumber.portNumber(2);
+
+    private static final Port PORT_IN = new DefaultPort(DEV, ONE, true, PORT_ANNOTATIONS);
+    private static final Port PORT_OUT = new DefaultPort(DEV, TWO, true, PORT_ANNOTATIONS_2);
+
+
+    private static final Lambda LAMBDA = new OchSignal(GridType.DWDM, ChannelSpacing.CHL_0GHZ, 1, 1);
+
+    private static final TrafficSelector SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(ONE)
+            .add(Criteria.matchLambda(LAMBDA))
+            .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
+            .build();
+
+    private static final TrafficTreatment TREATMENT = DefaultTrafficTreatment.builder()
+            .add(Instructions.modL0Lambda(LAMBDA))
+            .setOutput(TWO)
+            .build();
+
+    private static final FlowRule FLOW_RULE = DefaultFlowRule.builder()
+            .forDevice(DEVICE_ID)
+            .withSelector(SELECTOR)
+            .withTreatment(TREATMENT)
+            .withPriority(1)
+            .fromApp(APP_ID)
+            .makePermanent()
+            .build();
+
+    private static final String CONNECTION_UUID = "30c3e74c-0e3c-40b3-9e26-221c56e995c2";
+    private static final String END_POINT_1_UUID = "76be95de-5769-4e5d-b65e-62cb6c39cf6b";
+    private static final String END_POINT_2_UUID = "0923962e-b83f-4702-9b16-a1a0db0dc1f9";
+
+    private static final String CONNECTIVITY_REQUEST = "{\"tapi-connectivity:connectivity-service\":" +
+            "[{\"uuid\":\"" + CONNECTION_UUID + "\",\"service-layer\":\"PHOTONIC_MEDIA\",\"service-type\"" +
+            ":\"POINT_TO_POINT_CONNECTIVITY\",\"end-point\":[{\"local-id\":\"" + END_POINT_1_UUID + "\"," +
+            "\"layer-protocol-name\":" + "\"PHOTONIC_MEDIA\",\"layer-protocol-qualifier\":" +
+            "\"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC\"," + "\"service-interface-point\":" +
+            "{\"service-interface-point-uuid\":\"" + END_POINT_1_UUID + "\"}}," +
+            "{\"local-id\":\"" + END_POINT_2_UUID + "\",\"layer-protocol-name\":\"PHOTONIC_MEDIA\"," +
+            "\"layer-protocol-qualifier\":" + "\"tapi-photonic-media:PHOTONIC_LAYER_QUALIFIER_NMC\"," +
+            "\"service-interface-point\":{" + "\"service-interface-point-uuid\":\"" + END_POINT_2_UUID + "\"}}]}]}";
+
+    private static final String GET_CONNECTIVITY_REQUEST_REPLY = "{\"tapi-connectivity:connectivity-service\" : " +
+            "[{\"uuid\" : \"" + CONNECTION_UUID + "\"}]}";
+
+    private static final Set<String> CONNECTION_UUIDS = ImmutableSet.of(CONNECTION_UUID);
+
+    private TapiFlowRuleProgrammable tapiFrp;
+
+    @Before
+    public void setUp() throws Exception {
+        tapiFrp = new TapiFlowRuleProgrammable();
+        DriverHandler mockHandler = new InternalDriverHandler();
+        tapiFrp.setHandler(mockHandler);
+    }
+
+    @Test
+    public void createConnRequest() {
+        String output = tapiFrp.createConnectivityRequest(CONNECTION_UUID, FLOW_RULE).toString();
+        System.out.println(output);
+        assertEquals("Json to create network connectivity is wrong", CONNECTIVITY_REQUEST, output);
+    }
+
+    @Test
+    public void parseConnReply() throws IOException {
+        TapiFlowRuleProgrammable tapiFrp = new TapiFlowRuleProgrammable();
+        ObjectMapper mapper = new ObjectMapper();
+        JsonNode jsonReply = mapper.readTree(GET_CONNECTIVITY_REQUEST_REPLY);
+        Set<String> output = tapiFrp.parseTapiGetConnectivityRequest(jsonReply);
+        assertEquals("Wrong Tapi UUIDS", CONNECTION_UUIDS, output);
+    }
+
+    private class InternalDriverHandler implements DriverHandler {
+
+        @Override
+        public Driver driver() {
+            return null;
+        }
+
+        @Override
+        public DriverData data() {
+            return null;
+        }
+
+        @Override
+        public <T extends Behaviour> T behaviour(Class<T> behaviourClass) {
+            return null;
+        }
+
+        @Override
+        public boolean hasBehaviour(Class<? extends Behaviour> behaviourClass) {
+            return false;
+        }
+
+        @Override
+        public <T> T get(Class<T> serviceClass) {
+            if (serviceClass.equals(DeviceService.class)) {
+                return (T) new InternalDeviceService();
+            }
+            return null;
+        }
+    }
+
+    private class InternalDeviceService extends DeviceServiceAdapter {
+        @Override
+        public Port getPort(DeviceId deviceId, PortNumber portNumber) {
+            if (portNumber.equals(ONE)) {
+                return PORT_IN;
+            } else {
+                return PORT_OUT;
+            }
+        }
+    }
+}
\ No newline at end of file