[ONOS-7995] upgrade roadm custom view from GUI to GUI2
1. [Finished] Optical-related device view
2. [Finished] Port view
3. [Finished] Power config on Port view (Private implementation)
4. [Finished] Protocol view for TAPI device
Change-Id: Ie9b81cfa9991a7af2dd485a5df3fe582df830d27
Signed-off-by: Boyuan Yan <boyuan@opennetworking.org>
diff --git a/apps/roadm/app/BUILD b/apps/roadm/app/BUILD
new file mode 100644
index 0000000..ca46936
--- /dev/null
+++ b/apps/roadm/app/BUILD
@@ -0,0 +1,13 @@
+COMPILE_DEPS = CORE_DEPS + JACKSON + [
+ "//core/store/serializers:onos-core-serializers",
+ "//apps/optical-model:onos-apps-optical-model",
+]
+
+TEST_DEPS = TEST_ADAPTERS + [
+ "//utils/osgi:onlab-osgi-tests",
+]
+
+osgi_jar_with_tests(
+ test_deps = TEST_DEPS,
+ deps = COMPILE_DEPS,
+)
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/ChannelData.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/ChannelData.java
new file mode 100644
index 0000000..eb31a5e
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/ChannelData.java
@@ -0,0 +1,101 @@
+/*
+ * 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 org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.OchSignalCriterion;
+import org.onosproject.net.flow.criteria.PortCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.List;
+
+/**
+ * Representation of an internal ROADM connection.
+ */
+public final class ChannelData {
+ private PortNumber inPort;
+ private PortNumber outPort;
+ private OchSignal ochSignal;
+
+ private ChannelData(PortNumber inPort, PortNumber outPort, OchSignal ochSignal) {
+ this.inPort = inPort;
+ this.outPort = outPort;
+ this.ochSignal = ochSignal;
+ }
+
+ /**
+ * Returns a ChannelData representation from a flow rule. The rule must contain
+ * a Criterion.Type.IN_PORT selector, Criterion.Type.OCH_SIGID selector, and
+ * Instruction.Type.OUTPUT instruction.
+ *
+ * @param rule the flow rule representing the connection
+ * @return ChannelData representation of the connection
+ */
+ public static ChannelData fromFlow(FlowRule rule) {
+ checkNotNull(rule);
+
+ Criterion in = rule.selector().getCriterion(Criterion.Type.IN_PORT);
+ checkNotNull(in);
+ PortNumber inPort = ((PortCriterion) in).port();
+
+ Criterion och = rule.selector().getCriterion(Criterion.Type.OCH_SIGID);
+ OchSignal ochSignal = och == null ? null : ((OchSignalCriterion) och).lambda();
+
+ PortNumber outPort = null;
+ List<Instruction> instructions = rule.treatment().allInstructions();
+ for (Instruction ins : instructions) {
+ if (ins.type() == Instruction.Type.OUTPUT) {
+ outPort = ((Instructions.OutputInstruction) ins).port();
+ }
+ }
+ checkNotNull(outPort);
+
+ return new ChannelData(inPort, outPort, ochSignal);
+ }
+
+ /**
+ * Returns the input port.
+ *
+ * @return input port
+ */
+ public PortNumber inPort() {
+ return inPort;
+ }
+
+ /**
+ * Returns the output port.
+ *
+ * @return output port
+ */
+ public PortNumber outPort() {
+ return outPort;
+ }
+
+ /**
+ * Returns the channel signal.
+ *
+ * @return channel signal
+ */
+ public OchSignal ochSignal() {
+ return ochSignal;
+ }
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java
new file mode 100644
index 0000000..e13b2c7
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java
@@ -0,0 +1,105 @@
+/*
+ * 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 org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.store.serializers.KryoNamespaces;
+import org.onosproject.store.service.ConsistentMap;
+import org.onosproject.store.service.Serializer;
+import org.onosproject.store.service.StorageService;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages the port target powers for ROADM devices.
+ */
+@Component(immediate = true, service = RoadmStore.class)
+public class DistributedRoadmStore implements RoadmStore {
+ private static Logger log = LoggerFactory.getLogger(DistributedRoadmStore.class);
+
+ private ConsistentMap<DeviceId, Map<PortNumber, Long>> distPowerMap;
+ private Map<DeviceId, Map<PortNumber, Long>> powerMap;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected StorageService storageService;
+
+ @Activate
+ public void activate() {
+ distPowerMap = storageService.<DeviceId, Map<PortNumber, Long>>consistentMapBuilder()
+ .withName("onos-roadm-distributed-store")
+ .withSerializer(Serializer.using(KryoNamespaces.API))
+ .build();
+ powerMap = distPowerMap.asJavaMap();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ log.info("Stopped");
+ }
+
+ // Add a map to the store for a device if not already added.
+ // Powers still need to be initialized with calls to setTargetPower().
+ @Override
+ public void addDevice(DeviceId deviceId) {
+ powerMap.putIfAbsent(deviceId, new HashMap<>());
+ log.info("Initializing {}", deviceId);
+ }
+
+ // Returns true if Map for device exists in ConsistentMap
+ @Override
+ public boolean deviceAvailable(DeviceId deviceId) {
+ return powerMap.get(deviceId) != null;
+ }
+
+
+ @Override
+ public void setTargetPower(DeviceId deviceId, PortNumber portNumber, long targetPower) {
+ Map<PortNumber, Long> portMap = powerMap.get(deviceId);
+ if (portMap != null) {
+ portMap.put(portNumber, targetPower);
+ powerMap.put(deviceId, portMap);
+ } else {
+ log.info("Device {} not found in store", deviceId);
+ }
+ }
+
+ @Override
+ public Long getTargetPower(DeviceId deviceId, PortNumber portNumber) {
+ Map<PortNumber, Long> portMap = powerMap.get(deviceId);
+ if (portMap != null) {
+ return portMap.get(portNumber);
+ }
+ return null;
+ }
+
+ @Override
+ public void removeTargetPower(DeviceId deviceId, PortNumber portNumber) {
+ if (powerMap.get(deviceId) != null) {
+ powerMap.get(deviceId).remove(portNumber);
+ }
+ }
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java
new file mode 100644
index 0000000..b9506d2
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java
@@ -0,0 +1,117 @@
+/*
+ * 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 org.onlab.osgi.ServiceDirectory;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.device.DeviceService;
+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 java.util.Collection;
+
+import static org.onosproject.net.Device.Type;
+
+/**
+ * Table-View message handler for ROADM device view.
+ */
+public class RoadmDeviceViewMessageHandler extends UiMessageHandler {
+
+ private static final String ROADM_DEVICE_DATA_REQ = "roadmDataRequest";
+ private static final String ROADM_DEVICE_DATA_RESP = "roadmDataResponse";
+ private static final String ROADM_DEVICES = "roadms";
+
+ private static final String ID = "id";
+ private static final String NAME = "name";
+ private static final String TYPE = "type";
+ private static final String MASTER = "master";
+ private static final String PORTS = "ports";
+ private static final String VENDOR = "vendor";
+ private static final String HW_VERSION = "hwVersion";
+ private static final String SW_VERSION = "swVersion";
+ private static final String PROTOCOL = "protocol";
+
+ private static final String[] COLUMN_IDS = {
+ ID, NAME, TYPE, MASTER, PORTS, VENDOR, HW_VERSION, SW_VERSION, PROTOCOL
+ };
+
+ private DeviceService deviceService;
+ private MastershipService mastershipService;
+
+ @Override
+ public void init(UiConnection connection, ServiceDirectory directory) {
+ super.init(connection, directory);
+ deviceService = get(DeviceService.class);
+ mastershipService = get(MastershipService.class);
+ }
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new DeviceTableDataRequestHandler());
+ }
+
+ // Handler for sample table requests
+ private final class DeviceTableDataRequestHandler extends TableRequestHandler {
+
+ private DeviceTableDataRequestHandler() {
+ super(ROADM_DEVICE_DATA_REQ, ROADM_DEVICE_DATA_RESP, ROADM_DEVICES);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COLUMN_IDS;
+ }
+
+ @Override
+ protected String noRowsMessage(ObjectNode payload) {
+ return RoadmUtil.NO_ROWS_MESSAGE;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ for (Device device : deviceService.getDevices()) {
+ Type type = device.type();
+ if (type == Type.ROADM || type == Type.TERMINAL_DEVICE
+ || type == Type.OPTICAL_AMPLIFIER || type == Type.FIBER_SWITCH
+ || type == Type.OLS) {
+ populateRow(tm.addRow(), device);
+ }
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Device device) {
+ DeviceId devId = device.id();
+ String id = devId.toString();
+ row.cell(ID, id)
+ .cell(NAME, RoadmUtil.getAnnotation(device.annotations(), AnnotationKeys.NAME, id))
+ .cell(TYPE, RoadmUtil.objectToString(device.type(), RoadmUtil.UNKNOWN))
+ .cell(MASTER, mastershipService.getMasterFor(devId))
+ .cell(PORTS, deviceService.getPorts(devId).size())
+ .cell(VENDOR, device.manufacturer())
+ .cell(HW_VERSION, device.hwVersion())
+ .cell(SW_VERSION, device.swVersion())
+ .cell(PROTOCOL, RoadmUtil.getAnnotation(device.annotations(), PROTOCOL));
+ }
+ }
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java
new file mode 100644
index 0000000..c21841c
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java
@@ -0,0 +1,429 @@
+/*
+ * 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);
+ }
+ }
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmManager.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmManager.java
new file mode 100644
index 0000000..a98b252
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmManager.java
@@ -0,0 +1,653 @@
+/*
+ * 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.google.common.collect.ImmutableMap;
+import com.google.common.collect.Range;
+import org.onosproject.core.ApplicationId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.ChannelSpacing;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Direction;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.OchSignalType;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.LambdaQuery;
+import org.onosproject.net.behaviour.PowerConfig;
+import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointState;
+import org.onosproject.net.behaviour.protection.ProtectionConfigBehaviour;
+import org.onosproject.net.behaviour.protection.TransportEndpointState;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceListener;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.flow.DefaultFlowRule;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowId;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onosproject.net.optical.OpticalAnnotations.INPUT_PORT_STATUS;
+import static org.onosproject.roadm.RoadmUtil.OPS_OPT_AUTO;
+import static org.onosproject.roadm.RoadmUtil.OPS_OPT_FORCE;
+import static org.onosproject.roadm.RoadmUtil.OPS_OPT_MANUAL;
+
+/**
+ * Application for monitoring and configuring ROADM devices.
+ */
+@Component(immediate = true, service = RoadmService.class)
+public class RoadmManager implements RoadmService {
+
+ private static final String APP_NAME = "org.onosproject.roadm";
+ private ApplicationId appId;
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private DeviceListener deviceListener = new InternalDeviceListener();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected RoadmStore roadmStore;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected CoreService coreService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected FlowRuleService flowRuleService;
+
+ @Activate
+ protected void activate() {
+ appId = coreService.registerApplication(APP_NAME);
+ deviceService.addListener(deviceListener);
+ initDevices();
+
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ deviceService.removeListener(deviceListener);
+
+ log.info("Stopped");
+ }
+
+ @Deprecated
+ @Override
+ public void setProtectionSwitchWorkingPath(DeviceId deviceId, int index) {
+ checkNotNull(deviceId);
+ ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
+ if (behaviour == null) {
+ return;
+ }
+ Map<ConnectPoint, ProtectedTransportEndpointState> map = getProtectionSwitchStates(behaviour);
+ if (map == null) {
+ log.warn("Failed to get protected transport endpoint state in device {}", deviceId);
+ return;
+ }
+ if (map.isEmpty()) {
+ log.warn("No protected transport endpoint state found in device {}", deviceId);
+ return;
+ }
+ behaviour.switchToManual(map.keySet().toArray(new ConnectPoint[0])[0], index);
+ }
+
+ @Deprecated
+ @Override
+ public String getProtectionSwitchPortState(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
+ if (behaviour == null) {
+ return null;
+ }
+ Map<ConnectPoint, ProtectedTransportEndpointState> map = getProtectionSwitchStates(behaviour);
+ if (map == null) {
+ log.warn("Failed to get protected transport endpoint state in device {}", deviceId);
+ return null;
+ }
+ for (ProtectedTransportEndpointState state : map.values()) {
+ for (TransportEndpointState element : state.pathStates()) {
+ if (element.description().output().connectPoint().port().equals(portNumber)) {
+ return element.attributes().get(INPUT_PORT_STATUS);
+ }
+ }
+ }
+ // Do not need warning here for port polling.
+ log.debug("Unable to get port status, device: {}, port: {}", deviceId, portNumber);
+ return null;
+ }
+
+ @Override
+ public void configProtectionSwitch(DeviceId deviceId, String operation, ConnectPoint identifier, int index) {
+ checkNotNull(deviceId);
+ ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
+ if (behaviour == null) {
+ return;
+ }
+ // automatic operation
+ if (OPS_OPT_AUTO.equals(operation)) {
+ behaviour.switchToAutomatic(identifier);
+ return;
+ }
+ // force or manual operation
+ if (OPS_OPT_MANUAL.equals(operation)) {
+ behaviour.switchToManual(identifier, index);
+ } else if (OPS_OPT_FORCE.equals(operation)) {
+ behaviour.switchToForce(identifier, index);
+ }
+ }
+
+ @Override
+ public Map<ConnectPoint, ProtectedTransportEndpointState> getProtectionSwitchStates(DeviceId deviceId) {
+ checkNotNull(deviceId);
+ ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
+ if (behaviour == null) {
+ return ImmutableMap.of();
+ }
+ return getProtectionSwitchStates(behaviour);
+ }
+
+
+ @Override
+ public void setTargetPortPower(DeviceId deviceId, PortNumber portNumber, long power) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ roadmStore.setTargetPower(deviceId, portNumber, power);
+ powerConfig.setTargetPower(portNumber, Direction.ALL, power);
+ } else {
+ log.warn("Unable to set target port power for device {}", deviceId);
+ }
+ }
+
+ @Override
+ public Long getTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ // Request target port power when it doesn't exist. Inactive updating mode.
+ Long power = roadmStore.getTargetPower(deviceId, portNumber);
+ if (power == null) {
+ return syncTargetPortPower(deviceId, portNumber);
+ }
+ return power;
+ }
+
+ @Override
+ public Long syncTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Long> pl = powerConfig.getTargetPower(portNumber, Direction.ALL);
+ if (pl.isPresent()) {
+ roadmStore.setTargetPower(deviceId, portNumber, pl.get());
+ return pl.get();
+ } else {
+ roadmStore.removeTargetPower(deviceId, portNumber);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void setAttenuation(DeviceId deviceId, PortNumber portNumber,
+ OchSignal ochSignal, long attenuation) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ checkNotNull(ochSignal);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ powerConfig.setTargetPower(portNumber, ochSignal, attenuation);
+ } else {
+ log.warn("Cannot set attenuation for channel index {} on device {}",
+ ochSignal.spacingMultiplier(), deviceId);
+ }
+ }
+
+ @Override
+ public Long getAttenuation(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ checkNotNull(ochSignal);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Long> attenuation = powerConfig.getTargetPower(portNumber, ochSignal);
+ if (attenuation.isPresent()) {
+ return attenuation.get();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Long getCurrentPortPower(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Long> currentPower = powerConfig.currentPower(portNumber, Direction.ALL);
+ if (currentPower.isPresent()) {
+ return currentPower.get();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Long getCurrentChannelPower(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ checkNotNull(ochSignal);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Long> currentPower = powerConfig.currentPower(portNumber, ochSignal);
+ if (currentPower.isPresent()) {
+ return currentPower.get();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Set<OchSignal> queryLambdas(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ LambdaQuery lambdaQuery = getLambdaQuery(deviceId);
+ if (lambdaQuery != null) {
+ return lambdaQuery.queryLambdas(portNumber);
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
+ int timeout, PortNumber inPort, PortNumber outPort, OchSignal ochSignal) {
+ checkNotNull(deviceId);
+ checkNotNull(inPort);
+ checkNotNull(outPort);
+
+ TrafficSelector selector = DefaultTrafficSelector.builder()
+ .add(Criteria.matchInPort(inPort))
+ .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
+ .add(Criteria.matchLambda(ochSignal))
+ .build();
+ TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+ .add(Instructions.createOutput(outPort))
+ .build();
+
+ FlowRule.Builder flowBuilder = DefaultFlowRule.builder()
+ .forDevice(deviceId)
+ .fromApp(appId)
+ .withPriority(priority)
+ .withSelector(selector)
+ .withTreatment(treatment);
+ if (isPermanent) {
+ flowBuilder.makePermanent();
+ } else {
+ flowBuilder.makeTemporary(timeout);
+ }
+
+ FlowRule flowRule = flowBuilder.build();
+ flowRuleService.applyFlowRules(flowRule);
+
+ log.info("Created connection from input port {} to output port {}",
+ inPort.toLong(), outPort.toLong());
+
+ return flowRule.id();
+ }
+
+ @Override
+ public FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
+ int timeout, PortNumber inPort, PortNumber outPort,
+ OchSignal ochSignal, long attenuation) {
+ checkNotNull(deviceId);
+ checkNotNull(inPort);
+ checkNotNull(outPort);
+ FlowId flowId = createConnection(deviceId, priority, isPermanent, timeout, inPort, outPort, ochSignal);
+ delayedSetAttenuation(deviceId, outPort, ochSignal, attenuation);
+ return flowId;
+ }
+
+ @Override
+ public void removeConnection(DeviceId deviceId, FlowId flowId) {
+ checkNotNull(deviceId);
+ checkNotNull(flowId);
+ for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
+ if (entry.id().equals(flowId)) {
+ flowRuleService.removeFlowRules(entry);
+ log.info("Deleted connection {}", entry.id());
+ break;
+ }
+ }
+ }
+
+ @Override
+ public boolean hasPortTargetPower(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
+ return range.isPresent();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean portTargetPowerInRange(DeviceId deviceId, PortNumber portNumber, long power) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
+ return range.isPresent() && range.get().contains(power);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean attenuationInRange(DeviceId deviceId, PortNumber outPort, long att) {
+ checkNotNull(deviceId);
+ checkNotNull(outPort);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ OchSignal stubOch = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 0);
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(outPort, stubOch);
+ return range.isPresent() && range.get().contains(att);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean validInputPort(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Range<Long>> range = powerConfig.getInputPowerRange(portNumber, Direction.ALL);
+ return range.isPresent();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean validOutputPort(DeviceId deviceId, PortNumber portNumber) {
+ return hasPortTargetPower(deviceId, portNumber);
+ }
+
+ @Override
+ public boolean validChannel(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ checkNotNull(ochSignal);
+ LambdaQuery lambdaQuery = getLambdaQuery(deviceId);
+ if (lambdaQuery != null) {
+ Set<OchSignal> channels = lambdaQuery.queryLambdas(portNumber);
+ return channels.contains(ochSignal);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean channelAvailable(DeviceId deviceId, OchSignal ochSignal) {
+ checkNotNull(deviceId);
+ checkNotNull(ochSignal);
+ for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
+ if (ChannelData.fromFlow(entry).ochSignal().equals(ochSignal)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean validConnection(DeviceId deviceId, PortNumber inPort, PortNumber outPort) {
+ checkNotNull(deviceId);
+ checkNotNull(inPort);
+ checkNotNull(outPort);
+ return validInputPort(deviceId, inPort) && validOutputPort(deviceId, outPort);
+ }
+
+ @Override
+ public Range<Long> targetPortPowerRange(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
+ if (range.isPresent()) {
+ return range.get();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Range<Long> attenuationRange(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ checkNotNull(ochSignal);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(portNumber, ochSignal);
+ if (range.isPresent()) {
+ return range.get();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Range<Long> inputPortPowerRange(DeviceId deviceId, PortNumber portNumber) {
+ checkNotNull(deviceId);
+ checkNotNull(portNumber);
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig != null) {
+ Optional<Range<Long>> range = powerConfig.getInputPowerRange(portNumber, Direction.ALL);
+ if (range.isPresent()) {
+ return range.get();
+ }
+ }
+ return null;
+ }
+
+ private PowerConfig<Object> getPowerConfig(DeviceId deviceId) {
+ Device device = deviceService.getDevice(deviceId);
+ if (device != null && device.is(PowerConfig.class)) {
+ return device.as(PowerConfig.class);
+ }
+ // Do not need warning here for port polling.
+ log.debug("Unable to load PowerConfig for {}", deviceId);
+ return null;
+ }
+
+ private LambdaQuery getLambdaQuery(DeviceId deviceId) {
+ Device device = deviceService.getDevice(deviceId);
+ if (device != null && device.is(LambdaQuery.class)) {
+ return device.as(LambdaQuery.class);
+ }
+ // Do not need warning here for port polling.
+ log.debug("Unable to load LambdaQuery for {}", deviceId);
+ return null;
+ }
+
+ private ProtectionConfigBehaviour getProtectionConfig(DeviceId deviceId) {
+ Device device = deviceService.getDevice(deviceId);
+ if (device != null && device.is(ProtectionConfigBehaviour.class)) {
+ return device.as(ProtectionConfigBehaviour.class);
+ }
+ // Do not need warning here for port polling.
+ log.debug("Unable to load ProtectionConfigBehaviour for {}", deviceId);
+ return null;
+ }
+
+ // Initialize all devices
+ private void initDevices() {
+ for (Device device : deviceService.getDevices(Device.Type.ROADM)) {
+ initDevice(device.id());
+ //FIXME
+ // As roadm application is a optional tool for now.
+ // The target power initialization will be enhanced later,
+ // hopefully using an formal optical subsystem.
+ // setAllInitialTargetPortPowers(device.id());
+ }
+ }
+
+ // Initialize RoadmStore for a device to support target power
+ private void initDevice(DeviceId deviceId) {
+ if (!roadmStore.deviceAvailable(deviceId)) {
+ roadmStore.addDevice(deviceId);
+ }
+ log.info("Initialized device {}", deviceId);
+ }
+
+ // Sets the target port powers for a port on a device
+ // Attempts to read target powers from store. If no value is found then
+ // default value is used instead.
+ private void setInitialTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig == null) {
+ log.warn("Unable to set default initial powers for port {} on device {}", portNumber, deviceId);
+ return;
+ }
+
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
+ if (!range.isPresent()) {
+ log.warn("No target power range found for port {} on device {}", portNumber, deviceId);
+ return;
+ }
+
+ Long power = roadmStore.getTargetPower(deviceId, portNumber);
+ if (power == null) {
+ // Set default to middle of the range
+ power = (range.get().lowerEndpoint() + range.get().upperEndpoint()) / 2;
+ roadmStore.setTargetPower(deviceId, portNumber, power);
+ }
+ powerConfig.setTargetPower(portNumber, Direction.ALL, power);
+ }
+
+ // Sets the target port powers for each each port on a device
+ // Attempts to read target powers from store. If no value is found then
+ // default value is used instead
+ private void setAllInitialTargetPortPowers(DeviceId deviceId) {
+ PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
+ if (powerConfig == null) {
+ log.warn("Unable to set default initial powers for device {}", deviceId);
+ return;
+ }
+
+ List<Port> ports = deviceService.getPorts(deviceId);
+ for (Port port : ports) {
+ Optional<Range<Long>> range = powerConfig.getTargetPowerRange(port.number(), Direction.ALL);
+ if (range.isPresent()) {
+ Long power = roadmStore.getTargetPower(deviceId, port.number());
+ if (power == null) {
+ // Set default to middle of the range
+ power = (range.get().lowerEndpoint() + range.get().upperEndpoint()) / 2;
+ roadmStore.setTargetPower(deviceId, port.number(), power);
+ }
+ powerConfig.setTargetPower(port.number(), Direction.ALL, power);
+ } else {
+ log.warn("No target power range found for port {} on device {}", port.number(), deviceId);
+ }
+ }
+ }
+
+ // Delay the call to setTargetPower because the flow may not be in the store yet
+ // Tested with Lumentum ROADM-20 1 seconds was not enough, increased to 5 seconds
+ private void delayedSetAttenuation(DeviceId deviceId, PortNumber outPort,
+ OchSignal ochSignal, long attenuation) {
+ Runnable setAtt = () -> {
+ try {
+ TimeUnit.SECONDS.sleep(5);
+ } catch (InterruptedException e) {
+ log.warn("Thread interrupted. Setting attenuation early.");
+ Thread.currentThread().interrupt();
+ }
+ setAttenuation(deviceId, outPort, ochSignal, attenuation);
+ };
+ new Thread(setAtt).start();
+ }
+
+ // get protection endpoint states
+ private Map<ConnectPoint, ProtectedTransportEndpointState> getProtectionSwitchStates(
+ ProtectionConfigBehaviour behaviour) {
+ Map<ConnectPoint, ProtectedTransportEndpointState> map;
+ try {
+ map = behaviour.getProtectionEndpointStates().get();
+ } catch (InterruptedException e1) {
+ log.error("Interrupted.", e1);
+ Thread.currentThread().interrupt();
+ return ImmutableMap.of();
+ } catch (ExecutionException e1) {
+ log.error("Exception caught.", e1);
+ return ImmutableMap.of();
+ }
+ return map;
+ }
+
+ // Listens to device events.
+ private class InternalDeviceListener implements DeviceListener {
+ @Override
+ public void event(DeviceEvent deviceEvent) {
+ Device device = deviceEvent.subject();
+
+ switch (deviceEvent.type()) {
+ case DEVICE_ADDED:
+ case DEVICE_UPDATED:
+ initDevice(device.id());
+ break;
+ case PORT_ADDED:
+ case PORT_UPDATED:
+ //FIXME
+ // As roadm application is a optional tool for now.
+ // The target power initialization will be enhanced later,
+ // hopefully using an formal optical subsystem.
+ // setInitialTargetPortPower(device.id(), deviceEvent.port().number());
+ break;
+ default:
+ break;
+
+ }
+ }
+ }
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java
new file mode 100644
index 0000000..dea83b0
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java
@@ -0,0 +1,378 @@
+/*
+ * 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.ArrayNode;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+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.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointState;
+import org.onosproject.net.behaviour.protection.TransportEndpointState;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.intent.OpticalPathIntent;
+import org.onosproject.net.optical.OpticalAnnotations;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.onosproject.net.Device.Type;
+import static org.onosproject.net.behaviour.protection.ProtectedTransportEndpointState.ACTIVE_UNKNOWN;
+import static org.onosproject.roadm.RoadmUtil.OPS_OPT_AUTO;
+import static org.onosproject.roadm.RoadmUtil.OPS_OPT_FORCE;
+import static org.onosproject.roadm.RoadmUtil.OPS_OPT_MANUAL;
+
+/**
+ * Table-View message handler for ROADM port view.
+ */
+public class RoadmPortViewMessageHandler extends UiMessageHandler {
+
+ private static final String ROADM_PORT_DATA_REQ = "roadmPortDataRequest";
+ private static final String ROADM_PORT_DATA_RESP = "roadmPortDataResponse";
+ private static final String ROADM_PORTS = "roadmPorts";
+ private static final String ROADM_SET_TARGET_POWER_REQ = "roadmSetTargetPowerRequest";
+ private static final String ROADM_SET_TARGET_POWER_RESP = "roadmSetTargetPowerResponse";
+ private static final String ROADM_SYNC_TARGET_POWER_REQ = "roadmSyncTargetPowerRequest";
+ private static final String ROADM_SYNC_TARGET_POWER_RESP = "roadmSyncTargetPowerResp";
+ private static final String ROADM_SHOW_ITEMS_REQ = "roadmShowPortItemsRequest";
+ private static final String ROADM_SHOW_ITEMS_RESP = "roadmShowPortItemsResponse";
+ private static final String ROADM_SET_OPS_MODE_REQ = "roadmSetOpsModeRequest";
+ private static final String ROADM_SET_OPS_MODE_RESP = "roadmSetOpsModeResponse";
+
+ private static final String ID = "id";
+ private static final String REVERSE_PORT = "reversePort";
+ private static final String NAME = "name";
+ private static final String TYPE = "type";
+ private static final String ENABLED = "enabled";
+ private static final String MIN_FREQ = "minFreq";
+ private static final String MAX_FREQ = "maxFreq";
+ private static final String GRID = "grid";
+ private static final String POWER_RANGE = "powerRange";
+ private static final String CURRENT_POWER = "currentPower";
+ private static final String TARGET_POWER = "targetPower";
+ private static final String HAS_TARGET_POWER = "hasTargetPower";
+ private static final String SERVICE_STATE = "serviceState";
+
+ private static final String[] COLUMN_IDS = {
+ ID, REVERSE_PORT, TYPE, NAME, ENABLED, MIN_FREQ, MAX_FREQ, GRID, POWER_RANGE,
+ CURRENT_POWER, SERVICE_STATE, TARGET_POWER, HAS_TARGET_POWER
+ };
+
+ private RoadmService roadmService;
+ private DeviceService deviceService;
+
+ 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);
+ }
+
+ @Override
+ protected Collection<RequestHandler> createRequestHandlers() {
+ return ImmutableSet.of(new PortTableDataRequestHandler(),
+ new SetTargetPowerRequestHandler(),
+ new CreateShowItemsRequestHandler(),
+ new CreateOpsModeSetRequestHandler(),
+ new SyncTargetPowerRequestHandler()
+ );
+ }
+
+ // Handler for sample table requests
+ private final class PortTableDataRequestHandler extends TableRequestHandler {
+
+ private PortTableDataRequestHandler() {
+ super(ROADM_PORT_DATA_REQ, ROADM_PORT_DATA_RESP, ROADM_PORTS);
+ }
+
+ @Override
+ protected String[] getColumnIds() {
+ return COLUMN_IDS;
+ }
+
+ @Override
+ protected String noRowsMessage(ObjectNode payload) {
+ return RoadmUtil.NO_ROWS_MESSAGE;
+ }
+
+ @Override
+ protected void populateTable(TableModel tm, ObjectNode payload) {
+ DeviceId deviceId = DeviceId.deviceId(string(payload, RoadmUtil.DEV_ID));
+ if (deviceService.isAvailable(deviceId)) {
+ List<Port> ports = deviceService.getPorts(deviceId);
+ for (Port port : ports) {
+ populateRow(tm.addRow(), port, deviceId);
+ }
+ }
+ }
+
+ private void populateRow(TableModel.Row row, Port port, DeviceId deviceId) {
+ PortNumber portNum = port.number();
+ getFrequencyLimit(deviceId, portNum);
+ row.cell(ID, portNum.toLong())
+ .cell(REVERSE_PORT, RoadmUtil.getAnnotation(port.annotations(),
+ OpticalPathIntent.REVERSE_PORT_ANNOTATION_KEY))
+ .cell(TYPE, port.type())
+ .cell(ENABLED, port.isEnabled())
+ .cell(NAME, RoadmUtil.getAnnotation(port.annotations(), AnnotationKeys.PORT_NAME))
+ .cell(MIN_FREQ, RoadmUtil.asTHz(minFreq))
+ .cell(MAX_FREQ, RoadmUtil.asTHz(maxFreq))
+ .cell(GRID, RoadmUtil.asGHz(channelSpacing))
+ .cell(POWER_RANGE, getPowerRange(deviceId, portNum))
+ .cell(CURRENT_POWER, getCurrentPower(deviceId, portNum))
+ .cell(SERVICE_STATE, getPortServiceState(deviceId, portNum))
+ .cell(TARGET_POWER, getTargetPower(deviceId, portNum))
+ .cell(HAS_TARGET_POWER, roadmService.hasPortTargetPower(deviceId, portNum));
+ }
+
+ private String getPortServiceState(DeviceId deviceId, PortNumber portNumber) {
+ if (deviceService.getDevice(deviceId).type() != Type.FIBER_SWITCH) {
+ return RoadmUtil.NA;
+ }
+ Map<ConnectPoint, ProtectedTransportEndpointState> map =
+ roadmService.getProtectionSwitchStates(deviceId);
+ for (ProtectedTransportEndpointState state : map.values()) {
+ for (TransportEndpointState element : state.pathStates()) {
+ if (element.description().output().connectPoint().port().equals(portNumber)) {
+ return RoadmUtil.defaultString(element.attributes()
+ .get(OpticalAnnotations.INPUT_PORT_STATUS), RoadmUtil.UNKNOWN);
+ }
+ }
+ }
+ return RoadmUtil.UNKNOWN;
+ }
+
+ private Frequency minFreq = null, maxFreq = null, channelSpacing = null;
+ // Gets min frequency, max frequency, channel spacing
+ private void getFrequencyLimit(DeviceId deviceId, PortNumber portNumber) {
+ Set<OchSignal> signals = roadmService.queryLambdas(deviceId, portNumber);
+ if (signals.isEmpty()) {
+ return;
+ }
+ Comparator<OchSignal> compare =
+ (OchSignal a, OchSignal b) -> a.spacingMultiplier() - b.spacingMultiplier();
+ OchSignal minOch = Collections.min(signals, compare);
+ OchSignal maxOch = Collections.max(signals, compare);
+ minFreq = minOch.centralFrequency();
+ maxFreq = maxOch.centralFrequency();
+ channelSpacing = minOch.channelSpacing().frequency();
+ }
+
+ // Returns the power range as a string, N/A if the power range not exists.
+ // The power range would be input power range or target power range determined by port property.
+ // If the port is RX direction then acquire the input power range from driver.
+ // Otherwise there will be a TX direction port, thus acquire the target power range.
+ private String getPowerRange(DeviceId deviceId, PortNumber portNumber) {
+ Range<Long> range = roadmService.inputPortPowerRange(deviceId, portNumber);
+ if (range == null) {
+ range = roadmService.targetPortPowerRange(deviceId, portNumber);
+ }
+ return RoadmUtil.objectToString(range, RoadmUtil.NA);
+ }
+
+ // Returns the current power as a string, Unknown if no value can be found.
+ private String getCurrentPower(DeviceId deviceId, PortNumber portNumber) {
+ Long currentPower = roadmService.getCurrentPortPower(deviceId, portNumber);
+ return RoadmUtil.objectToString(currentPower, RoadmUtil.UNKNOWN);
+ }
+
+ // Returns target power as a string, Unknown if target power is expected but
+ // cannot be found, N/A if port does not have configurable target power
+ private String getTargetPower(DeviceId deviceId, PortNumber portNumber) {
+ if (!roadmService.hasPortTargetPower(deviceId, portNumber)) {
+ return RoadmUtil.NA;
+ }
+ Long targetPower = roadmService.getTargetPortPower(deviceId, portNumber);
+ return RoadmUtil.objectToString(targetPower, RoadmUtil.UNKNOWN);
+ }
+ }
+
+
+ // Handler for setting port target power
+ private final class SetTargetPowerRequestHandler extends RequestHandler {
+
+ private static final String TARGET_POWER_ERR_MSG = "Target power range is %s.";
+
+ private SetTargetPowerRequestHandler() {
+ super(ROADM_SET_TARGET_POWER_REQ);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ DeviceId deviceId = DeviceId.deviceId(string(payload, RoadmUtil.DEV_ID));
+ PortNumber portNumber = PortNumber.portNumber(payload.get(ID).asLong());
+ Range<Long> range = roadmService.targetPortPowerRange(deviceId, portNumber);
+ if (range == null) {
+ log.warn("Unable to determine target power range for device {}", deviceId);
+ return;
+ }
+ Long targetPower = payload.get(TARGET_POWER).asLong();
+ boolean validTargetPower = range.contains(targetPower);
+ if (validTargetPower) {
+ roadmService.setTargetPortPower(deviceId, portNumber, targetPower);
+ }
+ ObjectNode rootNode = objectNode();
+ rootNode.put(ID, payload.get(ID).asText());
+ rootNode.put(RoadmUtil.VALID, validTargetPower);
+ rootNode.put(RoadmUtil.MESSAGE, String.format(TARGET_POWER_ERR_MSG, range.toString()));
+ sendMessage(ROADM_SET_TARGET_POWER_RESP, rootNode);
+ }
+ }
+
+ // Handler for sync-up port target power
+ private final class SyncTargetPowerRequestHandler extends RequestHandler {
+
+ private static final String SYNCED_TARGET_POWER = "Synced target power is %s.";
+ private SyncTargetPowerRequestHandler() {
+ super(ROADM_SYNC_TARGET_POWER_REQ);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ DeviceId deviceId = DeviceId.deviceId(string(payload, RoadmUtil.DEV_ID));
+ PortNumber portNumber = PortNumber.portNumber(payload.get(ID).asLong());
+ Long targetPower = roadmService.syncTargetPortPower(deviceId, portNumber);
+ String power = RoadmUtil.objectToString(targetPower, RoadmUtil.UNKNOWN);
+ ObjectNode rootNode = objectNode();
+ rootNode.put(ID, payload.get(ID).asText())
+ .put(RoadmUtil.VALID, true)
+ .put(RoadmUtil.MESSAGE, String.format(SYNCED_TARGET_POWER, power));
+ sendMessage(ROADM_SYNC_TARGET_POWER_RESP, rootNode);
+ }
+ }
+
+ // Protection switch operation type and path index
+ private static final String OPS_ARRAY_INDEX = "index";
+ private static final String OPS_ARRAY_OPERATION = "operation";
+ private static final String[] OPS_NON_AUTO_OPTS = {OPS_OPT_FORCE, OPS_OPT_MANUAL};
+
+ private final class CreateShowItemsRequestHandler extends RequestHandler {
+ private static final String SHOW_TARGET_POWER = "showTargetPower";
+ private static final String SHOW_SERVICE_STATE = "showServiceState";
+ private static final String SHOW_FLOW_ICON = "showFlowIcon";
+ private static final String OPS_PATHS = "opsOperations";
+ private static final String OPS_ARRAY_NAME = "name";
+ private static final String OPS_GROUP_FMT = "GROUP%d ";
+
+ 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_FLOW_ICON, devType == Type.ROADM);
+ if (devType == Type.FIBER_SWITCH) {
+ node.put(SHOW_TARGET_POWER, false);
+ node.put(SHOW_SERVICE_STATE, true);
+ // add protection switch paths
+ putProtectionSwitchPaths(did, node);
+ } else {
+ node.put(SHOW_TARGET_POWER, true);
+ node.put(SHOW_SERVICE_STATE, false);
+ }
+ sendMessage(ROADM_SHOW_ITEMS_RESP, node);
+ }
+
+ private void putProtectionSwitchPaths(DeviceId deviceId, ObjectNode node) {
+ Map<ConnectPoint, ProtectedTransportEndpointState> states =
+ roadmService.getProtectionSwitchStates(deviceId);
+ ArrayNode nodes = node.putArray(OPS_PATHS);
+ // Add path names for every identifier.
+ int groupIndex = 0;
+ for (ConnectPoint identifier : states.keySet()) {
+ // No group name needed if there is only one connection point identifier.
+ String groupName = states.keySet().size() == 1 ? "" : String.format(OPS_GROUP_FMT, ++groupIndex);
+ // Add AUTOMATIC operation.
+ nodes.add(new ObjectNode(JsonNodeFactory.instance)
+ .put(OPS_ARRAY_INDEX, ACTIVE_UNKNOWN)
+ .put(OPS_ARRAY_OPERATION, OPS_OPT_AUTO)
+ .put(OPS_ARRAY_NAME, String.format("%s%s", groupName, OPS_OPT_AUTO)));
+ // Add FORCE and MANUAL operations for every path.
+ for (String opt : OPS_NON_AUTO_OPTS) {
+ int pathIndex = 0;
+ for (TransportEndpointState state : states.get(identifier).pathStates()) {
+ nodes.add(new ObjectNode(JsonNodeFactory.instance)
+ .put(OPS_ARRAY_INDEX, pathIndex++)
+ .put(OPS_ARRAY_OPERATION, opt)
+ .put(OPS_ARRAY_NAME,
+ String.format("%s%s %s", groupName, opt, state.id().id().toUpperCase())));
+ }
+ }
+ }
+
+
+ }
+ }
+
+ private final class CreateOpsModeSetRequestHandler extends RequestHandler {
+ private static final String DEVICE_INVALID_ERR_MSG = "Apply failed: device is offline or unavailable.";
+ private static final String TYPE_INVALID_ERR_MSG = "Apply failed: invalid device type.";
+
+ private CreateOpsModeSetRequestHandler() {
+ super(ROADM_SET_OPS_MODE_REQ);
+ }
+
+ @Override
+ public void process(ObjectNode payload) {
+ DeviceId did = DeviceId.deviceId(string(payload, RoadmUtil.DEV_ID));
+ ObjectNode node = objectNode();
+ if (!deviceService.isAvailable(did)) {
+ node.put(RoadmUtil.VALID, false);
+ node.put(RoadmUtil.MESSAGE, DEVICE_INVALID_ERR_MSG);
+ sendMessage(ROADM_SET_OPS_MODE_RESP, node);
+ return;
+ }
+ Type devType = deviceService.getDevice(did).type();
+ if (devType != Type.FIBER_SWITCH) {
+ node.put(RoadmUtil.VALID, false);
+ node.put(RoadmUtil.MESSAGE, TYPE_INVALID_ERR_MSG);
+ sendMessage(ROADM_SET_OPS_MODE_RESP, node);
+ return;
+ }
+ // get switch configuration from payload, and then switch the device.
+ roadmService.configProtectionSwitch(did, string(payload, OPS_ARRAY_OPERATION),
+ roadmService.getProtectionSwitchStates(did).keySet().toArray(new ConnectPoint[0])[0],
+ (int) number(payload, OPS_ARRAY_INDEX));
+ node.put(RoadmUtil.VALID, true);
+ sendMessage(ROADM_SET_OPS_MODE_RESP, node);
+ }
+ }
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmService.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmService.java
new file mode 100644
index 0000000..c9e65d8
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmService.java
@@ -0,0 +1,328 @@
+/*
+ * 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.google.common.collect.Range;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.OchSignal;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointState;
+import org.onosproject.net.flow.FlowId;
+
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * ROADM service interface. Provides an interface for ROADM power configuration.
+ *
+ * This application relies on the PowerConfig and LambdaQuery behaviours.
+ *
+ * The device's PowerConfig implementation should be parameterized as
+ * {@code PowerConfig<Object>} in order to support both Direction and OchSignal.
+ * For a reference implementation of PowerConfig, please see
+ * OplinkRoadmPowerConfig
+ *
+ * In this application, a "connection" refers to the selection of a channel
+ * to direct from an input to an output port. Connections are implemented
+ * using FlowRules with an input port selector, optical channel selector,
+ * and output port treatment (see RoadmManager#createConnection()).
+ *
+ * This application currently only supports fixed grid channels.
+ */
+public interface RoadmService {
+
+ /**
+ * Attempts to manually switch working path to the one specified by {@code index}.
+ *
+ * @param deviceId DeviceId of the device to configure
+ * @param index working path index to switch to
+ * @deprecated 1.11.0
+ */
+ @Deprecated
+ void setProtectionSwitchWorkingPath(DeviceId deviceId, int index);
+
+ /**
+ * Retrieves protection switch specified port's service status.
+ *
+ * @param deviceId DeviceId of the device to configure
+ * @param portNumber the port
+ * @return port service status
+ * @deprecated 1.11.0
+ */
+ @Deprecated
+ String getProtectionSwitchPortState(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Attempts to config protection switch by specified {@code operation} and {@code index}.
+ *
+ * @param deviceId DeviceId of the device to configure
+ * @param operation switch configuration, automatic, force or manual
+ * @param identifier {@link ConnectPoint} for the virtual Port representing protected path endpoint
+ * @param index working path index to switch to
+ */
+ void configProtectionSwitch(DeviceId deviceId, String operation, ConnectPoint identifier, int index);
+
+ /**
+ * Retrieves protection switch endpoint states.
+ * @param deviceId DeviceId of the device to configure
+ * @return map groups of underlying paths
+ */
+ Map<ConnectPoint, ProtectedTransportEndpointState> getProtectionSwitchStates(DeviceId deviceId);
+
+ /**
+ * Set target power for a port if the port has configurable target power.
+ *
+ * @param deviceId DeviceId of the device to configure
+ * @param portNumber PortNumber of the port to configure
+ * @param power value to set target power to
+ */
+ void setTargetPortPower(DeviceId deviceId, PortNumber portNumber, long power);
+
+ /**
+ * Returns the target power stored in storage for a port if the port has configurable target power.
+ *
+ * @param deviceId DeviceId of the device to configure
+ * @param portNumber PortNumber of the port to configure
+ * @return the target power if the port has a target power, null otherwise
+ */
+ Long getTargetPortPower(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Sync-up the target power for a port if the operator want to check the configuration.
+ * @param deviceId DeviceId of the device to configure
+ * @param portNumber PortNumber of the port to configure
+ * @return the target power if the port has a target power, null otherwise
+ */
+ Long syncTargetPortPower(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Sets the attenuation of a connection. This does not check that attenuation
+ * is within the acceptable range.
+ *
+ * @param deviceId DeviceId of the device to configure
+ * @param portNumber PortNumber of either the input or output port
+ * @param ochSignal channel to set attenuation for
+ * @param attenuation attenuation value to set to
+ */
+ void setAttenuation(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal, long attenuation);
+
+ /**
+ * Returns the attenuation of a connection.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of either the input or output port
+ * @param ochSignal channel to search for
+ * @return attenuation if found, null otherwise
+ */
+ Long getAttenuation(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal);
+
+ /**
+ * Returns the current port power.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port
+ * @return current power if found, null otherwise
+ */
+ Long getCurrentPortPower(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Returns the current channel power.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of either the input or output port of the connection
+ * @param ochSignal channel to search for
+ * @return channel power if found, null otherwise
+ */
+ Long getCurrentChannelPower(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal);
+
+ /**
+ * Returns the channels supported by a port.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port
+ * @return the set of supported channels
+ */
+ Set<OchSignal> queryLambdas(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Creates a new internal connection on a device without attenuation. This does
+ * not check that the connection is actually valid (e.g. an input port to an
+ * output port).
+ *
+ * Connections are represented as flows with an input port, output port, and
+ * channel.
+ *
+ * @param deviceId DeviceId of the device to create this connection for
+ * @param priority priority of the flow
+ * @param isPermanent permanence of the flow
+ * @param timeout timeout in seconds
+ * @param inPort input port
+ * @param outPort output port
+ * @param ochSignal channel to use
+ * @return FlowId of the FlowRule representing the connection
+ */
+ FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
+ int timeout, PortNumber inPort, PortNumber outPort, OchSignal ochSignal);
+
+ /**
+ * Creates a new internal connection on a device with attenuation. This does
+ * not check that the connection is actually valid (e.g. an input port to an
+ * output port, attenuation if within the acceptable range).
+ *
+ * Connections are represented as flows with an input port, output port, and
+ * channel. Implementation of attenuation is up to the vendor.
+ *
+ * @param deviceId DeviceId of the device to create this connection for
+ * @param priority priority of the flow
+ * @param isPermanent permanence of the flow
+ * @param timeout timeout in seconds
+ * @param inPort input port
+ * @param outPort output port
+ * @param ochSignal channel to use
+ * @param attenuation attenuation of the connection
+ * @return FlowId of the FlowRule representing the connection
+ */
+ FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
+ int timeout, PortNumber inPort, PortNumber outPort,
+ OchSignal ochSignal, long attenuation);
+
+ /**
+ * Removes an internal connection from a device by matching the FlowId and
+ * removing the flow representing the connection. This will remove any flow
+ * from any device so FlowId should correspond with a connection flow.
+ *
+ * @param deviceId DeviceId of the device to remove the connection from
+ * @param flowId FlowId of the flow representing the connection to remove
+ */
+ void removeConnection(DeviceId deviceId, FlowId flowId);
+
+ /**
+ * Returns true if the target power for this port can be configured.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port to check
+ * @return true if the target power for this port can be configured, false
+ * otherwise
+ */
+ boolean hasPortTargetPower(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Returns true if value is within the acceptable target power range of the port.
+ * Returns false if the port does not have a configurable target
+ * power.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param portNumber PortNumber of the port to check
+ * @param power value to check
+ * @return true if value is within the acceptable target power range, false
+ * otherwise
+ */
+ boolean portTargetPowerInRange(DeviceId deviceId, PortNumber portNumber, long power);
+
+ /**
+ * Returns true if value is within the acceptable attenuation range of a
+ * connection, and always returns false if the connection does not support
+ * attenuation. The attenuation range is determined by either the input
+ * or output port of the connection.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param portNumber PortNumber of either the input or output port of the connection
+ * @param att value to check
+ * @return true if value is within the acceptable attenuation range, false
+ * otherwise
+ */
+ boolean attenuationInRange(DeviceId deviceId, PortNumber portNumber, long att);
+
+ /**
+ * Returns true if the port is an input port.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param portNumber PortNumber of the port to check
+ * @return true if the port is an input port, false otherwise
+ */
+ boolean validInputPort(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Returns true if the port is an output port.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param portNumber PortNumber of the port to check
+ * @return true if the port is an output port, false otherwise
+ */
+ boolean validOutputPort(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Returns true if the channel is supported by the port. The port can be either
+ * an input or output port.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param portNumber PortNumber of the port to check
+ * @param ochSignal channel to check
+ * @return true if the channel is supported by the port, false otherwise
+ */
+ boolean validChannel(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal);
+
+ /**
+ * Returns true if the channel is not being used by a connection on the
+ * device.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param ochSignal channel to check
+ * @return true if the channel is not in use, false otherwise
+ */
+ boolean channelAvailable(DeviceId deviceId, OchSignal ochSignal);
+
+ /**
+ * Returns true if the connection from the input port to the output port is
+ * valid. This currently only checks if the given input and output ports are,
+ * respectively, valid input and output ports.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @param inPort input port of the connection
+ * @param outPort output port of the connection
+ * @return true if the connection is valid, false otherwise
+ */
+ boolean validConnection(DeviceId deviceId, PortNumber inPort, PortNumber outPort);
+
+ /**
+ * Returns the acceptable target port power range for a port.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port
+ * @return range if found, null otherwise
+ */
+ Range<Long> targetPortPowerRange(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Returns the acceptable attenuation range for a connection.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of either the input or output port
+ * @param ochSignal channel to check
+ * @return range if found, null otherwise
+ */
+ Range<Long> attenuationRange(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal);
+
+ /**
+ * Returns the expected input power range for an input port.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of an input port
+ * @return range if found, null otherwise
+ */
+ Range<Long> inputPortPowerRange(DeviceId deviceId, PortNumber portNumber);
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmStore.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmStore.java
new file mode 100644
index 0000000..6c7abac
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmStore.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+
+/**
+ * Interface for the ROADM store. Currently used to store target power only.
+ * This should be removed if target power could be read port annotations.
+ */
+public interface RoadmStore {
+
+ /**
+ * Adds the device to the store.
+ *
+ * <p>The device needs to be added to the store
+ * before setTargetPower and getTargetPower can be used. This does not initialize
+ * any of the target powers.
+ *
+ * @param deviceId DeviceId of the device to add
+ */
+ void addDevice(DeviceId deviceId);
+
+ /**
+ * Returns true if the device has been added to the store.
+ *
+ * @param deviceId DeviceId of the device to check
+ * @return true if device has been added to the store, false otherwise
+ */
+ boolean deviceAvailable(DeviceId deviceId);
+
+ /**
+ * Stores the targetPower for a port on a device. The device needs to be added
+ * to the store before this can be called. This does nothing if the device is
+ * not added.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port
+ * @param targetPower target port power to store
+ */
+ void setTargetPower(DeviceId deviceId, PortNumber portNumber, long targetPower);
+
+ /**
+ * Returns the targetPower for a port on a device. The device needs to be added
+ * to the store before this can be called. Returns null if the port's target
+ * power has not yet been initialized using setTargetPower.
+ *
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port
+ * @return target power if target power has already been set, null otherwise
+ */
+ Long getTargetPower(DeviceId deviceId, PortNumber portNumber);
+
+ /**
+ * Remove the targetPower for a port on a device. The power value is removed,
+ * however, the device isn't.
+ * @param deviceId DeviceId of the device
+ * @param portNumber PortNumber of the port
+ */
+ void removeTargetPower(DeviceId deviceId, PortNumber portNumber);
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmUiComponent.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmUiComponent.java
new file mode 100644
index 0000000..b92f240
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmUiComponent.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2019-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.google.common.collect.ImmutableList;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.component.annotations.ReferenceCardinality;
+import org.onosproject.ui.UiExtension;
+import org.onosproject.ui.UiExtensionService;
+import org.onosproject.ui.UiMessageHandlerFactory;
+import org.onosproject.ui.UiView;
+import org.onosproject.ui.UiViewHidden;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Skeletal ONOS UI Custom-View application component.
+ */
+@Component(immediate = true)
+public class RoadmUiComponent {
+
+ private static final String VIEW_ID = "roadm-gui";
+ private static final String VIEW_TEXT = "Roadm UI";
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY)
+ protected UiExtensionService uiExtensionService;
+
+ // List of application views
+ private final List<UiView> uiViews = ImmutableList.of(
+ new UiView(UiView.Category.OTHER, VIEW_ID, VIEW_TEXT),
+ new UiViewHidden("roadm-port-gui"),
+ new UiViewHidden("roadmFlow")
+ );
+
+ // Factory for UI message handlers
+ private final UiMessageHandlerFactory messageHandlerFactory =
+ () -> ImmutableList.of(
+ new RoadmDeviceViewMessageHandler(),
+ new RoadmPortViewMessageHandler(),
+ new RoadmFlowViewMessageHandler()
+ );
+
+ // Roadm UI extension
+ protected UiExtension extension =
+ new UiExtension.Builder(getClass().getClassLoader(), uiViews)
+ .resourcePath(VIEW_ID)
+ .messageHandlerFactory(messageHandlerFactory)
+ .ui2()
+ .build();
+
+ @Activate
+ protected void activate() {
+ uiExtensionService.register(extension);
+ log.info("Started");
+ }
+
+ @Deactivate
+ protected void deactivate() {
+ uiExtensionService.unregister(extension);
+ log.info("Stopped");
+ }
+
+}
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmUtil.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmUtil.java
new file mode 100644
index 0000000..a20b400
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/RoadmUtil.java
@@ -0,0 +1,107 @@
+/*
+ * 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 org.onlab.util.Frequency;
+import org.onosproject.net.Annotations;
+
+import static com.google.common.base.Strings.isNullOrEmpty;
+
+/**
+ * Roadm utilities.
+ */
+public final class RoadmUtil {
+
+ public static final String DEV_ID = "devId";
+ public static final String VALID = "valid";
+ public static final String MESSAGE = "message";
+ public static final String NA = "N/A";
+ public static final String UNKNOWN = "Unknown";
+ public static final String NO_ROWS_MESSAGE = "No items found";
+ // Optical protection switch operations.
+ // There are 3 operations for protection switch now: AUTOMATIC, FORCE, MANUAL.
+ public static final String OPS_OPT_AUTO = "AUTOMATIC";
+ public static final String OPS_OPT_FORCE = "FORCE";
+ public static final String OPS_OPT_MANUAL = "MANUAL";
+
+ private RoadmUtil() {
+ }
+
+ /**
+ * Formats Hz to GHz.
+ *
+ * @param value Hz in string format
+ * @return GHz in string format
+ */
+ public static String asGHz(Frequency value) {
+ return value == null ? UNKNOWN : String.valueOf(value.asGHz());
+ }
+
+ /**
+ * Formats Hz to THz.
+ *
+ * @param value Hz in string format
+ * @return THz in string format
+ */
+ public static String asTHz(Frequency value) {
+ return value == null ? UNKNOWN : String.valueOf(value.asTHz());
+ }
+
+ /**
+ * Gives a default value if the string is null or empty.
+ *
+ * @param value the string value
+ * @param defaultValue default value if null or empty
+ * @return processed string
+ */
+ public static String defaultString(String value, String defaultValue) {
+ return isNullOrEmpty(value) ? defaultValue : value;
+ }
+
+ /**
+ * Gives a default value if the object is null.
+ *
+ * @param object the object
+ * @param defaultValue default value if null
+ * @return processed string
+ */
+ public static String objectToString(Object object, String defaultValue) {
+ return object == null ? defaultValue : String.valueOf(object);
+ }
+
+ /**
+ * Gets value from annotations, if not exists, return default value.
+ *
+ * @param annotations the annotations
+ * @param key key value
+ * @param defaultValue default value
+ * @return value in string format
+ */
+ public static String getAnnotation(Annotations annotations, String key, String defaultValue) {
+ return defaultString(annotations.value(key), defaultValue);
+ }
+
+ /**
+ * Gets value from annotations, default value is NA.
+ *
+ * @param annotations the annotations
+ * @param key key value
+ * @return value in string format
+ */
+ public static String getAnnotation(Annotations annotations, String key) {
+ return getAnnotation(annotations, key, NA);
+ }
+}
\ No newline at end of file
diff --git a/apps/roadm/app/src/main/java/org/onosproject/roadm/package-info.java b/apps/roadm/app/src/main/java/org/onosproject/roadm/package-info.java
new file mode 100644
index 0000000..ce96e55
--- /dev/null
+++ b/apps/roadm/app/src/main/java/org/onosproject/roadm/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Application to monitor and configure ROADM devices.
+ */
+package org.onosproject.roadm;