ONOS-6968:
	- Added PortAdmin behaviour to restCiena driver
	- Added support for changePortState for RestDeviceProvide

Change-Id: I8083502e3cf64cf9df37d540d4fd511d34d6d8d9
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 3918c02..31de548 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
@@ -40,8 +40,8 @@
 import org.onosproject.protocol.rest.RestSBController;
 import org.slf4j.Logger;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.ArrayList;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onosproject.net.optical.device.OchPortHelper.ochPortDescription;
@@ -64,11 +64,13 @@
     private static final String PORT_IN = "properties.line-system.cmd.port-in";
     private static final String PORT_OUT = "properties.line-system.cmd.port-out";
 
-    private static final ArrayList<String> LINESIDE_PORT_ID = Lists.newArrayList(
-            "4", "48");
+    private static final String CHANNEL_ID =
+            "properties.transmitter.line-system-channel-number";
 
     private static final String PORT_REQUEST =
             "ciena-ws-ptp:ws-ptps?config=true&format=xml&depth=unbounded";
+    private static final ArrayList<String> LINESIDE_PORT_ID = Lists.newArrayList(
+            "4", "48");
 
     @Override
     public DeviceDescription discoverDeviceDetails() {
@@ -102,6 +104,18 @@
     }
 
     private List<PortDescription> getPorts() {
+        /*
+         * Relationship between ptp-index and port number shown in Ciena Wave Server
+         * CLI:
+         *      ptp-index = 4 * port_number (without decimal) + decimal
+         *      e.g
+         *          if port_number is 5 then ptp-index = 5 * 4 + 0 = 20
+         *          if port_number is 5.1 then ptp-index = 5 * 4 + 1 = 21
+         *
+         * Relationship between channelId and in/out port:
+         *      in_port = channelId * 2
+         *      out_port = channelId * 2 -1
+         */
         List<PortDescription> ports = Lists.newArrayList();
         RestSBController controller = checkNotNull(handler().get(RestSBController.class));
         DeviceId deviceId = handler().data().deviceId();
@@ -113,17 +127,18 @@
         portsConfig.forEach(sub -> {
             String portId = sub.getString(PORT_ID);
             DefaultAnnotations.Builder annotations = DefaultAnnotations.builder();
-
             if (LINESIDE_PORT_ID.contains(portId)) {
-                // TX port
+                // TX/OUT port
+                annotations.set(AnnotationKeys.CHANNEL_ID, sub.getString(CHANNEL_ID));
                 annotations.set(AnnotationKeys.PORT_NAME, portId + " TX");
                 ports.add(parseWaveServerCienaOchPorts(
                         sub.getLong(PORT_OUT),
                         sub,
                         annotations.build()));
 
-                // RX port
+                // 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,
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
new file mode 100644
index 0000000..0f3b6d7
--- /dev/null
+++ b/drivers/ciena/src/main/java/org/onosproject/drivers/ciena/CienaWaveserverPortAdmin.java
@@ -0,0 +1,132 @@
+/*
+ * 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.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 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();
+        }
+        CompletableFuture<Boolean> result =
+                CompletableFuture.completedFuture(put(devicePortNum, DISABLE));
+        return result;
+    }
+
+    @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();
+        }
+        CompletableFuture<Boolean> result =
+                CompletableFuture.completedFuture(put(devicePortNum, ENABLE));
+        return result;
+    }
+
+    @Override
+    public CompletableFuture<Boolean> isEnabled(PortNumber number) {
+        return Tools.exceptionalFuture(
+                new UnsupportedOperationException("isEnabled is not supported"));
+
+    }
+}
diff --git a/drivers/ciena/src/main/resources/ciena-drivers.xml b/drivers/ciena/src/main/resources/ciena-drivers.xml
index 84fe0ab..6ff5d75 100644
--- a/drivers/ciena/src/main/resources/ciena-drivers.xml
+++ b/drivers/ciena/src/main/resources/ciena-drivers.xml
@@ -21,6 +21,8 @@
                    impl="org.onosproject.net.optical.DefaultOpticalDevice"/>
         <behaviour api="org.onosproject.net.device.DeviceDescriptionDiscovery"
                    impl="org.onosproject.drivers.ciena.CienaWaveserverDeviceDescription"/>
+        <behaviour api="org.onosproject.net.behaviour.PortAdmin"
+                   impl="org.onosproject.drivers.ciena.CienaWaveserverPortAdmin"/>
         <behaviour api="org.onosproject.net.behaviour.LambdaQuery"
                    impl="org.onosproject.driver.optical.query.CBand50LambdaQuery"/>
     </driver>
diff --git a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
index c896df1..091dc34 100644
--- a/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
+++ b/providers/rest/device/src/main/java/org/onosproject/provider/rest/device/impl/RestDeviceProvider.java
@@ -28,6 +28,7 @@
 import org.onlab.util.SharedScheduledExecutors;
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
+import org.onosproject.net.behaviour.PortAdmin;
 import org.onosproject.net.config.ConfigException;
 import org.onosproject.net.AnnotationKeys;
 import org.onosproject.net.DefaultAnnotations;
@@ -80,6 +81,7 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 import java.util.stream.Collectors;
+import java.util.concurrent.CompletableFuture;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.onlab.util.Tools.groupedThreads;
@@ -495,6 +497,29 @@
     @Override
     public void changePortState(DeviceId deviceId, PortNumber portNumber,
                                 boolean enable) {
-        // TODO if required
+        Device device = deviceService.getDevice(deviceId);
+        if (device != null) {
+            if (device.is(PortAdmin.class)) {
+                PortAdmin portAdmin = device.as(PortAdmin.class);
+                CompletableFuture<Boolean> modified;
+                if (enable) {
+                    modified = portAdmin.enable(portNumber);
+                } else {
+                    modified = portAdmin.disable(portNumber);
+                }
+                modified.thenAcceptAsync(result -> {
+                    if (!result) {
+                        log.warn("Device {} port {} state can't be changed to {}",
+                                 deviceId, portNumber, enable);
+                    }
+                });
+
+            } else {
+                log.warn("Device {} does not support PortAdmin behavior", deviceId);
+            }
+        } else {
+            log.warn("unable to get the device {}, port {} state can't be changed to {}",
+                     deviceId, portNumber, enable);
+        }
     }
 }