ONOS-7043:
	- Added FlowRuleProgramable behavior for restCiena driver
	- Added CienaRestDevice which provides cleaner implementation
	  of behaviors

Change-Id: I94ab7afdc5a2cda82cc5d5ed794af512cb80adc6
diff --git a/drivers/ciena/BUCK b/drivers/ciena/BUCK
index f6b7fa7..be8746e 100644
--- a/drivers/ciena/BUCK
+++ b/drivers/ciena/BUCK
@@ -6,6 +6,7 @@
     '//protocols/rest/api:onos-protocols-rest-api',
     '//apps/optical-model:onos-apps-optical-model',
     '//lib:javax.ws.rs-api',
+    '//drivers/optical:onos-drivers-optical',
 ]
 
 TEST_DEPS = [
diff --git a/drivers/ciena/pom.xml b/drivers/ciena/pom.xml
index 671148a..9c6df7d 100644
--- a/drivers/ciena/pom.xml
+++ b/drivers/ciena/pom.xml
@@ -59,6 +59,11 @@
             <artifactId>onos-restsb-api</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-drivers-optical</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
new file mode 100644
index 0000000..e514188
--- /dev/null
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaFlowRuleProgrammable.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2016-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.
+ */
+package org.onosproject.drivers.ciena;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.driver.optical.flowrule.CrossConnectFlowRule;
+import org.onosproject.net.driver.AbstractHandlerBehaviour;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleProgrammable;
+
+import org.slf4j.Logger;
+
+
+import java.util.List;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.stream.Collectors;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class CienaFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
+    private CienaRestDevice restCiena;
+    private final Logger log = getLogger(getClass());
+
+    @Override
+    public Collection<FlowEntry> getFlowEntries() {
+        DeviceId deviceId = handler().data().deviceId();
+        log.debug("getting flow entries for device {}", deviceId);
+        log.debug("getFlowEntries not supported for device {}", deviceId);
+        return Collections.EMPTY_LIST;
+    }
+
+    @Override
+    public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
+        log.debug("installing flow rules: {}", rules);
+        // Apply the valid rules on the device
+        Collection<FlowRule> added = rules.stream()
+                .map(r -> createCrossConnectFlowRule(r))
+                .filter(xc -> installCrossConnect(xc))
+                .collect(Collectors.toList());
+        return added;
+    }
+
+    @Override
+    public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
+        log.debug("removing flow rules: {}", rules);
+        Collection<FlowRule> removed = rules.stream()
+                .map(r -> createCrossConnectFlowRule(r))
+                .filter(xc -> removeCrossConnect(xc))
+                .collect(Collectors.toList());
+        return removed;
+    }
+
+    private CrossConnectFlowRule createCrossConnectFlowRule(FlowRule r) {
+        List<PortNumber> linePorts = CienaWaveserverDeviceDescription.getLinesidePortId().stream()
+                .map(p -> PortNumber.portNumber(p))
+                .collect(Collectors.toList());
+        try {
+            return new CrossConnectFlowRule(r, linePorts);
+        } catch (IllegalArgumentException e) {
+            log.debug("unable to create cross connect for rule:\n{}", r);
+        }
+        return null;
+    }
+
+    private boolean installCrossConnect(CrossConnectFlowRule xc) {
+        if (xc == null) {
+            return false;
+        }
+        // only handling lineside rule
+        if (xc.isAddRule()) {
+            PortNumber outPort = xc.addDrop();
+            OchSignal signal = xc.ochSignal();
+            return install(outPort, signal);
+        }
+        return false;
+    }
+
+    private boolean removeCrossConnect(CrossConnectFlowRule xc) {
+        //for now setting channel to 0 for remove rule
+        if (xc == null) {
+            return false;
+        }
+        // only handling lineside rule
+        if (xc.isAddRule()) {
+            PortNumber outPort = xc.addDrop();
+            OchSignal signal = OchSignal.newDwdmSlot(xc.ochSignal().channelSpacing(),
+                                                     -CienaRestDevice.getMultiplierOffset());
+            return install(outPort, signal);
+        }
+        return false;
+    }
+
+    private boolean install(PortNumber outPort, OchSignal signal) {
+        /*
+         * rule is installed in three steps
+         * 1- disable port
+         * 2- change channel
+         * 3- enable port
+         */
+        try {
+            restCiena = new CienaRestDevice(handler());
+        } catch (NullPointerException e) {
+            log.error("unable to create CienaRestDevice, {}", e);
+            return false;
+        }
+        //1- disable port
+        //blindly disabling port
+        if (!restCiena.disablePort(outPort)) {
+            log.error("unable to disable port {}", outPort);
+            return false;
+        }
+        //2- change channel
+        if (!restCiena.changeChannel(signal, outPort)) {
+            log.error("unable to change the channel for port {}", outPort);
+            return false;
+        }
+        //3- enable port
+        if (!restCiena.enablePort(outPort)) {
+            log.error("unable to enable port {}", outPort);
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
new file mode 100644
index 0000000..7627959
--- /dev/null
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaRestDevice.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2016-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.
+ */
+package org.onosproject.drivers.ciena;
+
+import org.onlab.util.Frequency;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.protocol.rest.RestSBController;
+import org.slf4j.Logger;
+
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+public class CienaRestDevice {
+    private DeviceId deviceId;
+    private RestSBController controller;
+
+    private final Logger log = getLogger(getClass());
+    private static final String ENABLED = "enabled";
+    private static final String DISABLED = "disabled";
+    private static final Frequency BASE_FREQUENCY = Frequency.ofGHz(193_950);
+    private static final int MULTIPLIER_OFFSET = 80;
+
+    //URIs
+    private static final String PORT_URI = "ws-ptps/ptps/%s";
+    private static final String TRANSMITTER_URI = PORT_URI + "/properties/transmitter";
+    private static final String PORT_STATE_URI = PORT_URI  + "/state";
+    private static final String WAVELENGTH_URI = TRANSMITTER_URI + "/wavelength";
+    private static final String FREQUENCY_URI = TRANSMITTER_URI + "/ciena-ws-ptp-modem:frequency";
+    private static final String CHANNEL_URI = TRANSMITTER_URI + "/ciena-ws-ptp-modem:line-system-channel-number";
+
+    public CienaRestDevice(DriverHandler handler) throws NullPointerException {
+        deviceId = handler.data().deviceId();
+        controller = checkNotNull(handler.get(RestSBController.class));
+    }
+
+    private final String genPortStateRequest(String state) {
+        String request = "{\n" +
+                "\"state\": {\n" +
+                "\"admin-state\": \"" + state + "\"\n}\n}";
+        log.debug("generated request: \n{}", request);
+        return request;
+    }
+
+    private String genWavelengthChangeRequest(String wavelength) {
+        String request = "{\n" +
+                "\"wavelength\": {\n" +
+                "\"value\": " + wavelength + "\n" +
+                "}\n" +
+                "}";
+        log.debug("request:\n{}", request);
+        return request;
+
+    }
+
+    private String genFrequencyChangeRequest(long wavelength) {
+        String request = "{\n" +
+                "\"ciena-ws-ptp-modem:frequency\": {\n" +
+                "\"value\": " + Long.toString(wavelength) + "\n" +
+                "}\n" +
+                "}";
+        log.debug("request:\n{}", request);
+        return request;
+
+    }
+
+    private String genChannelChangeRequest(int channel) {
+        String request = "{\n" +
+                "\"ciena-ws-ptp-modem:line-system-channel-number\": " +
+                Integer.toString(channel) + "\n}";
+        log.debug("request:\n{}", request);
+        return request;
+
+    }
+
+
+    private final String genUri(String uriFormat, PortNumber port) {
+        return String.format(uriFormat, port.name());
+    }
+
+    private boolean changePortState(PortNumber number, String state) {
+        log.debug("changing the port {} state to {}", number, state);
+        String uri = genUri(PORT_STATE_URI, number);
+        String request = genPortStateRequest(state);
+        return putNoReply(uri, request);
+    }
+
+    public boolean disablePort(PortNumber number) {
+        return changePortState(number, DISABLED);
+    }
+
+    public boolean enablePort(PortNumber number) {
+        return changePortState(number, ENABLED);
+    }
+
+    public final boolean changeFrequency(OchSignal signal, PortNumber outPort) {
+        String uri = genUri(FREQUENCY_URI, outPort);
+        long frequency = toFrequency(signal);
+        String request = genFrequencyChangeRequest(frequency);
+        return putNoReply(uri, request);
+    }
+
+    public final boolean changeChannel(OchSignal signal, PortNumber outPort) {
+        String uri = genUri(CHANNEL_URI, outPort);
+        int channel = signal.spacingMultiplier() + MULTIPLIER_OFFSET;
+        log.debug("channel is {} for port {} on device {}", channel, outPort.name(), deviceId);
+        String request = genChannelChangeRequest(channel);
+        return putNoReply(uri, request);
+    }
+
+    private final long toFrequency(OchSignal signal) {
+        double frequency = BASE_FREQUENCY.asGHz() +
+                (signal.channelSpacing().frequency().asGHz() * (double) signal.slotGranularity());
+        return Double.valueOf(frequency).longValue();
+    }
+
+    public static int getMultiplierOffset() {
+        return MULTIPLIER_OFFSET;
+    }
+
+    private int put(String uri, String request) {
+        InputStream payload = new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
+        int response = controller.put(deviceId, uri, payload, MediaType.valueOf(MediaType.APPLICATION_JSON));
+        log.debug("response: {}", response);
+        return response;
+    }
+
+    private boolean putNoReply(String uri, String request) {
+        return put(uri, request) == Response.Status.NO_CONTENT.getStatusCode();
+    }
+
+}
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java
index 31de548..92aa0f5 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverDeviceDescription.java
@@ -51,6 +51,7 @@
 /**
  * Discovers the ports from a Ciena WaveServer Rest device.
  */
+//TODO: Use CienaRestDevice
 public class CienaWaveserverDeviceDescription extends AbstractHandlerBehaviour
         implements DeviceDescriptionDiscovery {
 
@@ -128,21 +129,15 @@
             String portId = sub.getString(PORT_ID);
             DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
             if (LINESIDE_PORT_ID.contains(portId)) {
-                // TX/OUT port
                 annotations.set(AnnotationKeys.CHANNEL_ID, sub.getString(CHANNEL_ID));
-                annotations.set(AnnotationKeys.PORT_NAME, portId + " TX");
+                // TX/OUT and RX/IN ports
+                annotations.set(AnnotationKeys.PORT_OUT, sub.getString(PORT_OUT));
+                annotations.set(AnnotationKeys.PORT_IN, sub.getString(PORT_IN));
                 ports.add(parseWaveServerCienaOchPorts(
-                        sub.getLong(PORT_OUT),
+                        Long.valueOf(portId),
                         sub,
                         annotations.build()));
 
-                // RX/IN port
-                annotations.set(AnnotationKeys.PORT_NAME, portId + " RX");
-                annotations.set(AnnotationKeys.CHANNEL_ID, sub.getString(CHANNEL_ID));
-                ports.add(parseWaveServerCienaOchPorts(
-                        sub.getLong(PORT_IN),
-                        sub,
-                        annotations.build()));
             } else if (!portId.equals("5") && !portId.equals("49")) {
                 DefaultAnnotations.builder()
                         .set(AnnotationKeys.PORT_NAME, portId);
@@ -184,6 +179,10 @@
                                   new OchSignal(gridType, chSpacing, spacingMult, 1), annotations);
     }
 
+    public static ArrayList<String> getLinesidePortId() {
+        return LINESIDE_PORT_ID;
+    }
+
     //FIXME remove when all optical types have two way information methods, see jira tickets
     private static long toGbps(long speed) {
         return speed * 1000;
diff --git a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
index 0f3b6d7..a943f7a 100644
--- a/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
@@ -17,110 +17,41 @@
 package org.onosproject.drivers.ciena;
 
 import org.onlab.util.Tools;
-import org.onosproject.net.AnnotationKeys;
-import org.onosproject.net.DeviceId;
-import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.behaviour.PortAdmin;
-import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.AbstractHandlerBehaviour;
-import org.onosproject.protocol.rest.RestSBController;
 import org.slf4j.Logger;
 
-import javax.ws.rs.core.MediaType;
 import java.util.concurrent.CompletableFuture;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import javax.ws.rs.core.Response.Status;
 
-import static com.google.common.base.Preconditions.checkNotNull;
 import static org.slf4j.LoggerFactory.getLogger;
 
 
 public class CienaWaveserverPortAdmin extends AbstractHandlerBehaviour
         implements PortAdmin {
+    private CienaRestDevice restCiena;
     private final Logger log = getLogger(getClass());
-    private static final String APP_JSON = "application/json";
-    private static final String ENABLE = "enabled";
-    private static final String DISABLE = "disabled";
-
-    private final String generateUri(long number) {
-        return String.format("ws-ptps/ptps/%d/state", number);
-    }
-
-    private final String generateRequest(String state) {
-        String request = "{\n" +
-                "\"state\": {\n" +
-                "\"admin-state\": \"" + state + "\"\n}\n}";
-        log.debug("generated request: \n{}", request);
-        return request;
-    }
-
-    private final boolean put(long number, String state) {
-        String uri = generateUri(number);
-        String request = generateRequest(state);
-        DeviceId deviceId = handler().data().deviceId();
-        RestSBController controller =
-                checkNotNull(handler().get(RestSBController.class));
-        InputStream payload =
-                new ByteArrayInputStream(request.getBytes(StandardCharsets.UTF_8));
-        int response = controller.put(deviceId, uri, payload,
-                                      MediaType.valueOf(APP_JSON));
-        log.debug("response: {}", response);
-        // expecting 204/NO_CONTENT_RESPONSE as successful response
-        return response == Status.NO_CONTENT.getStatusCode();
-    }
-
-    // returns null if specified port number was not a line side port
-    private Long getLineSidePort(PortNumber number) {
-        DeviceService deviceService = checkNotNull(handler().get(DeviceService.class));
-        DeviceId deviceId = handler().data().deviceId();
-        Port port = deviceService.getPort(deviceId, number);
-        if (port != null) {
-            String channelId = port.annotations().value(AnnotationKeys.CHANNEL_ID);
-            // any port that has channel is lineSidePort and will have TX and RX
-            if (channelId != null) {
-                String portName = port.annotations().value(AnnotationKeys.PORT_NAME);
-                // last three characters of portName will always be " TX" or " RX"
-                portName = portName.substring(0, portName.length() - 3);
-                log.debug("port number {} is mapped to {} lineside port",
-                          number, portName);
-                return new Long(portName);
-            }
-        }
-        // not a line-side port
-        return null;
-    }
 
     @Override
     public CompletableFuture<Boolean> disable(PortNumber number) {
-        log.debug("disabling port {}", number);
-        Long lineSidePort = getLineSidePort(number);
-        long devicePortNum;
-        if (lineSidePort != null) {
-            devicePortNum = lineSidePort.longValue();
-        } else {
-            devicePortNum = number.toLong();
+        try {
+            restCiena = new CienaRestDevice(handler());
+        } catch (NullPointerException e) {
+            log.error("unable to create CienaRestDevice, {}", e);
+            return CompletableFuture.completedFuture(false);
         }
-        CompletableFuture<Boolean> result =
-                CompletableFuture.completedFuture(put(devicePortNum, DISABLE));
-        return result;
+        return CompletableFuture.completedFuture(restCiena.disablePort(number));
     }
 
     @Override
     public CompletableFuture<Boolean> enable(PortNumber number) {
-        log.debug("enabling port {}", number);
-        Long lineSidePort = getLineSidePort(number);
-        long devicePortNum;
-        if (lineSidePort != null) {
-            devicePortNum = lineSidePort.longValue();
-        } else {
-            devicePortNum = number.toLong();
+        try {
+            restCiena = new CienaRestDevice(handler());
+        } catch (NullPointerException e) {
+            log.error("unable to create CienaRestDevice, {}", e);
+            return CompletableFuture.completedFuture(false);
         }
-        CompletableFuture<Boolean> result =
-                CompletableFuture.completedFuture(put(devicePortNum, ENABLE));
-        return result;
+        return CompletableFuture.completedFuture(restCiena.enablePort(number));
     }
 
     @Override
diff --git a/drivers/ciena/src/main/resources/ciena-drivers.xml b/drivers/ciena/src/main/resources/ciena-drivers.xml
index 6ff5d75..57ed4cf 100644
--- a/drivers/ciena/src/main/resources/ciena-drivers.xml
+++ b/drivers/ciena/src/main/resources/ciena-drivers.xml
@@ -23,6 +23,8 @@
                    impl="org.onosproject.drivers.ciena.CienaWaveserverDeviceDescription"/>
         <behaviour api="org.onosproject.net.behaviour.PortAdmin"
                    impl="org.onosproject.drivers.ciena.CienaWaveserverPortAdmin"/>
+        <behaviour api="org.onosproject.net.flow.FlowRuleProgrammable"
+                   impl="org.onosproject.drivers.ciena.CienaFlowRuleProgrammable"/>
         <behaviour api="org.onosproject.net.behaviour.LambdaQuery"
                    impl="org.onosproject.driver.optical.query.CBand50LambdaQuery"/>
     </driver>