blob: c21841c7d4da0d03819f650f932edd6e6fc065ea [file] [log] [blame]
/*
* 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.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);
}
}
}