From da878fcbf0dbf6d4d196d799d0180d7652dd56db Mon Sep 17 00:00:00 2001 From: Jimmy Yan Date: Fri, 2 Sep 2016 16:32:01 -0700 Subject: [PATCH] Add ROADM application Change-Id: I50fa93cf3a69122f6434b46e831b254771159294 --- apps/pom.xml | 1 + apps/roadm/BUCK | 24 + apps/roadm/pom.xml | 150 +++++ .../org/onosproject/roadm/ChannelData.java | 102 ++++ .../roadm/DistributedRoadmStore.java | 101 ++++ .../org/onosproject/roadm/RoadmComponent.java | 84 +++ .../roadm/RoadmDeviceViewMessageHandler.java | 115 ++++ .../roadm/RoadmFlowViewMessageHandler.java | 421 ++++++++++++++ .../org/onosproject/roadm/RoadmManager.java | 530 ++++++++++++++++++ .../roadm/RoadmPortViewMessageHandler.java | 235 ++++++++ .../org/onosproject/roadm/RoadmService.java | 283 ++++++++++ .../org/onosproject/roadm/RoadmStore.java | 67 +++ .../org/onosproject/roadm/package-info.java | 20 + .../app/view/roadmDevice/roadmDevice.css | 39 ++ .../app/view/roadmDevice/roadmDevice.html | 70 +++ .../app/view/roadmDevice/roadmDevice.js | 49 ++ .../app/view/roadmFlow/roadmFlow.css | 132 +++++ .../app/view/roadmFlow/roadmFlow.html | 118 ++++ .../resources/app/view/roadmFlow/roadmFlow.js | 324 +++++++++++ .../app/view/roadmPort/roadmPort.css | 69 +++ .../app/view/roadmPort/roadmPort.html | 73 +++ .../resources/app/view/roadmPort/roadmPort.js | 168 ++++++ apps/roadm/src/main/resources/webgui/css.html | 3 + apps/roadm/src/main/resources/webgui/js.html | 3 + .../org/onosproject/net/ChannelSpacing.java | 4 +- .../optical/power/OplinkRoadmPowerConfig.java | 11 + modules.defs | 1 + 27 files changed, 3195 insertions(+), 2 deletions(-) create mode 100644 apps/roadm/BUCK create mode 100644 apps/roadm/pom.xml create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/ChannelData.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmComponent.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmManager.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmService.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/RoadmStore.java create mode 100644 apps/roadm/src/main/java/org/onosproject/roadm/package-info.java create mode 100644 apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css create mode 100644 apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html create mode 100644 apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js create mode 100644 apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css create mode 100644 apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html create mode 100644 apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js create mode 100644 apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css create mode 100644 apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html create mode 100644 apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js create mode 100644 apps/roadm/src/main/resources/webgui/css.html create mode 100644 apps/roadm/src/main/resources/webgui/js.html diff --git a/apps/pom.xml b/apps/pom.xml index 1f088d7dc8..437e72be15 100644 --- a/apps/pom.xml +++ b/apps/pom.xml @@ -31,6 +31,7 @@ acl + roadm faultmanagement fwd mobility diff --git a/apps/roadm/BUCK b/apps/roadm/BUCK new file mode 100644 index 0000000000..205fd1bc85 --- /dev/null +++ b/apps/roadm/BUCK @@ -0,0 +1,24 @@ +COMPILE_DEPS = [ + '//lib:CORE_DEPS', + '//core/store/serializers:onos-core-serializers', + '//apps/optical-model:onos-apps-optical-model', +] + +TEST_DEPS = [ + '//lib:TEST_REST', + '//core/api:onos-api-tests', +] + +osgi_jar_with_tests ( + deps = COMPILE_DEPS, + test_deps = TEST_DEPS, +) + +onos_app ( + title = 'ROADM App', + category = 'Optical', + url = 'http://onosproject.org', + description = """This application provides an interface and web GUI for monitoring + and configuring power on ROADM devices.""", + required_apps = [ 'org.onosproject.optical-model' ], +) diff --git a/apps/roadm/pom.xml b/apps/roadm/pom.xml new file mode 100644 index 0000000000..492f5c76ae --- /dev/null +++ b/apps/roadm/pom.xml @@ -0,0 +1,150 @@ + + + 4.0.0 + + + org.onosproject + onos-apps + 1.9.0-SNAPSHOT + + + onos-apps-roadm + bundle + + Application for ROADM device management + http://onosproject.org + + + UTF-8 + 1.9.0-SNAPSHOT + org.onosproject.roadm + ROADM Application + Utility + + This application provides an interface and web GUI for monitoring + and configuring power on ROADM devices. + + + + + + org.onosproject + onos-api + ${onos.version} + + + + org.onosproject + onos-core-serializers + ${onos.version} + + + + org.onosproject + onlab-osgi + ${onos.version} + + + + org.onosproject + onos-optical-model + ${onos.version} + + + + junit + junit + test + + + + org.onosproject + onos-api + ${onos.version} + test + tests + + + + org.apache.felix + org.apache.felix.scr.annotations + provided + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.felix + maven-scr-plugin + + + generate-scr-srcdescriptor + + scr + + + + + + bundle + war + + + + + org.onosproject + onos-maven-plugin + + + cfg + generate-resources + + cfg + + + + swagger + generate-sources + + swagger + + + + app + package + + app + + + + + + + + diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/ChannelData.java b/apps/roadm/src/main/java/org/onosproject/roadm/ChannelData.java new file mode 100644 index 0000000000..07c6c41c1c --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/ChannelData.java @@ -0,0 +1,102 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import 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); + checkNotNull(och); + OchSignal ochSignal = ((OchSignalCriterion) och).lambda(); + + PortNumber outPort = null; + List 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/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java b/apps/roadm/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java new file mode 100644 index 0000000000..597ac2ec96 --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/DistributedRoadmStore.java @@ -0,0 +1,101 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +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.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 +public class DistributedRoadmStore implements RoadmStore { + private static Logger log = LoggerFactory.getLogger(DistributedRoadmStore.class); + + private ConsistentMap> distPowerMap; + private Map> powerMap; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected StorageService storageService; + + @Activate + public void activate() { + distPowerMap = storageService.>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 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 portMap = powerMap.get(deviceId); + if (portMap != null) { + return portMap.get(portNumber); + } + return null; + } +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmComponent.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmComponent.java new file mode 100644 index 0000000000..ffb332dd7d --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmComponent.java @@ -0,0 +1,84 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import com.google.common.collect.ImmutableList; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.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; + +/** + * ONOS UI for ROADM application. + */ +@Component(immediate = true) +public class RoadmComponent { + + private static final String DEVICE_VIEW_ID = "roadmDevice"; + private static final String DEVICE_VIEW_TEXT = "ROADM"; + + private static final String RESOURCE_PATH = "webgui"; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected UiExtensionService uiExtensionService; + + // List of application views + private final List deviceViews = ImmutableList.of( + new UiView(UiView.Category.OTHER, DEVICE_VIEW_ID, DEVICE_VIEW_TEXT), + new UiViewHidden("roadmPort"), + new UiViewHidden("roadmFlow") + ); + + // Factory for UI message handlers + private final UiMessageHandlerFactory messageHandlerFactory = + () -> ImmutableList.of( + new RoadmDeviceViewMessageHandler(), + new RoadmPortViewMessageHandler(), + new RoadmFlowViewMessageHandler() + ); + + // Device UI extension + protected UiExtension deviceExtension = + new UiExtension.Builder(getClass().getClassLoader(), deviceViews) + .resourcePath(RESOURCE_PATH) + .messageHandlerFactory(messageHandlerFactory) + .build(); + + @Activate + protected void activate() { + uiExtensionService.register(deviceExtension); + log.info("Started"); + } + + @Deactivate + protected void deactivate() { + uiExtensionService.unregister(deviceExtension); + log.info("Stopped"); + } + +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java new file mode 100644 index 0000000000..d2a6fe284b --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmDeviceViewMessageHandler.java @@ -0,0 +1,115 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableSet; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.Device; +import org.onosproject.net.device.DeviceService; +import org.onosproject.ui.RequestHandler; +import org.onosproject.ui.UiMessageHandler; +import org.onosproject.ui.table.TableModel; +import org.onosproject.ui.table.TableRequestHandler; + +import java.util.Collection; + +import static com.google.common.base.Strings.isNullOrEmpty; + +/** + * Table-View message handler for ROADM device view. + */ +public class RoadmDeviceViewMessageHandler extends UiMessageHandler { + + private static final String ROADM_DEVICE_DATA_REQ = "roadmDeviceDataRequest"; + private static final String ROADM_DEVICE_DATA_RESP = "roadmDeviceDataResponse"; + private static final String ROADM_DEVICES = "roadmDevices"; + + private static final String NO_ROWS_MESSAGE = "No items found"; + + private static final String ID = "id"; + private static final String FRIENDLY_NAME = "name"; + 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, FRIENDLY_NAME, MASTER, PORTS, VENDOR, HW_VERSION, SW_VERSION, + PROTOCOL + }; + + @Override + protected Collection createRequestHandlers() { + return ImmutableSet.of( + new DeviceTableDataRequestHandler() + ); + } + + // Returns friendly name of the device from the annotations + private static String deviceName(Device device) { + String name = device.annotations().value(AnnotationKeys.NAME); + return isNullOrEmpty(name) ? device.id().toString() : name; + } + + // Returns the device protocol from annotations + private static String deviceProtocol(Device device) { + String protocol = device.annotations().value(PROTOCOL); + return protocol != null ? protocol : "N/A"; + } + + // 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 NO_ROWS_MESSAGE; + } + + @Override + protected void populateTable(TableModel tm, ObjectNode payload) { + DeviceService ds = get(DeviceService.class); + MastershipService ms = get(MastershipService.class); + for (Device device : ds.getDevices(Device.Type.ROADM)) { + populateRow(tm.addRow(), device, ds, ms); + } + } + + private void populateRow(TableModel.Row row, Device device, DeviceService ds, + MastershipService ms) { + row.cell(ID, device.id().toString()) + .cell(FRIENDLY_NAME, deviceName(device)) + .cell(MASTER, ms.getMasterFor(device.id())) + .cell(PORTS, ds.getPorts(device.id()).size()) + .cell(VENDOR, device.manufacturer()) + .cell(HW_VERSION, device.hwVersion()) + .cell(SW_VERSION, device.swVersion()) + .cell(PROTOCOL, deviceProtocol(device)); + } + } +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java new file mode 100644 index 0000000000..08e74f0436 --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmFlowViewMessageHandler.java @@ -0,0 +1,421 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Range; +import org.onlab.osgi.ServiceDirectory; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.DeviceId; +import org.onosproject.net.OchSignal; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowId; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.ui.RequestHandler; +import org.onosproject.ui.UiConnection; +import org.onosproject.ui.UiMessageHandler; +import org.onosproject.ui.table.TableModel; +import org.onosproject.ui.table.TableRequestHandler; +import org.onosproject.ui.table.cell.HexLongFormatter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Set; + +import static org.onosproject.ui.JsonUtils.node; +import static org.onosproject.ui.JsonUtils.number; + +/** + * Table-View message handler for ROADM flow view. + */ +public class RoadmFlowViewMessageHandler extends UiMessageHandler { + + private static final String ROADM_FLOW_DATA_REQ = "roadmFlowDataRequest"; + private static final String ROADM_FLOW_DATA_RESP = "roadmFlowDataResponse"; + private static final String ROADM_FLOWS = "roadmFlows"; + + private static final String ROADM_SET_ATTENUATION_REQ = "roadmSetAttenuationRequest"; + private static final String ROADM_SET_ATTENUATION_RESP = "roadmSetAttenuationResponse"; + + private static final String ROADM_DELETE_FLOW_REQ = "roadmDeleteFlowRequest"; + + private static final String ROADM_CREATE_FLOW_REQ = "roadmCreateFlowRequest"; + private static final String ROADM_CREATE_FLOW_RESP = "roadmCreateFlowResponse"; + + private static final String NO_ROWS_MESSAGE = "No items found"; + + private static final String DEV_ID = "devId"; + + private static final String ID = "id"; + private static final String FLOW_ID = "flowId"; + private static final String APP_ID = "appId"; + private static final String GROUP_ID = "groupId"; + private static final String TABLE_ID = "tableId"; + private static final String PRIORITY = "priority"; + private static final String PERMANENT = "permanent"; + private static final String TIMEOUT = "timeout"; + private static final String STATE = "state"; + private static final String IN_PORT = "inPort"; + private static final String OUT_PORT = "outPort"; + private static final String CHANNEL_SPACING = "spacing"; + private static final String CHANNEL_MULTIPLIER = "multiplier"; + private static final String CURRENT_POWER = "currentPower"; + private static final String ATTENUATION = "attenuation"; + private static final String HAS_ATTENUATION = "hasAttenuation"; + + private static final String[] COLUMN_IDS = { + ID, FLOW_ID, APP_ID, GROUP_ID, TABLE_ID, PRIORITY, TIMEOUT, + PERMANENT, STATE, IN_PORT, OUT_PORT, CHANNEL_SPACING, + CHANNEL_MULTIPLIER, CURRENT_POWER, ATTENUATION, HAS_ATTENUATION + }; + + private static final String NA = "N/A"; + private static final String UNKNOWN = "Unknown"; + + private static final long GHZ = 1_000_000_000L; + + private FlowRuleService flowRuleService; + private RoadmService roadmService; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Override + public void init(UiConnection connection, ServiceDirectory directory) { + super.init(connection, directory); + flowRuleService = get(FlowRuleService.class); + roadmService = get(RoadmService.class); + } + + @Override + protected Collection createRequestHandlers() { + return ImmutableSet.of( + new FlowTableDataRequestHandler(), + new SetAttenuationRequestHandler(), + new DeleteConnectionRequestHandler(), + new CreateConnectionRequestHandler() + ); + } + + // Handler for sample table requests + private final class FlowTableDataRequestHandler extends TableRequestHandler { + + private FlowTableDataRequestHandler() { + super(ROADM_FLOW_DATA_REQ, ROADM_FLOW_DATA_RESP, ROADM_FLOWS); + } + + @Override + protected String[] getColumnIds() { + return COLUMN_IDS; + } + + @Override + protected String noRowsMessage(ObjectNode payload) { + return NO_ROWS_MESSAGE; + } + + @Override + protected TableModel createTableModel() { + TableModel tm = super.createTableModel(); + tm.setFormatter(FLOW_ID, HexLongFormatter.INSTANCE); + return tm; + } + + @Override + protected void populateTable(TableModel tm, ObjectNode payload) { + DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)")); + + Iterable flowEntries = flowRuleService.getFlowEntries(deviceId); + for (FlowEntry flowEntry : flowEntries) { + populateRow(tm.addRow(), flowEntry, deviceId); + } + } + + private void populateRow(TableModel.Row row, FlowEntry entry, DeviceId deviceId) { + ChannelData cd = ChannelData.fromFlow(entry); + row.cell(ID, entry.id().value()) + .cell(FLOW_ID, entry.id().value()) + .cell(APP_ID, entry.appId()) + .cell(PRIORITY, entry.priority()) + .cell(TIMEOUT, entry.timeout()) + .cell(PERMANENT, entry.isPermanent()) + .cell(STATE, entry.state().toString()) + .cell(IN_PORT, cd.inPort().toLong()) + .cell(OUT_PORT, cd.outPort().toLong()) + .cell(CHANNEL_SPACING, cd.ochSignal().channelSpacing().frequency().asHz() / GHZ) + .cell(CHANNEL_MULTIPLIER, cd.ochSignal().spacingMultiplier()) + .cell(CURRENT_POWER, getCurrentPower(deviceId, cd)) + .cell(ATTENUATION, getAttenuation(deviceId, cd)); + } + + private String getCurrentPower(DeviceId deviceId, ChannelData channelData) { + Range range = + roadmService.attenuationRange(deviceId, + channelData.outPort(), + channelData.ochSignal()); + if (range != null) { + Long currentPower = + roadmService.getCurrentChannelPower(deviceId, + channelData.outPort(), + channelData.ochSignal()); + if (currentPower != null) { + return String.valueOf(currentPower); + } + } + return NA; + } + + private String getAttenuation(DeviceId deviceId, ChannelData channelData) { + Long attenuation = + roadmService.getAttenuation(deviceId, channelData.outPort(), + channelData.ochSignal()); + if (attenuation != null) { + return String.valueOf(attenuation); + } + return UNKNOWN; + } + } + + // Handler for setting attenuation + private final class SetAttenuationRequestHandler extends RequestHandler { + + // Keys for response message + private static final String VALID = "valid"; + private static final String MESSAGE = "message"; + + // Error messages to display to user + private static final String ATTENUATION_RANGE_MSG = + "Attenuation must be in range %s."; + private static final String NO_ATTENUATION_MSG = + "Cannot set attenuation for this connection"; + + private SetAttenuationRequestHandler() { + super(ROADM_SET_ATTENUATION_REQ); + } + + @Override + public void process(ObjectNode payload) { + DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)")); + FlowId flowId = FlowId.valueOf(number(payload, FLOW_ID)); + long attenuation = payload.get(ATTENUATION).asLong(); + + // Get connection information from the flow + FlowEntry entry = findFlow(deviceId, flowId); + if (entry == null) { + log.error("Unable to find flow rule to set attenuation"); + return; + } + ChannelData cd = ChannelData.fromFlow(entry); + Range range = + roadmService.attenuationRange(deviceId, cd.outPort(), + cd.ochSignal()); + + boolean validAttenuation = (range != null && range.contains(attenuation)); + if (validAttenuation) { + roadmService.setAttenuation(deviceId, cd.outPort(), + cd.ochSignal(), attenuation); + } + + ObjectNode rootNode = objectNode(); + // Send back flowId so view can identify which callback function to use + rootNode.put(FLOW_ID, payload.get(FLOW_ID).asText()); + rootNode.put(VALID, validAttenuation); + if (range != null) { + rootNode.put(MESSAGE, String.format(ATTENUATION_RANGE_MSG, + range.toString())); + } else { + rootNode.put(MESSAGE, NO_ATTENUATION_MSG); + } + sendMessage(ROADM_SET_ATTENUATION_RESP, rootNode); + } + + private FlowEntry findFlow(DeviceId deviceId, FlowId flowId) { + for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) { + if (entry.id().equals(flowId)) { + return entry; + } + } + return null; + } + } + + // Handler for deleting a connection + private final class DeleteConnectionRequestHandler extends RequestHandler { + private DeleteConnectionRequestHandler() { + super(ROADM_DELETE_FLOW_REQ); + } + + @Override + public void process(ObjectNode payload) { + DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)")); + FlowId flowId = FlowId.valueOf(payload.get(ID).asLong()); + roadmService.removeConnection(deviceId, flowId); + } + } + + // Handler for creating a creating a connection from form data + private final class CreateConnectionRequestHandler extends RequestHandler { + + // Keys to load from JSON + private static final String FORM_DATA = "formData"; + private static final String CHANNEL_SPACING_INDEX = "index"; + private static final String INCLUDE_ATTENUATION = "includeAttenuation"; + + // Keys for validation results + private static final String CONNECTION = "connection"; + private static final String CHANNEL_AVAILABLE = "channelAvailable"; + + // Error messages to display to user + private static final String IN_PORT_ERR_MSG = "Invalid input port."; + private static final String OUT_PORT_ERR_MSG = "Invalid output port."; + private static final String CONNECTION_ERR_MSG = + "Invalid connection from input port to output port."; + private static final String CHANNEL_SPACING_ERR_MSG = + "Channel spacing not supported."; + private static final String CHANNEL_ERR_MSG = + "Channel index must be in range %s."; + private static final String CHANNEL_AVAILABLE_ERR_MSG = + "Channel is already being used."; + private static final String ATTENUATION_ERR_MSG = + "Attenuation must be in range %s."; + + // Keys for validation object + private static final String VALID = "valid"; + private static final String MESSAGE = "message"; + + private CreateConnectionRequestHandler() { + super(ROADM_CREATE_FLOW_REQ); + } + + @Override + public void process(ObjectNode payload) { + DeviceId did = DeviceId.deviceId(string(payload, DEV_ID, "(none)")); + ObjectNode flowNode = node(payload, FORM_DATA); + int priority = (int) number(flowNode, PRIORITY); + boolean permanent = bool(flowNode, PERMANENT); + int timeout = (int) number(flowNode, TIMEOUT); + PortNumber inPort = PortNumber.portNumber(number(flowNode, IN_PORT)); + PortNumber outPort = PortNumber.portNumber(number(flowNode, OUT_PORT)); + ObjectNode chNode = node(flowNode, CHANNEL_SPACING); + ChannelSpacing spacing = + channelSpacing((int) number(chNode, CHANNEL_SPACING_INDEX)); + int multiplier = (int) number(flowNode, CHANNEL_MULTIPLIER); + OchSignal och = OchSignal.newDwdmSlot(spacing, multiplier); + boolean includeAttenuation = bool(flowNode, INCLUDE_ATTENUATION); + long att = number(flowNode, ATTENUATION); + + boolean validInPort = roadmService.validInputPort(did, inPort); + boolean validOutPort = roadmService.validOutputPort(did, outPort); + boolean validConnect = roadmService.validConnection(did, inPort, outPort); + boolean validSpacing = true; + boolean validChannel = roadmService.validChannel(did, inPort, och); + boolean channelAvailable = roadmService.channelAvailable(did, och); + boolean validAttenuation = roadmService.attenuationInRange(did, outPort, att); + + if (validConnect && validChannel && channelAvailable) { + if (includeAttenuation && validAttenuation) { + roadmService.createConnection(did, priority, permanent, + timeout, inPort, outPort, + och, att); + } else if (!includeAttenuation) { + roadmService.createConnection(did, priority, permanent, + timeout, inPort, outPort, + och); + } + } + + // Construct error for channel + String channelMessage = "Invalid channel"; + if (!validChannel) { + Set lambdas = roadmService.queryLambdas(did, outPort); + if (lambdas != null) { + Range range = channelRange(lambdas); + if (range.contains(och.spacingMultiplier())) { + // Channel spacing error + validSpacing = false; + } else { + channelMessage = String.format(CHANNEL_ERR_MSG, range.toString()); + } + } + } + + // Construct error for attenuation + String attenuationMessage = "Invalid attenuation"; + if (!validAttenuation) { + Range range = + roadmService.attenuationRange(did, outPort, och); + if (range != null) { + attenuationMessage = + String.format(ATTENUATION_ERR_MSG, range.toString()); + } + } + + // Build response + ObjectNode node = objectNode(); + + node.set(IN_PORT, validationObject(validInPort, IN_PORT_ERR_MSG)); + node.set(OUT_PORT, validationObject(validOutPort, OUT_PORT_ERR_MSG)); + node.set(CONNECTION, validationObject(validConnect, CONNECTION_ERR_MSG)); + node.set(CHANNEL_SPACING, validationObject(validChannel || validSpacing, + CHANNEL_SPACING_ERR_MSG)); + node.set(CHANNEL_MULTIPLIER, validationObject(validChannel || !validSpacing, + channelMessage)); + node.set(CHANNEL_AVAILABLE, validationObject(!validChannel || channelAvailable, + CHANNEL_AVAILABLE_ERR_MSG)); + node.set(ATTENUATION, validationObject(validAttenuation, attenuationMessage)); + node.put(INCLUDE_ATTENUATION, includeAttenuation); + + sendMessage(ROADM_CREATE_FLOW_RESP, node); + } + + // Returns the ChannelSpacing based on the selection made + private ChannelSpacing channelSpacing(int selectionIndex) { + switch (selectionIndex) { + case 0: return ChannelSpacing.CHL_100GHZ; + case 1: return ChannelSpacing.CHL_50GHZ; + case 2: return ChannelSpacing.CHL_25GHZ; + case 3: return ChannelSpacing.CHL_12P5GHZ; + // 6.25GHz cannot be used with ChannelSpacing.newDwdmSlot + // case 4: return ChannelSpacing.CHL_6P25GHZ; + default: return ChannelSpacing.CHL_50GHZ; + } + } + + // Construct validation object to return to the view + private ObjectNode validationObject(boolean result, String message) { + ObjectNode node = objectNode(); + node.put(VALID, result); + if (!result) { + // return error message to display if validation failed + node.put(MESSAGE, message); + } + return node; + } + + // Returns the minimum and maximum channel spacing + private Range channelRange(Set signals) { + Comparator 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()); + } + } +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmManager.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmManager.java new file mode 100644 index 0000000000..011c3931fa --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmManager.java @@ -0,0 +1,530 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import com.google.common.collect.Range; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ChannelSpacing; +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.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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Application for monitoring and configuring ROADM devices. + */ +@Component(immediate = true) +@Service +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_UNARY) + protected RoadmStore roadmStore; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + 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"); + } + + private PowerConfig getPowerConfig(DeviceId deviceId) { + Device device = deviceService.getDevice(deviceId); + if (device != null && device.is(PowerConfig.class)) { + return device.as(PowerConfig.class); + } + log.warn("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); + } + return null; + } + + private void initDevices() { + for (Device device : deviceService.getDevices(Device.Type.ROADM)) { + initDevice(device.id()); + 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig == null) { + log.warn("Unable to set default initial powers for port {} on device {}", + portNumber, deviceId); + return; + } + + Optional> 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig == null) { + log.warn("Unable to set default initial powers for device {}", + deviceId); + return; + } + + List ports = deviceService.getPorts(deviceId); + for (Port port : ports) { + Optional> 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); + } + } + } + + @Override + public void setTargetPortPower(DeviceId deviceId, PortNumber portNumber, long power) { + checkNotNull(deviceId); + checkNotNull(portNumber); + PowerConfig 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); + // getTargetPortPower is not yet implemented in PowerConfig so we + // access store instead + return roadmStore.getTargetPower(deviceId, portNumber); + } + + @Override + public void setAttenuation(DeviceId deviceId, PortNumber portNumber, + OchSignal ochSignal, long attenuation) { + checkNotNull(deviceId); + checkNotNull(portNumber); + checkNotNull(ochSignal); + PowerConfig 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional currentPower = + powerConfig.currentPower(portNumber, ochSignal); + if (currentPower.isPresent()) { + return currentPower.get(); + } + } + return null; + } + + @Override + public Set 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); + + FlowRule.Builder flowBuilder = new DefaultFlowRule.Builder(); + flowBuilder.fromApp(appId); + flowBuilder.withPriority(priority); + if (isPermanent) { + flowBuilder.makePermanent(); + } else { + flowBuilder.makeTemporary(timeout); + } + flowBuilder.forDevice(deviceId); + + TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); + selectorBuilder.add(Criteria.matchInPort(inPort)); + selectorBuilder.add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID)); + selectorBuilder.add(Criteria.matchLambda(ochSignal)); + flowBuilder.withSelector(selectorBuilder.build()); + + TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); + treatmentBuilder.add(Instructions.createOutput(outPort)); + flowBuilder.withTreatment(treatmentBuilder.build()); + + 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; + } + + // Delay the call to setTargetPower because the flow may not be in the store yet + private void delayedSetAttenuation(DeviceId deviceId, PortNumber outPort, + OchSignal ochSignal, long attenuation) { + Runnable setAtt = () -> { + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + log.warn("Thread interrupted. Setting attenuation early."); + } + setAttenuation(deviceId, outPort, ochSignal, attenuation); + }; + new Thread(setAtt).start(); + } + + @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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional> 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional> 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + OchSignal stubOch = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 0); + Optional> 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 powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional> 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); + LambdaQuery lambdaQuery = getLambdaQuery(deviceId); + if (lambdaQuery != null) { + Set 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 targetPortPowerRange(DeviceId deviceId, PortNumber portNumber) { + checkNotNull(deviceId); + checkNotNull(portNumber); + PowerConfig powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional> range = + powerConfig.getTargetPowerRange(portNumber, Direction.ALL); + if (range.isPresent()) { + return range.get(); + } + } + return null; + } + + @Override + public Range attenuationRange(DeviceId deviceId, PortNumber portNumber, + OchSignal ochSignal) { + checkNotNull(deviceId); + checkNotNull(portNumber); + checkNotNull(ochSignal); + PowerConfig powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional> range = + powerConfig.getTargetPowerRange(portNumber, ochSignal); + if (range.isPresent()) { + return range.get(); + } + } + return null; + } + + @Override + public Range inputPortPowerRange(DeviceId deviceId, PortNumber portNumber) { + checkNotNull(deviceId); + checkNotNull(portNumber); + PowerConfig powerConfig = getPowerConfig(deviceId); + if (powerConfig != null) { + Optional> range = + powerConfig.getInputPowerRange(portNumber, Direction.ALL); + if (range.isPresent()) { + return range.get(); + } + } + return null; + } + + // 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: + setInitialTargetPortPower(device.id(), deviceEvent.port().number()); + break; + default: + break; + + } + } + } +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java new file mode 100644 index 0000000000..81fb581d9d --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmPortViewMessageHandler.java @@ -0,0 +1,235 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Range; +import org.onlab.osgi.ServiceDirectory; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.DeviceId; +import org.onosproject.net.optical.OpticalAnnotations; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.List; + +/** + * 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 NO_ROWS_MESSAGE = "No items found"; + + private static final String DEV_ID = "devId"; + + private static final String ID = "id"; + private static final String TYPE = "type"; + private static final String NAME = "name"; + 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 INPUT_POWER_RANGE = "inputPowerRange"; + 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[] COLUMN_IDS = { + ID, TYPE, NAME, ENABLED, MIN_FREQ, MAX_FREQ, GRID, INPUT_POWER_RANGE, + CURRENT_POWER, TARGET_POWER, HAS_TARGET_POWER, + }; + + private static final String NA = "N/A"; + private static final String UNKNOWN = "Unknown"; + + private static final long GHZ = 1_000_000_000L; + private static final long THZ = 1_000_000_000_000L; + + private DeviceService deviceService; + private RoadmService roadmService; + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Override + public void init(UiConnection connection, ServiceDirectory directory) { + super.init(connection, directory); + deviceService = get(DeviceService.class); + roadmService = get(RoadmService.class); + } + + @Override + protected Collection createRequestHandlers() { + return ImmutableSet.of( + new PortTableDataRequestHandler(), + new SetTargetPowerRequestHandler() + ); + } + + private String asGHz(String value) { + return String.valueOf(Double.valueOf(value) / GHZ); + } + + private String asTHz(String value) { + return String.valueOf(Double.valueOf(value) / THZ); + } + + private String annotation(Port port, String key, String defaultValue) { + String value = port.annotations().value(key); + return value != null ? value : defaultValue; + } + + private String annotation(Port port, String key) { + return annotation(port, key, NA); + } + + // 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 NO_ROWS_MESSAGE; + } + + @Override + protected void populateTable(TableModel tm, ObjectNode payload) { + DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)")); + + if (deviceService.isAvailable(deviceId)) { + List ports = deviceService.getPorts(deviceId); + for (Port port : ports) { + populateRow(tm.addRow(), port, deviceId); + } + } + } + + private void populateRow(TableModel.Row row, Port port, DeviceId deviceId) { + row.cell(ID, port.number().toLong()) + .cell(TYPE, port.type()) + .cell(ENABLED, port.isEnabled()) + .cell(NAME, annotation(port, AnnotationKeys.PORT_NAME)) + .cell(MIN_FREQ, asTHz(annotation(port, OpticalAnnotations.MIN_FREQ_HZ))) + .cell(MAX_FREQ, asTHz(annotation(port, OpticalAnnotations.MAX_FREQ_HZ))) + .cell(GRID, asGHz(annotation(port, OpticalAnnotations.GRID_HZ))) + .cell(INPUT_POWER_RANGE, getInputPowerRange(deviceId, port.number())) + .cell(CURRENT_POWER, getCurrentPower(deviceId, port.number())) + .cell(TARGET_POWER, getTargetPower(deviceId, port.number())) + .cell(HAS_TARGET_POWER, roadmService.hasPortTargetPower(deviceId, port.number())); + } + + // Returns the input power range as a string, N/A if the port is not an + // input port + private String getInputPowerRange(DeviceId deviceId, PortNumber portNumber) { + Range range = + roadmService.inputPortPowerRange(deviceId, portNumber); + if (range != null) { + return range.toString(); + } + return 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); + if (currentPower != null) { + return String.valueOf(currentPower); + } + return 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)) { + Long targetPower = + roadmService.getTargetPortPower(deviceId, portNumber); + if (targetPower != null) { + return String.valueOf(targetPower); + } else { + return UNKNOWN; + } + } + return NA; + } + } + + + // Handler for setting port target power + private final class SetTargetPowerRequestHandler extends RequestHandler { + + private static final String VALID = "valid"; + private static final String MESSAGE = "message"; + + 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, DEV_ID, "(none)")); + PortNumber portNumber = PortNumber.portNumber(payload.get(ID).asLong()); + long targetPower = payload.get(TARGET_POWER).asLong(); + boolean validTargetPower; + + Range range = + roadmService.targetPortPowerRange(deviceId, portNumber); + if (range != null) { + validTargetPower = range.contains(targetPower); + + if (validTargetPower) { + roadmService.setTargetPortPower(deviceId, portNumber, targetPower); + } + + ObjectNode rootNode = objectNode(); + rootNode.put(ID, payload.get(ID).asText()); + rootNode.put(VALID, validTargetPower); + rootNode.put(MESSAGE, String.format(TARGET_POWER_ERR_MSG, range.toString())); + sendMessage(ROADM_SET_TARGET_POWER_RESP, rootNode); + + } else { + log.warn("Unable to determine target power range for device {}", deviceId); + } + } + } +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmService.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmService.java new file mode 100644 index 0000000000..19334ae48a --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmService.java @@ -0,0 +1,283 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import com.google.common.collect.Range; +import org.onosproject.net.DeviceId; +import org.onosproject.net.OchSignal; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.FlowId; + +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} 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 { + + /** + * 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 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); + + /** + * 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 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. 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 + * @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 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 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 inputPortPowerRange(DeviceId deviceId, PortNumber portNumber); +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/RoadmStore.java b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmStore.java new file mode 100644 index 0000000000..202ba8d4f6 --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/RoadmStore.java @@ -0,0 +1,67 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.roadm; + +import 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. + * + *

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); +} diff --git a/apps/roadm/src/main/java/org/onosproject/roadm/package-info.java b/apps/roadm/src/main/java/org/onosproject/roadm/package-info.java new file mode 100644 index 0000000000..156b44a5f0 --- /dev/null +++ b/apps/roadm/src/main/java/org/onosproject/roadm/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2016-present Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Application to monitor and configure ROADM devices. + */ +package org.onosproject.roadm; diff --git a/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css new file mode 100644 index 0000000000..80f975f616 --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.css @@ -0,0 +1,39 @@ +/* css for ROADM device table view */ + +.less-gap { + margin-top: -20px; +} + +#ov-roadm-device h2 { + display: inline-block; +} + +/* Panel Styling */ +#ov-roadm-device-item-details-panel.floatpanel { + position: absolute; + top: 115px; +} + +.light #ov-roadm-device-item-details-panel.floatpanel { + background-color: rgb(229, 234, 237); +} +.dark #ov-roadm-device-item-details-panel.floatpanel { + background-color: #3A4042; +} + +#ov-roadm-device-item-details-panel h3 { + margin: 0; + font-size: large; +} + +#ov-roadm-device-item-details-panel h4 { + margin: 0; +} + +#ov-roadm-device-item-details-panel td { + padding: 5px; +} +#ov-roadm-device-item-details-panel td.label { + font-style: italic; + opacity: 0.8; +} diff --git a/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html new file mode 100644 index 0000000000..a9a70e8348 --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.html @@ -0,0 +1,70 @@ + +

