/* * 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.onlab.util.Frequency; import org.onlab.util.Spectrum; import org.onosproject.net.ChannelSpacing; import org.onosproject.net.DeviceId; import org.onosproject.net.device.DeviceService; 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; import static org.onosproject.net.Device.Type; /** * 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 ROADM_SHOW_ITEMS_REQ = "roadmShowFlowItemsRequest"; private static final String ROADM_SHOW_ITEMS_RESP = "roadmShowFlowItemsResponse"; 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 CHANNEL_FREQUENCY = "channelFrequency"; 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, CHANNEL_FREQUENCY, CURRENT_POWER, ATTENUATION, HAS_ATTENUATION }; private RoadmService roadmService; private DeviceService deviceService; private FlowRuleService flowRuleService; private final Logger log = LoggerFactory.getLogger(getClass()); @Override public void init(UiConnection connection, ServiceDirectory directory) { super.init(connection, directory); roadmService = get(RoadmService.class); deviceService = get(DeviceService.class); flowRuleService = get(FlowRuleService.class); } @Override protected Collection<RequestHandler> createRequestHandlers() { return ImmutableSet.of( new FlowTableDataRequestHandler(), new SetAttenuationRequestHandler(), new DeleteConnectionRequestHandler(), new CreateConnectionRequestHandler(), new CreateShowItemsRequestHandler() ); } // 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 RoadmUtil.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, RoadmUtil.DEV_ID)); // Update flows 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); String spacing = RoadmUtil.NA, multiplier = RoadmUtil.NA, channelFrequency = ""; OchSignal ochSignal = cd.ochSignal(); if (ochSignal != null) { Frequency spacingFreq = ochSignal.channelSpacing().frequency(); spacing = RoadmUtil.asGHz(spacingFreq); int spacingMult = ochSignal.spacingMultiplier(); multiplier = String.valueOf(spacingMult); channelFrequency = String.format(" (%sGHz)", RoadmUtil.asGHz(Spectrum.CENTER_FREQUENCY.add(spacingFreq.multiply(spacingMult)))); } 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, spacing) .cell(CHANNEL_MULTIPLIER, multiplier) .cell(CHANNEL_FREQUENCY, channelFrequency) .cell(CURRENT_POWER, getCurrentPower(deviceId, cd)) .cell(HAS_ATTENUATION, hasAttenuation(deviceId, cd)) .cell(ATTENUATION, getAttenuation(deviceId, cd)); } private String getCurrentPower(DeviceId deviceId, ChannelData channelData) { if (hasAttenuation(deviceId, channelData)) { // report channel power if channel exists Long currentPower = roadmService.getCurrentChannelPower(deviceId, channelData.outPort(), channelData.ochSignal()); return RoadmUtil.objectToString(currentPower, RoadmUtil.UNKNOWN); } // otherwise, report port power Type devType = deviceService.getDevice(deviceId).type(); PortNumber port = devType == Type.FIBER_SWITCH ? channelData.inPort() : channelData.outPort(); Long currentPower = roadmService.getCurrentPortPower(deviceId, port); return RoadmUtil.objectToString(currentPower, RoadmUtil.UNKNOWN); } private String getAttenuation(DeviceId deviceId, ChannelData channelData) { OchSignal signal = channelData.ochSignal(); if (signal == null) { return RoadmUtil.NA; } Long attenuation = roadmService.getAttenuation(deviceId, channelData.outPort(), signal); return RoadmUtil.objectToString(attenuation, RoadmUtil.UNKNOWN); } private boolean hasAttenuation(DeviceId deviceId, ChannelData channelData) { OchSignal signal = channelData.ochSignal(); if (signal == null) { return false; } return roadmService.attenuationRange(deviceId, channelData.outPort(), signal) != null; } } // Handler for setting attenuation private final class SetAttenuationRequestHandler extends RequestHandler { // 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, RoadmUtil.DEV_ID)); FlowId flowId = FlowId.valueOf(number(payload, FLOW_ID)); // Get connection information from the flow FlowEntry entry = findFlow(deviceId, flowId); if (entry == null) { log.error("Unable to find flow rule to set attenuation for device {}", deviceId); return; } ChannelData channelData = ChannelData.fromFlow(entry); PortNumber port = channelData.outPort(); OchSignal signal = channelData.ochSignal(); Range<Long> range = roadmService.attenuationRange(deviceId, port, signal); Long attenuation = payload.get(ATTENUATION).asLong(); boolean validAttenuation = range != null && range.contains(attenuation); if (validAttenuation) { roadmService.setAttenuation(deviceId, port, signal, 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(RoadmUtil.VALID, validAttenuation); if (range == null) { rootNode.put(RoadmUtil.MESSAGE, NO_ATTENUATION_MSG); } else { rootNode.put(RoadmUtil.MESSAGE, String.format(ATTENUATION_RANGE_MSG, range.toString())); } 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, RoadmUtil.DEV_ID)); 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"; // 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."; private CreateConnectionRequestHandler() { super(ROADM_CREATE_FLOW_REQ); } @Override public void process(ObjectNode payload) { DeviceId did = DeviceId.deviceId(string(payload, RoadmUtil.DEV_ID)); 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); long att = number(flowNode, ATTENUATION); boolean showItems = deviceService.getDevice(did).type() != Type.FIBER_SWITCH; 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) { if (validChannel && channelAvailable) { if (validAttenuation) { roadmService.createConnection(did, priority, permanent, timeout, inPort, outPort, och, att); } else { roadmService.createConnection(did, priority, permanent, timeout, inPort, outPort, och); } } } String channelMessage = "Invalid channel"; String attenuationMessage = "Invalid attenuation"; if (showItems) { // Construct error for 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 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)); 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(RoadmUtil.VALID, result); if (!result) { // return error message to display if validation failed node.put(RoadmUtil.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()); } } private final class CreateShowItemsRequestHandler extends RequestHandler { private static final String SHOW_CHANNEL = "showChannel"; private static final String SHOW_ATTENUATION = "showAttenuation"; private CreateShowItemsRequestHandler() { super(ROADM_SHOW_ITEMS_REQ); } @Override public void process(ObjectNode payload) { DeviceId did = DeviceId.deviceId(string(payload, RoadmUtil.DEV_ID)); Type devType = deviceService.getDevice(did).type(); // Build response ObjectNode node = objectNode(); node.put(SHOW_CHANNEL, devType != Type.FIBER_SWITCH); node.put(SHOW_ATTENUATION, devType == Type.ROADM); sendMessage(ROADM_SHOW_ITEMS_RESP, node); } } }