mirror of
https://github.com/opennetworkinglab/onos.git
synced 2026-05-04 19:56:49 +02:00
Add ROADM application
Change-Id: I50fa93cf3a69122f6434b46e831b254771159294
This commit is contained in:
parent
7f872a7058
commit
da878fcbf0
@ -31,6 +31,7 @@
|
||||
|
||||
<modules>
|
||||
<module>acl</module>
|
||||
<module>roadm</module>
|
||||
<module>faultmanagement</module>
|
||||
<module>fwd</module>
|
||||
<module>mobility</module>
|
||||
|
||||
24
apps/roadm/BUCK
Normal file
24
apps/roadm/BUCK
Normal file
@ -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' ],
|
||||
)
|
||||
150
apps/roadm/pom.xml
Normal file
150
apps/roadm/pom.xml
Normal file
@ -0,0 +1,150 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ 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.
|
||||
--><project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-apps</artifactId>
|
||||
<version>1.9.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>onos-apps-roadm</artifactId>
|
||||
<packaging>bundle</packaging>
|
||||
|
||||
<description>Application for ROADM device management</description>
|
||||
<url>http://onosproject.org</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<onos.version>1.9.0-SNAPSHOT</onos.version>
|
||||
<onos.app.name>org.onosproject.roadm</onos.app.name>
|
||||
<onos.app.title>ROADM Application</onos.app.title>
|
||||
<onos.app.category>Utility</onos.app.category>
|
||||
<onos.app.readme>
|
||||
This application provides an interface and web GUI for monitoring
|
||||
and configuring power on ROADM devices.
|
||||
</onos.app.readme>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-api</artifactId>
|
||||
<version>${onos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-core-serializers</artifactId>
|
||||
<version>${onos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onlab-osgi</artifactId>
|
||||
<version>${onos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-optical-model</artifactId>
|
||||
<version>${onos.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-api</artifactId>
|
||||
<version>${onos.version}</version>
|
||||
<scope>test</scope>
|
||||
<classifier>tests</classifier>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>org.apache.felix.scr.annotations</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-bundle-plugin</artifactId>
|
||||
<extensions>true</extensions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.felix</groupId>
|
||||
<artifactId>maven-scr-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>generate-scr-srcdescriptor</id>
|
||||
<goals>
|
||||
<goal>scr</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<supportedProjectTypes>
|
||||
<supportedProjectType>bundle</supportedProjectType>
|
||||
<supportedProjectType>war</supportedProjectType>
|
||||
</supportedProjectTypes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.onosproject</groupId>
|
||||
<artifactId>onos-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>cfg</id>
|
||||
<phase>generate-resources</phase>
|
||||
<goals>
|
||||
<goal>cfg</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>swagger</id>
|
||||
<phase>generate-sources</phase>
|
||||
<goals>
|
||||
<goal>swagger</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>app</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>app</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
102
apps/roadm/src/main/java/org/onosproject/roadm/ChannelData.java
Normal file
102
apps/roadm/src/main/java/org/onosproject/roadm/ChannelData.java
Normal file
@ -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<Instruction> instructions = rule.treatment().allInstructions();
|
||||
for (Instruction ins : instructions) {
|
||||
if (ins.type() == Instruction.Type.OUTPUT) {
|
||||
outPort = ((Instructions.OutputInstruction) ins).port();
|
||||
}
|
||||
}
|
||||
checkNotNull(outPort);
|
||||
|
||||
return new ChannelData(inPort, outPort, ochSignal);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the input port.
|
||||
*
|
||||
* @return input port
|
||||
*/
|
||||
public PortNumber inPort() {
|
||||
return inPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output port.
|
||||
*
|
||||
* @return output port
|
||||
*/
|
||||
public PortNumber outPort() {
|
||||
return outPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the channel signal.
|
||||
*
|
||||
* @return channel signal
|
||||
*/
|
||||
public OchSignal ochSignal() {
|
||||
return ochSignal;
|
||||
}
|
||||
}
|
||||
@ -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<DeviceId, Map<PortNumber, Long>> distPowerMap;
|
||||
private Map<DeviceId, Map<PortNumber, Long>> powerMap;
|
||||
|
||||
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
|
||||
protected StorageService storageService;
|
||||
|
||||
@Activate
|
||||
public void activate() {
|
||||
distPowerMap = storageService.<DeviceId, Map<PortNumber, Long>>consistentMapBuilder()
|
||||
.withName("onos-roadm-distributed-store")
|
||||
.withSerializer(Serializer.using(KryoNamespaces.API))
|
||||
.build();
|
||||
powerMap = distPowerMap.asJavaMap();
|
||||
|
||||
log.info("Started");
|
||||
}
|
||||
|
||||
@Deactivate
|
||||
public void deactivate() {
|
||||
log.info("Stopped");
|
||||
}
|
||||
|
||||
// Add a map to the store for a device if not already added.
|
||||
// Powers still need to be initialized with calls to setTargetPower().
|
||||
@Override
|
||||
public void addDevice(DeviceId deviceId) {
|
||||
powerMap.putIfAbsent(deviceId, new HashMap<>());
|
||||
log.info("Initializing {}", deviceId);
|
||||
}
|
||||
|
||||
// Returns true if Map for device exists in ConsistentMap
|
||||
@Override
|
||||
public boolean deviceAvailable(DeviceId deviceId) {
|
||||
return powerMap.get(deviceId) != null;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setTargetPower(DeviceId deviceId, PortNumber portNumber, long targetPower) {
|
||||
Map<PortNumber, Long> portMap = powerMap.get(deviceId);
|
||||
if (portMap != null) {
|
||||
portMap.put(portNumber, targetPower);
|
||||
powerMap.put(deviceId, portMap);
|
||||
} else {
|
||||
log.info("Device {} not found in store", deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTargetPower(DeviceId deviceId, PortNumber portNumber) {
|
||||
Map<PortNumber, Long> portMap = powerMap.get(deviceId);
|
||||
if (portMap != null) {
|
||||
return portMap.get(portNumber);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -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<UiView> 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");
|
||||
}
|
||||
|
||||
}
|
||||
@ -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<RequestHandler> 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,421 @@
|
||||
/*
|
||||
* Copyright 2016-present Open Networking Laboratory
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.onosproject.roadm;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Range;
|
||||
import org.onlab.osgi.ServiceDirectory;
|
||||
import org.onosproject.net.ChannelSpacing;
|
||||
import org.onosproject.net.DeviceId;
|
||||
import org.onosproject.net.OchSignal;
|
||||
import org.onosproject.net.PortNumber;
|
||||
import org.onosproject.net.flow.FlowEntry;
|
||||
import org.onosproject.net.flow.FlowId;
|
||||
import org.onosproject.net.flow.FlowRuleService;
|
||||
import org.onosproject.ui.RequestHandler;
|
||||
import org.onosproject.ui.UiConnection;
|
||||
import org.onosproject.ui.UiMessageHandler;
|
||||
import org.onosproject.ui.table.TableModel;
|
||||
import org.onosproject.ui.table.TableRequestHandler;
|
||||
import org.onosproject.ui.table.cell.HexLongFormatter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.onosproject.ui.JsonUtils.node;
|
||||
import static org.onosproject.ui.JsonUtils.number;
|
||||
|
||||
/**
|
||||
* Table-View message handler for ROADM flow view.
|
||||
*/
|
||||
public class RoadmFlowViewMessageHandler extends UiMessageHandler {
|
||||
|
||||
private static final String ROADM_FLOW_DATA_REQ = "roadmFlowDataRequest";
|
||||
private static final String ROADM_FLOW_DATA_RESP = "roadmFlowDataResponse";
|
||||
private static final String ROADM_FLOWS = "roadmFlows";
|
||||
|
||||
private static final String ROADM_SET_ATTENUATION_REQ = "roadmSetAttenuationRequest";
|
||||
private static final String ROADM_SET_ATTENUATION_RESP = "roadmSetAttenuationResponse";
|
||||
|
||||
private static final String ROADM_DELETE_FLOW_REQ = "roadmDeleteFlowRequest";
|
||||
|
||||
private static final String ROADM_CREATE_FLOW_REQ = "roadmCreateFlowRequest";
|
||||
private static final String ROADM_CREATE_FLOW_RESP = "roadmCreateFlowResponse";
|
||||
|
||||
private static final String NO_ROWS_MESSAGE = "No items found";
|
||||
|
||||
private static final String DEV_ID = "devId";
|
||||
|
||||
private static final String ID = "id";
|
||||
private static final String FLOW_ID = "flowId";
|
||||
private static final String APP_ID = "appId";
|
||||
private static final String GROUP_ID = "groupId";
|
||||
private static final String TABLE_ID = "tableId";
|
||||
private static final String PRIORITY = "priority";
|
||||
private static final String PERMANENT = "permanent";
|
||||
private static final String TIMEOUT = "timeout";
|
||||
private static final String STATE = "state";
|
||||
private static final String IN_PORT = "inPort";
|
||||
private static final String OUT_PORT = "outPort";
|
||||
private static final String CHANNEL_SPACING = "spacing";
|
||||
private static final String CHANNEL_MULTIPLIER = "multiplier";
|
||||
private static final String CURRENT_POWER = "currentPower";
|
||||
private static final String ATTENUATION = "attenuation";
|
||||
private static final String HAS_ATTENUATION = "hasAttenuation";
|
||||
|
||||
private static final String[] COLUMN_IDS = {
|
||||
ID, FLOW_ID, APP_ID, GROUP_ID, TABLE_ID, PRIORITY, TIMEOUT,
|
||||
PERMANENT, STATE, IN_PORT, OUT_PORT, CHANNEL_SPACING,
|
||||
CHANNEL_MULTIPLIER, CURRENT_POWER, ATTENUATION, HAS_ATTENUATION
|
||||
};
|
||||
|
||||
private static final String NA = "N/A";
|
||||
private static final String UNKNOWN = "Unknown";
|
||||
|
||||
private static final long GHZ = 1_000_000_000L;
|
||||
|
||||
private FlowRuleService flowRuleService;
|
||||
private RoadmService roadmService;
|
||||
|
||||
private final Logger log = LoggerFactory.getLogger(getClass());
|
||||
|
||||
@Override
|
||||
public void init(UiConnection connection, ServiceDirectory directory) {
|
||||
super.init(connection, directory);
|
||||
flowRuleService = get(FlowRuleService.class);
|
||||
roadmService = get(RoadmService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Collection<RequestHandler> createRequestHandlers() {
|
||||
return ImmutableSet.of(
|
||||
new FlowTableDataRequestHandler(),
|
||||
new SetAttenuationRequestHandler(),
|
||||
new DeleteConnectionRequestHandler(),
|
||||
new CreateConnectionRequestHandler()
|
||||
);
|
||||
}
|
||||
|
||||
// Handler for sample table requests
|
||||
private final class FlowTableDataRequestHandler extends TableRequestHandler {
|
||||
|
||||
private FlowTableDataRequestHandler() {
|
||||
super(ROADM_FLOW_DATA_REQ, ROADM_FLOW_DATA_RESP, ROADM_FLOWS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String[] getColumnIds() {
|
||||
return COLUMN_IDS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String noRowsMessage(ObjectNode payload) {
|
||||
return NO_ROWS_MESSAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableModel createTableModel() {
|
||||
TableModel tm = super.createTableModel();
|
||||
tm.setFormatter(FLOW_ID, HexLongFormatter.INSTANCE);
|
||||
return tm;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populateTable(TableModel tm, ObjectNode payload) {
|
||||
DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
|
||||
|
||||
Iterable<FlowEntry> flowEntries = flowRuleService.getFlowEntries(deviceId);
|
||||
for (FlowEntry flowEntry : flowEntries) {
|
||||
populateRow(tm.addRow(), flowEntry, deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
private void populateRow(TableModel.Row row, FlowEntry entry, DeviceId deviceId) {
|
||||
ChannelData cd = ChannelData.fromFlow(entry);
|
||||
row.cell(ID, entry.id().value())
|
||||
.cell(FLOW_ID, entry.id().value())
|
||||
.cell(APP_ID, entry.appId())
|
||||
.cell(PRIORITY, entry.priority())
|
||||
.cell(TIMEOUT, entry.timeout())
|
||||
.cell(PERMANENT, entry.isPermanent())
|
||||
.cell(STATE, entry.state().toString())
|
||||
.cell(IN_PORT, cd.inPort().toLong())
|
||||
.cell(OUT_PORT, cd.outPort().toLong())
|
||||
.cell(CHANNEL_SPACING, cd.ochSignal().channelSpacing().frequency().asHz() / GHZ)
|
||||
.cell(CHANNEL_MULTIPLIER, cd.ochSignal().spacingMultiplier())
|
||||
.cell(CURRENT_POWER, getCurrentPower(deviceId, cd))
|
||||
.cell(ATTENUATION, getAttenuation(deviceId, cd));
|
||||
}
|
||||
|
||||
private String getCurrentPower(DeviceId deviceId, ChannelData channelData) {
|
||||
Range<Long> range =
|
||||
roadmService.attenuationRange(deviceId,
|
||||
channelData.outPort(),
|
||||
channelData.ochSignal());
|
||||
if (range != null) {
|
||||
Long currentPower =
|
||||
roadmService.getCurrentChannelPower(deviceId,
|
||||
channelData.outPort(),
|
||||
channelData.ochSignal());
|
||||
if (currentPower != null) {
|
||||
return String.valueOf(currentPower);
|
||||
}
|
||||
}
|
||||
return NA;
|
||||
}
|
||||
|
||||
private String getAttenuation(DeviceId deviceId, ChannelData channelData) {
|
||||
Long attenuation =
|
||||
roadmService.getAttenuation(deviceId, channelData.outPort(),
|
||||
channelData.ochSignal());
|
||||
if (attenuation != null) {
|
||||
return String.valueOf(attenuation);
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for setting attenuation
|
||||
private final class SetAttenuationRequestHandler extends RequestHandler {
|
||||
|
||||
// Keys for response message
|
||||
private static final String VALID = "valid";
|
||||
private static final String MESSAGE = "message";
|
||||
|
||||
// Error messages to display to user
|
||||
private static final String ATTENUATION_RANGE_MSG =
|
||||
"Attenuation must be in range %s.";
|
||||
private static final String NO_ATTENUATION_MSG =
|
||||
"Cannot set attenuation for this connection";
|
||||
|
||||
private SetAttenuationRequestHandler() {
|
||||
super(ROADM_SET_ATTENUATION_REQ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ObjectNode payload) {
|
||||
DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
|
||||
FlowId flowId = FlowId.valueOf(number(payload, FLOW_ID));
|
||||
long attenuation = payload.get(ATTENUATION).asLong();
|
||||
|
||||
// Get connection information from the flow
|
||||
FlowEntry entry = findFlow(deviceId, flowId);
|
||||
if (entry == null) {
|
||||
log.error("Unable to find flow rule to set attenuation");
|
||||
return;
|
||||
}
|
||||
ChannelData cd = ChannelData.fromFlow(entry);
|
||||
Range<Long> range =
|
||||
roadmService.attenuationRange(deviceId, cd.outPort(),
|
||||
cd.ochSignal());
|
||||
|
||||
boolean validAttenuation = (range != null && range.contains(attenuation));
|
||||
if (validAttenuation) {
|
||||
roadmService.setAttenuation(deviceId, cd.outPort(),
|
||||
cd.ochSignal(), attenuation);
|
||||
}
|
||||
|
||||
ObjectNode rootNode = objectNode();
|
||||
// Send back flowId so view can identify which callback function to use
|
||||
rootNode.put(FLOW_ID, payload.get(FLOW_ID).asText());
|
||||
rootNode.put(VALID, validAttenuation);
|
||||
if (range != null) {
|
||||
rootNode.put(MESSAGE, String.format(ATTENUATION_RANGE_MSG,
|
||||
range.toString()));
|
||||
} else {
|
||||
rootNode.put(MESSAGE, NO_ATTENUATION_MSG);
|
||||
}
|
||||
sendMessage(ROADM_SET_ATTENUATION_RESP, rootNode);
|
||||
}
|
||||
|
||||
private FlowEntry findFlow(DeviceId deviceId, FlowId flowId) {
|
||||
for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
|
||||
if (entry.id().equals(flowId)) {
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for deleting a connection
|
||||
private final class DeleteConnectionRequestHandler extends RequestHandler {
|
||||
private DeleteConnectionRequestHandler() {
|
||||
super(ROADM_DELETE_FLOW_REQ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ObjectNode payload) {
|
||||
DeviceId deviceId = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
|
||||
FlowId flowId = FlowId.valueOf(payload.get(ID).asLong());
|
||||
roadmService.removeConnection(deviceId, flowId);
|
||||
}
|
||||
}
|
||||
|
||||
// Handler for creating a creating a connection from form data
|
||||
private final class CreateConnectionRequestHandler extends RequestHandler {
|
||||
|
||||
// Keys to load from JSON
|
||||
private static final String FORM_DATA = "formData";
|
||||
private static final String CHANNEL_SPACING_INDEX = "index";
|
||||
private static final String INCLUDE_ATTENUATION = "includeAttenuation";
|
||||
|
||||
// Keys for validation results
|
||||
private static final String CONNECTION = "connection";
|
||||
private static final String CHANNEL_AVAILABLE = "channelAvailable";
|
||||
|
||||
// Error messages to display to user
|
||||
private static final String IN_PORT_ERR_MSG = "Invalid input port.";
|
||||
private static final String OUT_PORT_ERR_MSG = "Invalid output port.";
|
||||
private static final String CONNECTION_ERR_MSG =
|
||||
"Invalid connection from input port to output port.";
|
||||
private static final String CHANNEL_SPACING_ERR_MSG =
|
||||
"Channel spacing not supported.";
|
||||
private static final String CHANNEL_ERR_MSG =
|
||||
"Channel index must be in range %s.";
|
||||
private static final String CHANNEL_AVAILABLE_ERR_MSG =
|
||||
"Channel is already being used.";
|
||||
private static final String ATTENUATION_ERR_MSG =
|
||||
"Attenuation must be in range %s.";
|
||||
|
||||
// Keys for validation object
|
||||
private static final String VALID = "valid";
|
||||
private static final String MESSAGE = "message";
|
||||
|
||||
private CreateConnectionRequestHandler() {
|
||||
super(ROADM_CREATE_FLOW_REQ);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(ObjectNode payload) {
|
||||
DeviceId did = DeviceId.deviceId(string(payload, DEV_ID, "(none)"));
|
||||
ObjectNode flowNode = node(payload, FORM_DATA);
|
||||
int priority = (int) number(flowNode, PRIORITY);
|
||||
boolean permanent = bool(flowNode, PERMANENT);
|
||||
int timeout = (int) number(flowNode, TIMEOUT);
|
||||
PortNumber inPort = PortNumber.portNumber(number(flowNode, IN_PORT));
|
||||
PortNumber outPort = PortNumber.portNumber(number(flowNode, OUT_PORT));
|
||||
ObjectNode chNode = node(flowNode, CHANNEL_SPACING);
|
||||
ChannelSpacing spacing =
|
||||
channelSpacing((int) number(chNode, CHANNEL_SPACING_INDEX));
|
||||
int multiplier = (int) number(flowNode, CHANNEL_MULTIPLIER);
|
||||
OchSignal och = OchSignal.newDwdmSlot(spacing, multiplier);
|
||||
boolean includeAttenuation = bool(flowNode, INCLUDE_ATTENUATION);
|
||||
long att = number(flowNode, ATTENUATION);
|
||||
|
||||
boolean validInPort = roadmService.validInputPort(did, inPort);
|
||||
boolean validOutPort = roadmService.validOutputPort(did, outPort);
|
||||
boolean validConnect = roadmService.validConnection(did, inPort, outPort);
|
||||
boolean validSpacing = true;
|
||||
boolean validChannel = roadmService.validChannel(did, inPort, och);
|
||||
boolean channelAvailable = roadmService.channelAvailable(did, och);
|
||||
boolean validAttenuation = roadmService.attenuationInRange(did, outPort, att);
|
||||
|
||||
if (validConnect && validChannel && channelAvailable) {
|
||||
if (includeAttenuation && validAttenuation) {
|
||||
roadmService.createConnection(did, priority, permanent,
|
||||
timeout, inPort, outPort,
|
||||
och, att);
|
||||
} else if (!includeAttenuation) {
|
||||
roadmService.createConnection(did, priority, permanent,
|
||||
timeout, inPort, outPort,
|
||||
och);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct error for channel
|
||||
String channelMessage = "Invalid channel";
|
||||
if (!validChannel) {
|
||||
Set<OchSignal> lambdas = roadmService.queryLambdas(did, outPort);
|
||||
if (lambdas != null) {
|
||||
Range<Integer> range = channelRange(lambdas);
|
||||
if (range.contains(och.spacingMultiplier())) {
|
||||
// Channel spacing error
|
||||
validSpacing = false;
|
||||
} else {
|
||||
channelMessage = String.format(CHANNEL_ERR_MSG, range.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Construct error for attenuation
|
||||
String attenuationMessage = "Invalid attenuation";
|
||||
if (!validAttenuation) {
|
||||
Range<Long> range =
|
||||
roadmService.attenuationRange(did, outPort, och);
|
||||
if (range != null) {
|
||||
attenuationMessage =
|
||||
String.format(ATTENUATION_ERR_MSG, range.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Build response
|
||||
ObjectNode node = objectNode();
|
||||
|
||||
node.set(IN_PORT, validationObject(validInPort, IN_PORT_ERR_MSG));
|
||||
node.set(OUT_PORT, validationObject(validOutPort, OUT_PORT_ERR_MSG));
|
||||
node.set(CONNECTION, validationObject(validConnect, CONNECTION_ERR_MSG));
|
||||
node.set(CHANNEL_SPACING, validationObject(validChannel || validSpacing,
|
||||
CHANNEL_SPACING_ERR_MSG));
|
||||
node.set(CHANNEL_MULTIPLIER, validationObject(validChannel || !validSpacing,
|
||||
channelMessage));
|
||||
node.set(CHANNEL_AVAILABLE, validationObject(!validChannel || channelAvailable,
|
||||
CHANNEL_AVAILABLE_ERR_MSG));
|
||||
node.set(ATTENUATION, validationObject(validAttenuation, attenuationMessage));
|
||||
node.put(INCLUDE_ATTENUATION, includeAttenuation);
|
||||
|
||||
sendMessage(ROADM_CREATE_FLOW_RESP, node);
|
||||
}
|
||||
|
||||
// Returns the ChannelSpacing based on the selection made
|
||||
private ChannelSpacing channelSpacing(int selectionIndex) {
|
||||
switch (selectionIndex) {
|
||||
case 0: return ChannelSpacing.CHL_100GHZ;
|
||||
case 1: return ChannelSpacing.CHL_50GHZ;
|
||||
case 2: return ChannelSpacing.CHL_25GHZ;
|
||||
case 3: return ChannelSpacing.CHL_12P5GHZ;
|
||||
// 6.25GHz cannot be used with ChannelSpacing.newDwdmSlot
|
||||
// case 4: return ChannelSpacing.CHL_6P25GHZ;
|
||||
default: return ChannelSpacing.CHL_50GHZ;
|
||||
}
|
||||
}
|
||||
|
||||
// Construct validation object to return to the view
|
||||
private ObjectNode validationObject(boolean result, String message) {
|
||||
ObjectNode node = objectNode();
|
||||
node.put(VALID, result);
|
||||
if (!result) {
|
||||
// return error message to display if validation failed
|
||||
node.put(MESSAGE, message);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
// Returns the minimum and maximum channel spacing
|
||||
private Range<Integer> channelRange(Set<OchSignal> signals) {
|
||||
Comparator<OchSignal> compare =
|
||||
(OchSignal a, OchSignal b) -> a.spacingMultiplier() - b.spacingMultiplier();
|
||||
OchSignal minOch = Collections.min(signals, compare);
|
||||
OchSignal maxOch = Collections.max(signals, compare);
|
||||
return Range.closed(minOch.spacingMultiplier(), maxOch.spacingMultiplier());
|
||||
}
|
||||
}
|
||||
}
|
||||
530
apps/roadm/src/main/java/org/onosproject/roadm/RoadmManager.java
Normal file
530
apps/roadm/src/main/java/org/onosproject/roadm/RoadmManager.java
Normal file
@ -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<Object> 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<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig == null) {
|
||||
log.warn("Unable to set default initial powers for port {} on device {}",
|
||||
portNumber, deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
|
||||
if (!range.isPresent()) {
|
||||
log.warn("No target power range found for port {} on device {}",
|
||||
portNumber, deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
Long power = roadmStore.getTargetPower(deviceId, portNumber);
|
||||
if (power == null) {
|
||||
// Set default to middle of the range
|
||||
power = (range.get().lowerEndpoint() + range.get().upperEndpoint()) / 2;
|
||||
roadmStore.setTargetPower(deviceId, portNumber, power);
|
||||
}
|
||||
powerConfig.setTargetPower(portNumber, Direction.ALL, power);
|
||||
}
|
||||
|
||||
// Sets the target port powers for each each port on a device
|
||||
// Attempts to read target powers from store. If no value is found then
|
||||
// default value is used instead
|
||||
private void setAllInitialTargetPortPowers(DeviceId deviceId) {
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig == null) {
|
||||
log.warn("Unable to set default initial powers for device {}",
|
||||
deviceId);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Port> ports = deviceService.getPorts(deviceId);
|
||||
for (Port port : ports) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(port.number(), Direction.ALL);
|
||||
if (range.isPresent()) {
|
||||
Long power = roadmStore.getTargetPower(deviceId, port.number());
|
||||
if (power == null) {
|
||||
// Set default to middle of the range
|
||||
power = (range.get().lowerEndpoint() + range.get().upperEndpoint()) / 2;
|
||||
roadmStore.setTargetPower(deviceId, port.number(), power);
|
||||
}
|
||||
powerConfig.setTargetPower(port.number(), Direction.ALL, power);
|
||||
} else {
|
||||
log.warn("No target power range found for port {} on device {}",
|
||||
port.number(), deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTargetPortPower(DeviceId deviceId, PortNumber portNumber, long power) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
roadmStore.setTargetPower(deviceId, portNumber, power);
|
||||
powerConfig.setTargetPower(portNumber, Direction.ALL, power);
|
||||
} else {
|
||||
log.warn("Unable to set target port power for device {}", deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
// 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<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
powerConfig.setTargetPower(portNumber, ochSignal, attenuation);
|
||||
} else {
|
||||
log.warn("Cannot set attenuation for channel index {} on device {}",
|
||||
ochSignal.spacingMultiplier(), deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getAttenuation(DeviceId deviceId, PortNumber portNumber,
|
||||
OchSignal ochSignal) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
checkNotNull(ochSignal);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Long> attenuation =
|
||||
powerConfig.getTargetPower(portNumber, ochSignal);
|
||||
if (attenuation.isPresent()) {
|
||||
return attenuation.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCurrentPortPower(DeviceId deviceId, PortNumber portNumber) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Long> currentPower =
|
||||
powerConfig.currentPower(portNumber, Direction.ALL);
|
||||
if (currentPower.isPresent()) {
|
||||
return currentPower.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getCurrentChannelPower(DeviceId deviceId, PortNumber portNumber,
|
||||
OchSignal ochSignal) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
checkNotNull(ochSignal);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Long> currentPower =
|
||||
powerConfig.currentPower(portNumber, ochSignal);
|
||||
if (currentPower.isPresent()) {
|
||||
return currentPower.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<OchSignal> queryLambdas(DeviceId deviceId, PortNumber portNumber) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
LambdaQuery lambdaQuery = getLambdaQuery(deviceId);
|
||||
if (lambdaQuery != null) {
|
||||
return lambdaQuery.queryLambdas(portNumber);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
|
||||
int timeout, PortNumber inPort, PortNumber outPort,
|
||||
OchSignal ochSignal) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(inPort);
|
||||
checkNotNull(outPort);
|
||||
|
||||
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<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
|
||||
return range.isPresent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean portTargetPowerInRange(DeviceId deviceId, PortNumber portNumber,
|
||||
long power) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
|
||||
return range.isPresent() && range.get().contains(power);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean attenuationInRange(DeviceId deviceId, PortNumber outPort,
|
||||
long att) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(outPort);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
OchSignal stubOch = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 0);
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(outPort, stubOch);
|
||||
return range.isPresent() && range.get().contains(att);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validInputPort(DeviceId deviceId, PortNumber portNumber) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getInputPowerRange(portNumber, Direction.ALL);
|
||||
return range.isPresent();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validOutputPort(DeviceId deviceId, PortNumber portNumber) {
|
||||
return hasPortTargetPower(deviceId, portNumber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validChannel(DeviceId deviceId, PortNumber portNumber,
|
||||
OchSignal ochSignal) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
LambdaQuery lambdaQuery = getLambdaQuery(deviceId);
|
||||
if (lambdaQuery != null) {
|
||||
Set<OchSignal> channels = lambdaQuery.queryLambdas(portNumber);
|
||||
return channels.contains(ochSignal);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean channelAvailable(DeviceId deviceId, OchSignal ochSignal) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(ochSignal);
|
||||
for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
|
||||
if (ChannelData.fromFlow(entry).ochSignal().equals(ochSignal)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean validConnection(DeviceId deviceId, PortNumber inPort,
|
||||
PortNumber outPort) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(inPort);
|
||||
checkNotNull(outPort);
|
||||
return validInputPort(deviceId, inPort) && validOutputPort(deviceId, outPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> targetPortPowerRange(DeviceId deviceId, PortNumber portNumber) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
|
||||
if (range.isPresent()) {
|
||||
return range.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> attenuationRange(DeviceId deviceId, PortNumber portNumber,
|
||||
OchSignal ochSignal) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
checkNotNull(ochSignal);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getTargetPowerRange(portNumber, ochSignal);
|
||||
if (range.isPresent()) {
|
||||
return range.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Range<Long> inputPortPowerRange(DeviceId deviceId, PortNumber portNumber) {
|
||||
checkNotNull(deviceId);
|
||||
checkNotNull(portNumber);
|
||||
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
|
||||
if (powerConfig != null) {
|
||||
Optional<Range<Long>> range =
|
||||
powerConfig.getInputPowerRange(portNumber, Direction.ALL);
|
||||
if (range.isPresent()) {
|
||||
return range.get();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 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;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<RequestHandler> 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<Port> 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<Long> 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<Long> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
283
apps/roadm/src/main/java/org/onosproject/roadm/RoadmService.java
Normal file
283
apps/roadm/src/main/java/org/onosproject/roadm/RoadmService.java
Normal file
@ -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<Object>} in order to support both Direction and OchSignal.
|
||||
* For a reference implementation of PowerConfig, please see
|
||||
* OplinkRoadmPowerConfig
|
||||
*
|
||||
* In this application, a "connection" refers to the selection of a channel
|
||||
* to direct from an input to an output port. Connections are implemented
|
||||
* using FlowRules with an input port selector, optical channel selector,
|
||||
* and output port treatment (see RoadmManager#createConnection()).
|
||||
*
|
||||
* This application currently only supports fixed grid channels.
|
||||
*/
|
||||
public interface RoadmService {
|
||||
|
||||
/**
|
||||
* 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<OchSignal> queryLambdas(DeviceId deviceId, PortNumber portNumber);
|
||||
|
||||
/**
|
||||
* Creates a new internal connection on a device without attenuation. This does
|
||||
* not check that the connection is actually valid (e.g. an input port to an
|
||||
* output port).
|
||||
*
|
||||
* Connections are represented as flows with an input port, output port, and
|
||||
* channel. 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<Long> targetPortPowerRange(DeviceId deviceId, PortNumber portNumber);
|
||||
|
||||
/**
|
||||
* Returns the acceptable attenuation range for a connection.
|
||||
*
|
||||
* @param deviceId DeviceId of the device
|
||||
* @param portNumber PortNumber of either the input or output port
|
||||
* @param ochSignal channel to check
|
||||
* @return range if found, null otherwise
|
||||
*/
|
||||
Range<Long> attenuationRange(DeviceId deviceId, PortNumber portNumber,
|
||||
OchSignal ochSignal);
|
||||
|
||||
/**
|
||||
* Returns the expected input power range for an input port.
|
||||
*
|
||||
* @param deviceId DeviceId of the device
|
||||
* @param portNumber PortNumber of an input port
|
||||
* @return range if found, null otherwise
|
||||
*/
|
||||
Range<Long> inputPortPowerRange(DeviceId deviceId, PortNumber portNumber);
|
||||
}
|
||||
@ -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.
|
||||
*
|
||||
* <p>The device needs to be added to the store
|
||||
* before setTargetPower and getTargetPower can be used. This does not initialize
|
||||
* any of the target powers.
|
||||
*
|
||||
* @param deviceId DeviceId of the device to add
|
||||
*/
|
||||
void addDevice(DeviceId deviceId);
|
||||
|
||||
/**
|
||||
* Returns true if the device has been added to the store.
|
||||
*
|
||||
* @param deviceId DeviceId of the device to check
|
||||
* @return true if device has been added to the store, false otherwise
|
||||
*/
|
||||
boolean deviceAvailable(DeviceId deviceId);
|
||||
|
||||
/**
|
||||
* Stores the targetPower for a port on a device. The device needs to be added
|
||||
* to the store before this can be called. This does nothing if the device is
|
||||
* not added.
|
||||
*
|
||||
* @param deviceId DeviceId of the device
|
||||
* @param portNumber PortNumber of the port
|
||||
* @param targetPower target port power to store
|
||||
*/
|
||||
void setTargetPower(DeviceId deviceId, PortNumber portNumber, long targetPower);
|
||||
|
||||
/**
|
||||
* Returns the targetPower for a port on a device. The device needs to be added
|
||||
* to the store before this can be called. Returns null if the port's target
|
||||
* power has not yet been initialized using setTargetPower.
|
||||
*
|
||||
* @param deviceId DeviceId of the device
|
||||
* @param portNumber PortNumber of the port
|
||||
* @return target power if target power has already been set, null otherwise
|
||||
*/
|
||||
Long getTargetPower(DeviceId deviceId, PortNumber portNumber);
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,70 @@
|
||||
<!-- partial HTML -->
|
||||
<div id="ov-roadm-device" class="less-gap">
|
||||
|
||||
<div class="tabular-header">
|
||||
<h2>Optical Devices ({{tableData.length}} total)</h2>
|
||||
<div class="ctrl-btns">
|
||||
<div class="refresh" ng-class="{active: autoRefresh}"
|
||||
icon icon-id="refresh" icon-size="42"
|
||||
tooltip tt-msg="autoRefreshTip"
|
||||
ng-click="toggleRefresh()"></div>
|
||||
<div class="separator"></div>
|
||||
|
||||
<div ng-class="{'current-view': !!selId}"
|
||||
icon icon-id="deviceTable" icon-size="42"></div>
|
||||
|
||||
<div ng-class="{active: !!selId}"
|
||||
icon icon-id="flowTable" icon-size="42"
|
||||
tooltip tt-msg="flowTip"
|
||||
ng-click="nav('roadmFlow')"></div>
|
||||
|
||||
|
||||
<div ng-class="{active: !!selId}"
|
||||
icon icon-id="portTable" icon-size="42"
|
||||
tooltip tt-msg="portTip"
|
||||
ng-click="nav('roadmPort')"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-list" onos-table-resize>
|
||||
|
||||
<div class="table-header" onos-sortable-header>
|
||||
<table>
|
||||
<tr>
|
||||
<td colId="name"sortable>Friendly Name</td>
|
||||
<td colId="id" sortable>Device ID </td>
|
||||
<td colId="master" sortable col-width="120px">Master </td>
|
||||
<td colId="ports" sortable col-width="70px">Ports </td>
|
||||
<td colId="vendor" sortable>Vendor </td>
|
||||
<td colId="hwVersion" sortable>H/W Version </td>
|
||||
<td colId="swVersion" sortable>S/W Version </td>
|
||||
<td colId="protocol" sortable col-width="100px">Protocol </td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-body">
|
||||
<table>
|
||||
<tr ng-if="!tableData.length" class="no-data">
|
||||
<td colspan="3">
|
||||
{{annots.no_rows_msg}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="item in tableData track by $index"
|
||||
ng-class="{selected: item.id === selId}"
|
||||
ng-click="selectCallback($event, item)">
|
||||
<td>{{item.name}}</td>
|
||||
<td>{{item.id}}</td>
|
||||
<td>{{item.master}}</td>
|
||||
<td>{{item.ports}}</td>
|
||||
<td>{{item.vendor}}</td>
|
||||
<td>{{item.hwVersion}}</td>
|
||||
<td>{{item.swVersion}}</td>
|
||||
<td>{{item.protocol}}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
@ -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');
|
||||
}]);
|
||||
}());
|
||||
132
apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css
Normal file
132
apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.css
Normal file
@ -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;
|
||||
}
|
||||
118
apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html
Normal file
118
apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.html
Normal file
@ -0,0 +1,118 @@
|
||||
<!-- partial HTML -->
|
||||
<div id="ov-roadm-flow" class="less-gap">
|
||||
|
||||
<div class="tabular-header">
|
||||
<h2>Connections for Optical Device {{devId}} ({{tableData.length}} total)</h2>
|
||||
<div class="ctrl-btns">
|
||||
<div class="active"
|
||||
icon icon-id="plus" icon-size="42"
|
||||
tooltip tt-msg="addFlowTip"
|
||||
ng-click="displayFlowForm()"></div>
|
||||
|
||||
<div class="refresh" ng-class="{active: autoRefresh}"
|
||||
icon icon-id="refresh" icon-size="42"
|
||||
tooltip tt-msg="autoRefreshTip"
|
||||
ng-click="toggleRefresh()"></div>
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="active"
|
||||
icon icon-id="deviceTable" icon-size="42"
|
||||
tooltip tt-msg="deviceTip"
|
||||
ng-click="nav('roadmDevice')"></div>
|
||||
|
||||
<div class="current-view"
|
||||
icon icon-id="flowTable" icon-size="42"
|
||||
tooltip tt-msg="flowTip"></div>
|
||||
|
||||
<div class="active"
|
||||
icon icon-id="portTable" icon-size="42"
|
||||
tooltip tt-msg="portTip"
|
||||
ng-click="nav('roadmPort')"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-list" onos-table-resize>
|
||||
|
||||
<div class="table-header" onos-sortable-header>
|
||||
<table>
|
||||
<tr>
|
||||
<td col-width="30px"></td>
|
||||
<td colId="flowId" sortable col-width="180px">Flow ID </td>
|
||||
<td colId="appId" sortable>App ID </td>
|
||||
<td colId="priority" sortable>Priority </td>
|
||||
<td colId="timeout" sortable>Timeout </td>
|
||||
<td colId="permanent" sortable>Permanent </td>
|
||||
<td colId="state" sortable>State </td>
|
||||
<td colId="inPort" sortable>In Port </td>
|
||||
<td colId="outPort" sortable>Out Port </td>
|
||||
<td colId="multiplier" sortable>Channel </td>
|
||||
<td colId="spacing">Spacing <span class="units">(GHz)</span> </td>
|
||||
<td colId="currentPower" col-width="180px">Current Power <span class="units">(0.01dBm)</span></td>
|
||||
<td colId="attenuation" col-width="200px">Attenuation <span class="units">(0.01dB)</span></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-body">
|
||||
<table>
|
||||
<tr ng-if="!tableData.length" class="no-data">
|
||||
<td colspan="13">
|
||||
{{annots.no_rows_msg}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="flow in tableData track by $index"
|
||||
ng-class="{selected: flow.id === selId}">
|
||||
<td class="delete-icon" ng-click="deleteFlow($event, flow)">×</td>
|
||||
<td>{{flow.flowId}}</td>
|
||||
<td>{{flow.appId}}</td>
|
||||
<td>{{flow.priority}}</td>
|
||||
<td>{{flow.timeout}}</td>
|
||||
<td>{{flow.permanent}}</td>
|
||||
<td>{{flow.state}}</td>
|
||||
<td>{{flow.inPort}}</td>
|
||||
<td>{{flow.outPort}}</td>
|
||||
<td>{{flow.multiplier}} ({{flow.multiplier * 0.05 + 193.1 | number:2}} THz)</td>
|
||||
<td>{{flow.spacing}}</td>
|
||||
<td>{{flow.currentPower}}</td>
|
||||
<td class="editable" roadm-att="flow" roadm-set-att="setAttenuation(flow, targetVal, cb)"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flow-form" ng-controller="FlowFormController as form" ng-show="showFlowForm">
|
||||
<div class="delete-icon" ng-click="hideFlowForm()">×</div>
|
||||
<form>
|
||||
<label>Priority</label><input type="number" ng-model="form.flow.priority" />
|
||||
<span class="form-error" ng-show="form.priorityError" >{{form.priorityMessage}}</span><br />
|
||||
|
||||
<label>Is Permanent</label><input type="checkbox" ng-model="form.flow.permanent" /><br />
|
||||
|
||||
<label>Timeout</label><input type="number" ng-model="form.flow.timeout" ng-disabled="form.flow.permanent"/>
|
||||
<span class="form-error" ng-show="form.timeoutError" >{{form.timeoutMessage}}</span><br />
|
||||
|
||||
<label>In Port</label><input type="number" ng-model="form.flow.inPort" />
|
||||
<span class="form-error" ng-show="form.inPortError">{{form.inPortMessage}}</span><br />
|
||||
|
||||
<label>Out Port</label><input type="number" ng-model="form.flow.outPort" />
|
||||
<span class="form-error" ng-show="form.outPortError">{{form.outPortMessage}}</span>
|
||||
<span class="form-error" ng-show="form.connectionError">{{form.connectionMessage}}</span><br />
|
||||
|
||||
<label>Channel Spacing</label><select ng-model="form.flow.spacing" ng-options="x.freq for x in form.spacings"></select>
|
||||
<span class="form-error" ng-show="form.spacingError">{{form.spacingMessage}}</span><br />
|
||||
|
||||
<label>Spacing Multiplier</label><input type="number" ng-model="form.flow.multiplier" />
|
||||
<span class="form-error" ng-show="form.multiplierError">{{form.multiplierMessage}}</span>
|
||||
<span class="form-error" ng-show="form.channelError">{{form.channelMessage}}</span><br />
|
||||
|
||||
<label>Include Attenuation</label><input type="checkbox" ng-model="form.flow.includeAttenuation" /><br />
|
||||
|
||||
<label>Attenuation</label><input type="number" ng-model="form.flow.attenuation" ng-disabled="!form.flow.includeAttenuation"/>
|
||||
<span class="form-error" ng-show="form.attenuationError">{{form.attenuationMessage}}</span><br />
|
||||
</form>
|
||||
<button type="submit" class="submit" ng-click="form.createFlow(form.flow)">Create Connection</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
324
apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js
Normal file
324
apps/roadm/src/main/resources/app/view/roadmFlow/roadmFlow.js
Normal file
@ -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 =
|
||||
'<span class="attenuation" ng-show="!editMode" ng-click="enableEdit()">{{currItem.attenuation}}</span>' +
|
||||
'<form ng-show="editMode" name="form" novalidate>' +
|
||||
'<input type="number" name="formVal" ng-model="formVal">' +
|
||||
'<button type="submit" class="submit" ng-click="send()">Set</button>' +
|
||||
'<button type="button" class="cancel" ng-click="cancel()">Cancel</button>' +
|
||||
'<span class="input-error" ng-show="showError">{{errorMessage}}</span>' +
|
||||
'</form>';
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
}());
|
||||
@ -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;
|
||||
}
|
||||
@ -0,0 +1,73 @@
|
||||
<!-- partial HTML -->
|
||||
<div id="ov-roadm-port" class="less-gap">
|
||||
|
||||
<div class="tabular-header">
|
||||
<h2>Ports for Optical Device {{devId}} ({{tableData.length}} total)</h2>
|
||||
<div class="ctrl-btns">
|
||||
<div class="refresh" ng-class="{active: autoRefresh}"
|
||||
icon icon-id="refresh" icon-size="42"
|
||||
tooltip tt-msg="autoRefreshTip"
|
||||
ng-click="toggleRefresh()"></div>
|
||||
<div class="separator"></div>
|
||||
|
||||
<div class="active"
|
||||
icon icon-id="deviceTable" icon-size="42"
|
||||
tooltip tt-msg="deviceTip"
|
||||
ng-click="nav('roadmDevice')"></div>
|
||||
|
||||
<div class="active"
|
||||
icon icon-id="flowTable" icon-size="42"
|
||||
tooltip tt-msg="flowTip"
|
||||
ng-click="nav('roadmFlow')"></div>
|
||||
|
||||
<div class="current-view"
|
||||
icon icon-id="portTable" icon-size="42"
|
||||
tooltip tt-msg="portTip"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="summary-list" onos-table-resize>
|
||||
|
||||
<div class="table-header" onos-sortable-header>
|
||||
<table>
|
||||
<tr>
|
||||
<td colId="id" sortable>Port Number </td>
|
||||
<td colId="name" sortable>Name </td>
|
||||
<td colId="type" sortable>Type </td>
|
||||
<td colId="enabled" sortable>Enabled </td>
|
||||
<td colId="minFreq" sortable>Min Freq <span class="units">(THz)</span> </td>
|
||||
<td colId="maxFreq" sortable>Max Freq <span class="units">(THz)</span> </td>
|
||||
<td colId="grid" sortable>Grid <span class="units">(GHz)</span> </td>
|
||||
<td colId="portMac" sortable>Input Power Range </td>
|
||||
<td colId="currentPower">Current Power <span class="units">(0.01dBm)</span> </td>
|
||||
<td colId="targetPower" col-width="200px">Target Power <span class="units">(0.01dBm)</span> </td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="table-body">
|
||||
<table>
|
||||
<tr ng-if="!tableData.length" class="no-data">
|
||||
<td colspan="10">
|
||||
{{annots.no_rows_msg}}
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr ng-repeat="item in tableData track by $index"
|
||||
ng-class="{selected: item.id === selId}">
|
||||
<td>{{item.id}}</td>
|
||||
<td>{{item.name}}</td>
|
||||
<td>{{item.type}}</td>
|
||||
<td>{{item.enabled}}</td>
|
||||
<td>{{item.minFreq}}</td>
|
||||
<td>{{item.maxFreq}}</td>
|
||||
<td>{{item.grid}}</td>
|
||||
<td>{{item.inputPowerRange}}</td>
|
||||
<td>{{item.currentPower}}</td>
|
||||
<td class="editable" roadm-power="item" roadm-set-power="setPortPower(port, targetVal, cb)"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
168
apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js
Normal file
168
apps/roadm/src/main/resources/app/view/roadmPort/roadmPort.js
Normal file
@ -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 =
|
||||
'<span class="target-power" ng-show="!editMode" ng-click="enableEdit()">{{currItem.targetPower}}</span>' +
|
||||
'<form ng-show="editMode" name="form" novalidate>' +
|
||||
'<input type="number" name="formVal" ng-model="formVal">' +
|
||||
'<button type="submit" ng-click="send()">Set</button>' +
|
||||
'<button type="button" ng-click="cancel()">Cancel</button>' +
|
||||
'<span class="input-error" ng-show="showError">{{errorMessage}}</span>' +
|
||||
'</form>';
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}]);
|
||||
}());
|
||||
3
apps/roadm/src/main/resources/webgui/css.html
Normal file
3
apps/roadm/src/main/resources/webgui/css.html
Normal file
@ -0,0 +1,3 @@
|
||||
<link rel="stylesheet" href="app/view/roadmDevice/roadmDevice.css">
|
||||
<link rel="stylesheet" href="app/view/roadmPort/roadmPort.css">
|
||||
<link rel="stylesheet" href="app/view/roadmFlow/roadmFlow.css">
|
||||
3
apps/roadm/src/main/resources/webgui/js.html
Normal file
3
apps/roadm/src/main/resources/webgui/js.html
Normal file
@ -0,0 +1,3 @@
|
||||
<script src="app/view/roadmDevice/roadmDevice.js"></script>
|
||||
<script src="app/view/roadmPort/roadmPort.js"></script>
|
||||
<script src="app/view/roadmFlow/roadmFlow.js"></script>
|
||||
@ -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);
|
||||
|
||||
@ -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");
|
||||
|
||||
@ -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',
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user