+ +
+

Optical Devices ({{tableData.length}} total)

+
+
+
+ +
+ +
+ + +
+
+
+ +
+ +
+ + + + + + + + + + + +
Friendly NameDevice ID Master Ports Vendor H/W Version S/W Version Protocol
+
+ +
+ + + + + + + + + + + + + + + +
+ {{annots.no_rows_msg}} +
{{item.name}}{{item.id}}{{item.master}}{{item.ports}}{{item.vendor}}{{item.hwVersion}}{{item.swVersion}}{{item.protocol}}
+
+ +
+
diff --git a/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js new file mode 100644 index 0000000000..2537ed8cc7 --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmDevice/roadmDevice.js @@ -0,0 +1,49 @@ +// js for roadm device table view +(function () { + 'use strict'; + + // injected refs + var $log, $scope, $loc, wss, ns; + + // constants + var detailsReq = 'roadmDeviceDetailsRequest'; + + angular.module('ovRoadmDevice', []) + .controller('OvRoadmDeviceCtrl', + ['$log', '$scope', '$location', 'TableBuilderService', 'WebSocketService', + 'NavService', + + function (_$log_, _$scope_, _$loc_, tbs, _wss_, _ns_) { + $log = _$log_; + $scope = _$scope_; + $loc = _$loc_; + wss = _wss_; + ns = _ns_; + + // query for if a certain device needs to be highlighted + var params = $loc.search(); + if (params.hasOwnProperty('devId')) { + $scope.selId = params['devId']; + } + + // TableBuilderService creating a table for us + tbs.buildTable({ + scope: $scope, + tag: 'roadmDevice' + }); + + $scope.nav = function (path) { + if ($scope.selId) { + ns.navTo(path, { devId: $scope.selId }); + } + }; + + // cleanup + $scope.$on('$destroy', function () { + //wss.unbindHandlers(handlers); + $log.log('OvRoadmDeviceCtrl has been destroyed'); + }); + + $log.log('OvRoadmDeviceCtrl has been created'); + }]); +}()); diff --git a/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css new file mode 100644 index 0000000000..81e5c16aca --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css @@ -0,0 +1,132 @@ +/* css for ROADM flow table view */ + +#ov-roadm-flow h2 { + display: inline-block; +} + +/* Panel Styling */ +#ov-roadm-flow-item-details-panel.floatpanel { + position: absolute; + top: 115px; +} + +.light #ov-roadm-flow-item-details-panel.floatpanel { + background-color: rgb(229, 234, 237); +} + +.dark #ov-roadm-flow-item-details-panel.floatpanel { + background-color: #3A4042; +} + +#ov-roadm-flow-item-details-panel h3 { + margin: 0; + font-size: large; +} + +#ov-roadm-flow-item-details-panel h4 { + margin: 0; +} + +#ov-roadm-flow-item-details-panel td { + padding: 5px; +} +#ov-roadm-flow-item-details-panel td.label { + font-style: italic; + opacity: 0.8; +} + +#ov-roadm-flow .table-header span.units { + font-variant: normal; + text-transform: none; +} + +/* editable attenuation */ +#ov-roadm-flow .editable span { + width: 100%; + display: inline-block; +} + +#ov-roadm-flow .editable span.attenuation:hover { + color: #009fdb +} + +#ov-roadm-flow .editable input { + padding: 0; +} +#ov-roadm-flow .editable button { + margin: 0; + padding: 0px 5px 0px 5px; +} + +#ov-roadm-flow .editable input { + width: 80px; +} + +#ov-roadm-flow .editable .input-error { + color: red; + font-size: 10px; + width: 180px; +} + +/* delete flow button */ +#ov-roadm-flow .table-body .delete-icon { + font-size: 24px; + line-height: 0px; + text-align: center; +} + +#ov-roadm-flow .table-body .delete-icon:hover { + color: red; +} + +/* Create connection form */ +#ov-roadm-flow div.flow-form { + background-color: #ffffff; + border: 1px solid #888888; + width: 720px; + height: 270px; + padding: 20px; + position: absolute; + right: 15px; + bottom: 15px; +} + +#ov-roadm-flow .flow-form div.delete-icon { + cursor: pointer; + cursor: hand; + font-size: 36px; + position: absolute; + right: 20px; + top: 5px; +} + +#ov-roadm-flow .flow-form label { + width: 150px; + display: inline-block; +} + +#ov-roadm-flow .flow-form input { + width: 150px; + display: inline-block +} + +#ov-roadm-flow .flow-form select { + width: 150px; +} + +#ov-roadm-flow .flow-form .form-error { + margin-left: 15px; + color: red; +} + +#ov-roadm-flow .flow-form form { + font-size: 14px; + color: #444444; + line-height: 26px; + margin-bottom: 15px; +} + +#ov-roadm-flow .flow-form button.submit { + margin-left: 150px; + width: 150px; +} diff --git a/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html new file mode 100644 index 0000000000..323eec5c9c --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html @@ -0,0 +1,118 @@ + +
+ +
+

