Add ROADM application

Change-Id: I50fa93cf3a69122f6434b46e831b254771159294
diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java
new file mode 100644
index 0000000..08e74f0
--- /dev/null
+++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2016-present Open Networking Laboratory
+ *
+ * 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.roadm;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Range;
+import org.onlab.osgi.ServiceDirectory;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.ui.RequestHandler;
+import org.onosproject.ui.UiConnection;
+import org.onosproject.ui.UiMessageHandler;
+import org.onosproject.ui.table.TableModel;
+import org.onosproject.ui.table.TableRequestHandler;
+import org.onosproject.ui.table.cell.HexLongFormatter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Set;
+
+import static org.onosproject.ui.JsonUtils.node;
+import static org.onosproject.ui.JsonUtils.number;
+
+/**
+ * Table-View message handler for ROADM flow view.
+ */
+public class RoadmFlowViewMessageHandler extends UiMessageHandler {
+
+    private static final String ROADM_FLOW_DATA_REQ = "roadmFlowDataRequest";
+    private static final String ROADM_FLOW_DATA_RESP = "roadmFlowDataResponse";
+    private static final String ROADM_FLOWS = "roadmFlows";
+
+    private static final String ROADM_SET_ATTENUATION_REQ = "roadmSetAttenuationRequest";
+    private static final String ROADM_SET_ATTENUATION_RESP = "roadmSetAttenuationResponse";
+
+    private static final String ROADM_DELETE_FLOW_REQ = "roadmDeleteFlowRequest";
+
+    private static final String ROADM_CREATE_FLOW_REQ = "roadmCreateFlowRequest";
+    private static final String ROADM_CREATE_FLOW_RESP = "roadmCreateFlowResponse";
+
+    private static final String NO_ROWS_MESSAGE = "No items found";
+
+    private static final String DEV_ID = "devId";
+
+    private static final String ID = "id";
+    private static final String FLOW_ID = "flowId";
+    private static final String APP_ID = "appId";
+    private static final String GROUP_ID = "groupId";
+    private static final String TABLE_ID = "tableId";
+    private static final String PRIORITY = "priority";
+    private static final String PERMANENT = "permanent";
+    private static final String TIMEOUT = "timeout";
+    private static final String STATE = "state";
+    private static final String IN_PORT = "inPort";
+    private static final String OUT_PORT = "outPort";
+    private static final String CHANNEL_SPACING = "spacing";
+    private static final String CHANNEL_MULTIPLIER = "multiplier";
+    private static final String CURRENT_POWER = "currentPower";
+    private static final String ATTENUATION = "attenuation";
+    private static final String HAS_ATTENUATION = "hasAttenuation";
+
+    private static final String[] COLUMN_IDS = {
+            ID, FLOW_ID, APP_ID, GROUP_ID, TABLE_ID, PRIORITY, TIMEOUT,
+            PERMANENT, STATE, IN_PORT, OUT_PORT, CHANNEL_SPACING,
+            CHANNEL_MULTIPLIER, CURRENT_POWER, ATTENUATION, HAS_ATTENUATION
+    };
+
+    private static final String NA = "N/A";
+    private static final String UNKNOWN = "Unknown";
+
+    private static final long GHZ = 1_000_000_000L;
+
+    private FlowRuleService flowRuleService;
+    private RoadmService roadmService;
+
+    private final Logger log = LoggerFactory.getLogger(getClass());
+
+    @Override
+    public void init(UiConnection connection, ServiceDirectory directory) {
+        super.init(connection, directory);
+        flowRuleService = get(FlowRuleService.class);
+        roadmService = get(RoadmService.class);
+    }
+
+    @Override
+    protected Collection<RequestHandler> createRequestHandlers() {
+        return ImmutableSet.of(
+                new FlowTableDataRequestHandler(),
+                new SetAttenuationRequestHandler(),
+                new DeleteConnectionRequestHandler(),
+                new CreateConnectionRequestHandler()
+        );
+    }
+
+    // Handler for sample table requests
+    private final class FlowTableDataRequestHandler extends TableRequestHandler {
+
+        private FlowTableDataRequestHandler() {
+            super(ROADM_FLOW_DATA_REQ, ROADM_FLOW_DATA_RESP, ROADM_FLOWS);
+        }
+
+        @Override
+        protected String[] getColumnIds() {
+            return COLUMN_IDS;
+        }
+
+        @Override
+        protected String noRowsMessage(ObjectNode payload) {
+            return NO_ROWS_MESSAGE;
+        }
+
+        @Override
+        protected TableModel createTableModel() {
+            TableModel tm = super.createTableModel();
+            tm.setFormatter(FLOW_ID, HexLongFormatter.INSTANCE);
+            return tm;
+        }
+
+        @Override
+        protected void populateTable(TableModel tm, ObjectNode payload) {
+            DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
+
+            Iterable<FlowEntry> flowEntries = flowRuleService.getFlowEntries(deviceId);
+            for (FlowEntry flowEntry : flowEntries) {
+                populateRow(tm.addRow(), flowEntry, deviceId);
+            }
+        }
+
+        private void populateRow(TableModel.Row row, FlowEntry entry, DeviceId deviceId) {
+            ChannelData cd = ChannelData.fromFlow(entry);
+            row.cell(ID, entry.id().value())
+                    .cell(FLOW_ID, entry.id().value())
+                    .cell(APP_ID, entry.appId())
+                    .cell(PRIORITY, entry.priority())
+                    .cell(TIMEOUT, entry.timeout())
+                    .cell(PERMANENT, entry.isPermanent())
+                    .cell(STATE, entry.state().toString())
+                    .cell(IN_PORT, cd.inPort().toLong())
+                    .cell(OUT_PORT, cd.outPort().toLong())
+                    .cell(CHANNEL_SPACING, cd.ochSignal().channelSpacing().frequency().asHz() / GHZ)
+                    .cell(CHANNEL_MULTIPLIER, cd.ochSignal().spacingMultiplier())
+                    .cell(CURRENT_POWER, getCurrentPower(deviceId, cd))
+                    .cell(ATTENUATION, getAttenuation(deviceId, cd));
+        }
+
+        private String getCurrentPower(DeviceId deviceId, ChannelData channelData) {
+            Range<Long> range =
+                    roadmService.attenuationRange(deviceId,
+                                                  channelData.outPort(),
+                                                  channelData.ochSignal());
+            if (range != null) {
+                Long currentPower =
+                        roadmService.getCurrentChannelPower(deviceId,
+                                                            channelData.outPort(),
+                                                            channelData.ochSignal());
+                if (currentPower != null) {
+                    return String.valueOf(currentPower);
+                }
+            }
+            return NA;
+        }
+
+        private String getAttenuation(DeviceId deviceId, ChannelData channelData) {
+            Long attenuation =
+                    roadmService.getAttenuation(deviceId, channelData.outPort(),
+                                                channelData.ochSignal());
+            if (attenuation != null) {
+                return String.valueOf(attenuation);
+            }
+            return UNKNOWN;
+        }
+    }
+
+    // Handler for setting attenuation
+    private final class SetAttenuationRequestHandler extends RequestHandler {
+
+        // Keys for response message
+        private static final String VALID = "valid";
+        private static final String MESSAGE = "message";
+
+        // Error messages to display to user
+        private static final String ATTENUATION_RANGE_MSG =
+                "Attenuation must be in range %s.";
+        private static final String NO_ATTENUATION_MSG =
+                "Cannot set attenuation for this connection";
+
+        private SetAttenuationRequestHandler() {
+            super(ROADM_SET_ATTENUATION_REQ);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
+            FlowId flowId = FlowId.valueOf(number(payload, FLOW_ID));
+            long attenuation = payload.get(ATTENUATION).asLong();
+
+            // Get connection information from the flow
+            FlowEntry entry = findFlow(deviceId, flowId);
+            if (entry == null) {
+                log.error("Unable to find flow rule to set attenuation");
+                return;
+            }
+            ChannelData cd = ChannelData.fromFlow(entry);
+            Range<Long> range =
+                    roadmService.attenuationRange(deviceId, cd.outPort(),
+                                                  cd.ochSignal());
+
+            boolean validAttenuation = (range != null && range.contains(attenuation));
+            if (validAttenuation) {
+                roadmService.setAttenuation(deviceId, cd.outPort(),
+                                            cd.ochSignal(), attenuation);
+            }
+
+            ObjectNode rootNode = objectNode();
+            // Send back flowId so view can identify which callback function to use
+            rootNode.put(FLOW_ID, payload.get(FLOW_ID).asText());
+            rootNode.put(VALID, validAttenuation);
+            if (range != null) {
+                rootNode.put(MESSAGE, String.format(ATTENUATION_RANGE_MSG,
+                                                    range.toString()));
+            } else {
+                rootNode.put(MESSAGE, NO_ATTENUATION_MSG);
+            }
+            sendMessage(ROADM_SET_ATTENUATION_RESP, rootNode);
+        }
+
+        private FlowEntry findFlow(DeviceId deviceId, FlowId flowId) {
+            for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
+                if (entry.id().equals(flowId)) {
+                    return entry;
+                }
+            }
+            return null;
+        }
+    }
+
+    // Handler for deleting a connection
+    private final class DeleteConnectionRequestHandler extends RequestHandler {
+        private DeleteConnectionRequestHandler() {
+            super(ROADM_DELETE_FLOW_REQ);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
+            FlowId flowId = FlowId.valueOf(payload.get(ID).asLong());
+            roadmService.removeConnection(deviceId, flowId);
+        }
+    }
+
+    // Handler for creating a creating a connection from form data
+    private final class CreateConnectionRequestHandler extends RequestHandler {
+
+        // Keys to load from JSON
+        private static final String FORM_DATA = "formData";
+        private static final String CHANNEL_SPACING_INDEX = "index";
+        private static final String INCLUDE_ATTENUATION = "includeAttenuation";
+
+        // Keys for validation results
+        private static final String CONNECTION = "connection";
+        private static final String CHANNEL_AVAILABLE = "channelAvailable";
+
+        // Error messages to display to user
+        private static final String IN_PORT_ERR_MSG = "Invalid input port.";
+        private static final String OUT_PORT_ERR_MSG = "Invalid output port.";
+        private static final String CONNECTION_ERR_MSG =
+                "Invalid connection from input port to output port.";
+        private static final String CHANNEL_SPACING_ERR_MSG =
+                "Channel spacing not supported.";
+        private static final String CHANNEL_ERR_MSG =
+                "Channel index must be in range %s.";
+        private static final String CHANNEL_AVAILABLE_ERR_MSG =
+                "Channel is already being used.";
+        private static final String ATTENUATION_ERR_MSG =
+                "Attenuation must be in range %s.";
+
+        // Keys for validation object
+        private static final String VALID = "valid";
+        private static final String MESSAGE = "message";
+
+        private CreateConnectionRequestHandler() {
+            super(ROADM_CREATE_FLOW_REQ);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            DeviceId did = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
+            ObjectNode flowNode = node(payload, FORM_DATA);
+            int priority = (int) number(flowNode, PRIORITY);
+            boolean permanent = bool(flowNode, PERMANENT);
+            int timeout = (int) number(flowNode, TIMEOUT);
+            PortNumber inPort = PortNumber.portNumber(number(flowNode, IN_PORT));
+            PortNumber outPort = PortNumber.portNumber(number(flowNode, OUT_PORT));
+            ObjectNode chNode = node(flowNode, CHANNEL_SPACING);
+            ChannelSpacing spacing =
+                    channelSpacing((int) number(chNode, CHANNEL_SPACING_INDEX));
+            int multiplier = (int) number(flowNode, CHANNEL_MULTIPLIER);
+            OchSignal och = OchSignal.newDwdmSlot(spacing, multiplier);
+            boolean includeAttenuation = bool(flowNode, INCLUDE_ATTENUATION);
+            long att = number(flowNode, ATTENUATION);
+
+            boolean validInPort = roadmService.validInputPort(did, inPort);
+            boolean validOutPort = roadmService.validOutputPort(did, outPort);
+            boolean validConnect = roadmService.validConnection(did, inPort, outPort);
+            boolean validSpacing = true;
+            boolean validChannel = roadmService.validChannel(did, inPort, och);
+            boolean channelAvailable = roadmService.channelAvailable(did, och);
+            boolean validAttenuation = roadmService.attenuationInRange(did, outPort, att);
+
+            if (validConnect && validChannel && channelAvailable) {
+                if (includeAttenuation && validAttenuation) {
+                    roadmService.createConnection(did, priority, permanent,
+                                                  timeout, inPort, outPort,
+                                                  och, att);
+                } else if (!includeAttenuation) {
+                    roadmService.createConnection(did, priority, permanent,
+                                                  timeout, inPort, outPort,
+                                                  och);
+                }
+            }
+
+            // Construct error for channel
+            String channelMessage = "Invalid channel";
+            if (!validChannel) {
+                Set<OchSignal> lambdas = roadmService.queryLambdas(did, outPort);
+                if (lambdas != null) {
+                    Range<Integer> range = channelRange(lambdas);
+                    if (range.contains(och.spacingMultiplier())) {
+                        // Channel spacing error
+                        validSpacing = false;
+                    } else {
+                        channelMessage = String.format(CHANNEL_ERR_MSG, range.toString());
+                    }
+                }
+            }
+
+            // Construct error for attenuation
+            String attenuationMessage = "Invalid attenuation";
+            if (!validAttenuation) {
+                Range<Long> range =
+                        roadmService.attenuationRange(did, outPort, och);
+                if (range != null) {
+                    attenuationMessage =
+                            String.format(ATTENUATION_ERR_MSG, range.toString());
+                }
+            }
+
+            // Build response
+            ObjectNode node = objectNode();
+
+            node.set(IN_PORT, validationObject(validInPort, IN_PORT_ERR_MSG));
+            node.set(OUT_PORT, validationObject(validOutPort, OUT_PORT_ERR_MSG));
+            node.set(CONNECTION, validationObject(validConnect, CONNECTION_ERR_MSG));
+            node.set(CHANNEL_SPACING, validationObject(validChannel || validSpacing,
+                                                       CHANNEL_SPACING_ERR_MSG));
+            node.set(CHANNEL_MULTIPLIER, validationObject(validChannel || !validSpacing,
+                                                          channelMessage));
+            node.set(CHANNEL_AVAILABLE, validationObject(!validChannel || channelAvailable,
+                                                         CHANNEL_AVAILABLE_ERR_MSG));
+            node.set(ATTENUATION, validationObject(validAttenuation, attenuationMessage));
+            node.put(INCLUDE_ATTENUATION, includeAttenuation);
+
+            sendMessage(ROADM_CREATE_FLOW_RESP, node);
+        }
+
+        // Returns the ChannelSpacing based on the selection made
+        private ChannelSpacing channelSpacing(int selectionIndex) {
+            switch (selectionIndex) {
+                case 0: return ChannelSpacing.CHL_100GHZ;
+                case 1: return ChannelSpacing.CHL_50GHZ;
+                case 2: return ChannelSpacing.CHL_25GHZ;
+                case 3: return ChannelSpacing.CHL_12P5GHZ;
+                // 6.25GHz cannot be used with ChannelSpacing.newDwdmSlot
+                // case 4: return ChannelSpacing.CHL_6P25GHZ;
+                default: return ChannelSpacing.CHL_50GHZ;
+            }
+        }
+
+        // Construct validation object to return to the view
+        private ObjectNode validationObject(boolean result, String message) {
+            ObjectNode node = objectNode();
+            node.put(VALID, result);
+            if (!result) {
+                // return error message to display if validation failed
+                node.put(MESSAGE, message);
+            }
+            return node;
+        }
+
+        // Returns the minimum and maximum channel spacing
+        private Range<Integer> channelRange(Set<OchSignal> signals) {
+            Comparator<OchSignal> compare =
+                    (OchSignal a, OchSignal b) -> a.spacingMultiplier() - b.spacingMultiplier();
+            OchSignal minOch = Collections.min(signals, compare);
+            OchSignal maxOch = Collections.max(signals, compare);
+            return Range.closed(minOch.spacingMultiplier(), maxOch.spacingMultiplier());
+        }
+    }
+}