Connections for Optical Device {{devId}} ({{tableData.length}} total)

+
+
+ +
+
+ +
+ +
+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + + + + +
Flow ID App ID Priority Timeout Permanent State In Port Out Port Channel Spacing (GHz) Current Power (0.01dBm)Attenuation (0.01dB)
+
+ +
+ + + + + + + + + + + + + + + + + + + + +
+ {{annots.no_rows_msg}} +
×{{flow.flowId}}{{flow.appId}}{{flow.priority}}{{flow.timeout}}{{flow.permanent}}{{flow.state}}{{flow.inPort}}{{flow.outPort}}{{flow.multiplier}} ({{flow.multiplier * 0.05 + 193.1 | number:2}} THz){{flow.spacing}}{{flow.currentPower}}
+
+ +
+ +
+
×
+
+ + {{form.priorityMessage}}
+ +
+ + + {{form.timeoutMessage}}
+ + + {{form.inPortMessage}}
+ + + {{form.outPortMessage}} + {{form.connectionMessage}}
+ + + {{form.spacingMessage}}
+ + + {{form.multiplierMessage}} + {{form.channelMessage}}
+ +
+ + + {{form.attenuationMessage}}
+
+ +
+ +
diff --git a/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js new file mode 100644 index 0000000000..ad9bf76efd --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js @@ -0,0 +1,324 @@ +// js for roadm flow table view +(function () { + 'use strict'; + + var SET_ATT_REQ = "roadmSetAttenuationRequest"; + var SET_ATT_RESP = "roadmSetAttenuationResponse"; + var DELETE_FLOW_REQ = "roadmDeleteFlowRequest"; + var CREATE_FLOW_REQ = "roadmCreateFlowRequest"; + var CREATE_FLOW_RESP = "roadmCreateFlowResponse"; + + // injected references + var $log, $scope, $location, fs, tbs, wss, ns; + + // used to map id to a request call function + var flowCbTable = {}; + + function setAttenuation(flow, targetVal, cb) { + flowCbTable[flow.id] = cb; + wss.sendEvent(SET_ATT_REQ, + { + devId: $scope.devId, + flowId: flow.id, + attenuation: targetVal + }); + } + + function attenuationCb(data) { + flowCbTable[data.flowId](data.valid, data.message); + } + + // check if value is an integer + function isInteger(val) { + var INTEGER_REGEXP = /^\-?\d+$/; + if (INTEGER_REGEXP.test(val)) { + return true; + } + return false; + } + + angular.module('ovRoadmFlow', []) + .controller('OvRoadmFlowCtrl', + ['$log', '$scope', '$location', + 'FnService', 'TableBuilderService', 'WebSocketService', 'NavService', + + function (_$log_, _$scope_, _$location_, _fs_, _tbs_, _wss_, _ns_) { + var params; + $log = _$log_; + $scope = _$scope_; + $location = _$location_; + fs = _fs_; + tbs = _tbs_; + wss = _wss_; + ns = _ns_; + + $scope.addFlowTip = 'Create a flow'; + $scope.deviceTip = 'Show device table'; + $scope.flowTip = 'Show flow view for this device'; + $scope.groupTip = 'Show group view for this device'; + $scope.meterTip = 'Show meter view for selected device'; + + $scope.showFlowForm = false; + + var handlers = {}; + handlers[SET_ATT_RESP] = attenuationCb; + wss.bindHandlers(handlers); + + params = $location.search(); + if (params.hasOwnProperty('devId')) { + $scope.devId = params['devId']; + } + + tbs.buildTable({ + scope: $scope, + tag: 'roadmFlow', + query: params + }); + + $scope.displayFlowForm = function () { + $scope.showFlowForm = true; + } + + $scope.hideFlowForm = function () { + $scope.showFlowForm = false; + } + + $scope.setAttenuation = setAttenuation; + + $scope.deleteFlow = function ($event, row) { + wss.sendEvent(DELETE_FLOW_REQ, + { + devId: $scope.devId, + id: row.id + }); + } + + $scope.createFlow = function(flow) { + wss.sendEvent(CREATE_FLOW_REQ, + { + devId: $scope.devId, + flow: flow + }); + } + + $scope.fakeCurrentPower = function(flow) { + if (!isNaN(flow.currentPower)) { + var val = parseInt(flow.attenuation); + return val + (val % 5 - 2); + } else { + return flow.currentPower; + } + } + + $scope.nav = function (path) { + if ($scope.devId) { + ns.navTo(path, { devId: $scope.devId }); + } + }; + + $scope.$on('$destroy', function () { + wss.unbindHandlers(handlers); + }); + + $log.log('OvRoadmFlowCtrl has been created'); + }]) + + .directive('roadmAtt', ['WebSocketService', function() { + + var retTemplate = + '{{currItem.attenuation}}' + + '
' + + '' + + '' + + '' + + '{{errorMessage}}' + + '
'; + + return { + restrict: 'A', + scope: { + currItem: '=roadmAtt', + roadmSetAtt: '&' + }, + template: retTemplate, + link: function ($scope, $element) { + $scope.editMode = false; + $scope.showError = false; + $scope.errorMessage = "Invalid attenuation" + }, + controller: function($scope, $timeout) { + $scope.enableEdit = function() { + // connection must support attenuation to be editable + if ($scope.editMode === false) { + // Ensure that the entry being edited remains the same even + // if the table entries are shifted around. + $scope.targetItem = $scope.currItem; + // Ensure the value seen in the field remains the same + $scope.formVal = parseInt($scope.currItem.attenuation); + $scope.editMode = true; + $timeout(function () { + $scope.$apply() + }); + } + }; + $scope.send = function() { + // check input is an integer + if (!isInteger($scope.formVal)) { + $scope.sendCb(false, "Attenuation must be an integer"); + return; + } + $scope.roadmSetAtt({flow: $scope.targetItem, targetVal: $scope.formVal, cb: $scope.sendCb}); + }; + // Callback for server-side validation. Displays the error message + // if the input is invalid. + $scope.sendCb = function(valid, message) { + if (valid) { + // check if it's still pointing to the same item + // reordering the entries may change the binding + if ($scope.currItem.id === $scope.targetItem.id) { + // update the ui to display the new attenuation value + $scope.currItem.attenuation = $scope.formVal; + } + $scope.cancel(); + } else { + $scope.errorMessage = message; + $scope.showError = true; + } + $timeout(function () { + $scope.$apply() + }); + } + $scope.cancel = function() { + $scope.editMode = false; + $scope.showError = false; + } + } + }; + }]) + + .controller('FlowFormController', function($timeout) { + var notIntegerError = "Must be an integer."; + + this.clearErrors = function() { + this.priorityError = false; + this.timeoutError = false; + this.isPermanentError = false; + this.inPortError = false; + this.outPortError = false; + this.spacingError = false; + this.multiplierError = false; + this.attenuationError = false; + this.connectionError = false; + this.channelError = false; + } + this.clearErrors(); + + this.spacings = [ + {index: 0, freq: "100 GHz"}, + {index: 1, freq: "50 GHz"}, + {index: 2, freq: "25 GHz"}, + {index: 3, freq: "12.5 GHz"} + ]; + + this.flow = {}; + //this.flow.priority = 88; + this.flow.permanent = true; + this.flow.timeout = 0; + //this.flow.inPort = 2; + //this.flow.outPort = 2; + this.flow.spacing = this.spacings[1]; + //this.flow.multiplier = 0; + this.flow.includeAttenuation = true; + this.flow.attenuation = 0; + + var parent = this; + + function createFlowCb(data) { + if (!data.inPort.valid) { + parent.inPortMessage = data.inPort.message; + parent.inPortError = true; + } + if (!data.outPort.valid) { + parent.outPortMessage = data.outPort.message; + parent.outPortError = true; + } + if (!data.connection.valid) { + parent.connectionMessage = data.connection.message; + parent.connectionError = true; + } + if (!data.spacing.valid) { + parent.spacingMessage = data.spacing.message; + parent.spacingError = true; + } + if (!data.multiplier.valid) { + parent.multiplierMessage = data.multiplier.message; + parent.multiplierError = true; + } + if (!data.channelAvailable.valid) { + parent.channelMessage = data.channelAvailable.message; + parent.channelError = true; + } + if (data.includeAttenuation && !data.attenuation.valid) { + parent.attenuationMessage = data.attenuation.message; + parent.attenuationError = true; + } + $timeout(function () { + $scope.$apply() + }); + } + + var handlers = {} + handlers[CREATE_FLOW_RESP] = createFlowCb; + wss.bindHandlers(handlers); + + this.createFlow = function(connection) { + this.clearErrors(); + + var error = false; + if (!isInteger(connection.priority)) { + this.priorityMessage = notIntegerError; + this.priorityError = true; + error = true; + } + if (!connection.permanent && !isInteger(connection.timeout)) { + this.timeoutMessage = notIntegerError; + this.timeoutError = true; + error = true; + } + if (!isInteger(connection.inPort)) { + this.inPortMessage = notIntegerError; + this.inPortError = true; + error = true; + } + if (!isInteger(connection.outPort)) { + this.outPortMessage = notIntegerError; + this.outPortError = true; + error = true; + } + if (!isInteger(connection.multiplier)) { + this.multiplierMessage = notIntegerError; + this.multiplierError = true; + error = true; + } + if (connection.includeAttenuation && !isInteger(connection.attenuation)) { + this.attenuationMessage = notIntegerError; + this.attenuationError = true; + error = true; + } + + if (!error) { + wss.sendEvent(CREATE_FLOW_REQ, + { + devId: $scope.devId, + formData: connection + }); + $log.log('Request to create connection has been sent'); + } + } + + $scope.$on('$destroy', function () { + wss.unbindHandlers(handlers); + }); + }); + +}()); diff --git a/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css new file mode 100644 index 0000000000..511b2fc09e --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.css @@ -0,0 +1,69 @@ +/* css for ROADM port table view */ + +#ov-roadm-port h2 { + display: inline-block; +} + +/* Panel Styling */ +#ov-roadm-port-item-details-panel.floatpanel { + position: absolute; + top: 115px; +} + +.light #ov-roadm-port-item-details-panel.floatpanel { + background-color: rgb(229, 234, 237); +} +.dark #ov-roadm-port-item-details-panel.floatpanel { + background-color: #3A4042; +} + +#ov-roadm-port-item-details-panel h3 { + margin: 0; + font-size: large; +} + +#ov-roadm-port-item-details-panel h4 { + margin: 0; +} + +#ov-roadm-port-item-details-panel td { + padding: 5px; +} + +#ov-roadm-port-item-details-panel td.label { + font-style: italic; + opacity: 0.8; +} + +#ov-roadm-port .table-header span.units { + font-variant: normal; + text-transform: none; +} + +/* Editable Target Power field */ +#ov-roadm-port .editable span { + width: 100%; + display: inline-block; +} + +#ov-roadm-port .editable span.target-power:hover { + color: #009fdb +} + +#ov-roadm-port .editable input { + padding: 0; +} +#ov-roadm-port .editable button { + margin: 0; + padding: 0px 5px 0px 5px; +} + +#ov-roadm-port .editable input { + width: 80px; +} + +#ov-roadm-port .editable .input-error { + color: red; + font-size: 10px; + width: 180px; +} diff --git a/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html new file mode 100644 index 0000000000..1e2affdbce --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.html @@ -0,0 +1,73 @@ + +
+ +
+

Ports for Optical Device {{devId}} ({{tableData.length}} total)

+
+
+
+ +
+ +
+ +
+
+
+ +
+ +
+ + + + + + + + + + + + + +
Port Number Name Type Enabled Min Freq (THz) Max Freq (THz) Grid (GHz) Input Power Range Current Power (0.01dBm) Target Power (0.01dBm)
+
+ +
+ + + + + + + + + + + + + + + + + +
+ {{annots.no_rows_msg}} +
{{item.id}}{{item.name}}{{item.type}}{{item.enabled}}{{item.minFreq}}{{item.maxFreq}}{{item.grid}}{{item.inputPowerRange}}{{item.currentPower}}
+
+ +
+
diff --git a/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js new file mode 100644 index 0000000000..447ac7d590 --- /dev/null +++ b/apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js @@ -0,0 +1,168 @@ +// js for roadm port table view +(function () { + 'use strict'; + + var SET_TARGET_POWER_REQ = "roadmSetTargetPowerRequest"; + var SET_TARGET_POWER_RESP = "roadmSetTargetPowerResponse"; + + // injected references + var $log, $scope, $location, fs, tbs, wss, ns; + + var portCbTable = {}; + + function setPortPower(port, targetVal, cb) { + var id = port.id; + portCbTable[id] = cb; + wss.sendEvent("roadmSetTargetPowerRequest", + { + devId: $scope.devId, + id: port.id, + targetPower: targetVal + }); + } + + function portPowerCb(data) { + portCbTable[data.id](data.valid, data.message); + } + + // check if value is an integer + function isInteger(val) { + var INTEGER_REGEXP = /^\-?\d+$/; + if (INTEGER_REGEXP.test(val)) { + return true; + } + return false; + } + + angular.module('ovRoadmPort', []) + .controller('OvRoadmPortCtrl', + ['$log', '$scope', '$location', + 'FnService', 'TableBuilderService', 'WebSocketService', 'NavService', + + function (_$log_, _$scope_, _$location_, _fs_, _tbs_, _wss_, _ns_) { + var params; + $log = _$log_; + $scope = _$scope_; + $location = _$location_; + fs = _fs_; + tbs = _tbs_; + wss = _wss_; + ns = _ns_; + + $scope.deviceTip = 'Show device table'; + $scope.flowTip = 'Show flow view for this device'; + $scope.groupTip = 'Show group view for this device'; + $scope.meterTip = 'Show meter view for selected device'; + + var handlers = {}; + handlers[SET_TARGET_POWER_RESP] = portPowerCb; + wss.bindHandlers(handlers); + + params = $location.search(); + if (params.hasOwnProperty('devId')) { + $scope.devId = params['devId']; + } + + tbs.buildTable({ + scope: $scope, + tag: 'roadmPort', + query: params + }); + + $scope.setPortPower = setPortPower; + + $scope.setTargetPower = function (port, targetVal) { + wss.sendEvent("roadmSetTargetPowerRequest", + { + devId: $scope.devId, + id: port.id, + targetPower: targetVal + }); + $log.debug('Got a click on:', port); + } + + $scope.nav = function (path) { + if ($scope.devId) { + ns.navTo(path, { devId: $scope.devId }); + } + }; + + $scope.$on('$destroy', function () { + wss.unbindHandlers(handlers); + }); + + $log.log('OvRoadmPortCtrl has been created'); + }]) + + .directive('roadmPower', ['WebSocketService', function() { + + var retTemplate = + '{{currItem.targetPower}}' + + '
' + + '' + + '' + + '' + + '{{errorMessage}}' + + '
'; + + return { + restrict: 'A', + scope: { + currItem: '=roadmPower', + roadmSetPower: '&' + }, + template: retTemplate, + link: function ($scope, $element) { + $scope.editMode = false; + $scope.showError = false; + $scope.errorMessage = "Invalid target power"; + }, + controller: function($scope, $timeout) { + $scope.enableEdit = function() { + if ($scope.currItem.hasTargetPower === "true" && $scope.editMode === false) { + // Ensure that the entry being edited remains the same even + // if the table entries are shifted around. + $scope.targetItem = $scope.currItem; + // Ensure the value seen in the field remains the same + $scope.formVal = parseInt($scope.currItem.targetPower); + $scope.editMode = true; + $timeout(function () { + $scope.$apply() + }); + } + }; + // Callback for server-side validation. Displays the error message + // if the input is invalid. + $scope.sendCb = function(valid, message) { + if (valid) { + // check if it's still pointing to the same item + // reordering the entries may change the binding + if ($scope.currItem.id === $scope.targetItem.id) { + // update the ui to display the new attenuation value + $scope.currItem.targetPower = $scope.formVal; + } + $scope.cancel(); + } else { + $scope.errorMessage = message; + $scope.showError = true; + } + $timeout(function () { + $scope.$apply() + }); + } + $scope.send = function() { + // check input is an integer + if (!isInteger($scope.formVal)) { + $scope.sendCb(false, "Target power must be an integer"); + return; + } + $scope.roadmSetPower({port: $scope.targetItem, targetVal: $scope.formVal, cb: $scope.sendCb}); + }; + $scope.cancel = function() { + $scope.editMode = false; + $scope.showError = false; + } + } + }; + }]); +}()); diff --git a/apps/roadm/src/main/resources/webgui/css.html b/apps/roadm/src/main/resources/webgui/css.html new file mode 100644 index 0000000000..4db2eeb747 --- /dev/null +++ b/apps/roadm/src/main/resources/webgui/css.html @@ -0,0 +1,3 @@ + + + diff --git a/apps/roadm/src/main/resources/webgui/js.html b/apps/roadm/src/main/resources/webgui/js.html new file mode 100644 index 0000000000..b3e37df64c --- /dev/null +++ b/apps/roadm/src/main/resources/webgui/js.html @@ -0,0 +1,3 @@ + + + diff --git a/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java b/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java index c427827af0..36a2a9d0dd 100644 --- a/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java +++ b/core/api/src/main/java/org/onosproject/net/ChannelSpacing.java @@ -30,9 +30,9 @@ public enum ChannelSpacing { private final Frequency frequency; /** - * Creates an instance with the specified interval in GHz. + * Creates an instance with the specified interval in MHz. * - * @param value interval of neighboring wavelengths in GHz. + * @param value interval of neighboring wavelengths in MHz. */ ChannelSpacing(long value) { this.frequency = Frequency.ofMHz(value); diff --git a/drivers/optical/src/main/java/org/onosproject/driver/optical/power/OplinkRoadmPowerConfig.java b/drivers/optical/src/main/java/org/onosproject/driver/optical/power/OplinkRoadmPowerConfig.java index 97f6fd08b0..3a7dc0c779 100644 --- a/drivers/optical/src/main/java/org/onosproject/driver/optical/power/OplinkRoadmPowerConfig.java +++ b/drivers/optical/src/main/java/org/onosproject/driver/optical/power/OplinkRoadmPowerConfig.java @@ -122,12 +122,23 @@ public class OplinkRoadmPowerConfig extends AbstractHandlerBehaviour TrafficSelector selector = entry.selector(); OchSignalCriterion entrySigid = (OchSignalCriterion) selector.getCriterion(Criterion.Type.OCH_SIGID); + // Check channel if (entrySigid != null && och.equals(entrySigid.lambda())) { + // Check input port PortCriterion entryPort = (PortCriterion) selector.getCriterion(Criterion.Type.IN_PORT); if (entryPort != null && portNum.equals(entryPort.port())) { return entry; } + + // Check output port + TrafficTreatment treatment = entry.treatment(); + for (Instruction instruction : treatment.allInstructions()) { + if (instruction.type() == Instruction.Type.OUTPUT && + ((Instructions.OutputInstruction) instruction).port().equals(portNum)) { + return entry; + } + } } } log.warn("No matching flow found"); diff --git a/modules.defs b/modules.defs index 4011227cb8..d4624bc043 100644 --- a/modules.defs +++ b/modules.defs @@ -145,6 +145,7 @@ ONOS_APPS = [ '//apps/pcep-api:onos-apps-pcep-api-oar', '//apps/pim:onos-apps-pim-oar', '//apps/reactive-routing:onos-apps-reactive-routing-oar', + '//apps/roadm:onos-apps-roadm-oar', '//apps/sdnip:onos-apps-sdnip-oar', '//apps/test/demo:onos-apps-test-demo-oar', '//apps/test/distributed-primitives:onos-apps-test-distributed-primitives-oar',