blob: 191f608ece99dd9b0cb06d2d21f9ef99cc1a6ecb [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.roadm;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.net.ChannelSpacing;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Direction;
import org.onosproject.net.ModulationScheme;
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.ModulationConfig;
import org.onosproject.net.behaviour.PowerConfig;
import org.onosproject.net.behaviour.protection.ProtectedTransportEndpointState;
import org.onosproject.net.behaviour.protection.ProtectionConfigBehaviour;
import org.onosproject.net.behaviour.protection.TransportEndpointState;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceListener;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.FlowId;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.FlowRuleService;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.instructions.Instructions;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceCardinality;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.onosproject.net.optical.OpticalAnnotations.INPUT_PORT_STATUS;
import static org.onosproject.roadm.RoadmUtil.OPS_OPT_AUTO;
import static org.onosproject.roadm.RoadmUtil.OPS_OPT_FORCE;
import static org.onosproject.roadm.RoadmUtil.OPS_OPT_MANUAL;
/**
* Application for monitoring and configuring ROADM devices.
*/
@Component(immediate = true, service = RoadmService.class)
public class RoadmManager implements RoadmService {
private static final String APP_NAME = "org.onosproject.roadm";
private ApplicationId appId;
private final Logger log = LoggerFactory.getLogger(getClass());
private DeviceListener deviceListener = new InternalDeviceListener();
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected RoadmStore roadmStore;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected DeviceService deviceService;
@Reference(cardinality = ReferenceCardinality.MANDATORY)
protected FlowRuleService flowRuleService;
@Activate
protected void activate() {
appId = coreService.registerApplication(APP_NAME);
deviceService.addListener(deviceListener);
initDevices();
log.info("Started");
}
@Deactivate
protected void deactivate() {
deviceService.removeListener(deviceListener);
log.info("Stopped");
}
@Deprecated
@Override
public void setProtectionSwitchWorkingPath(DeviceId deviceId, int index) {
checkNotNull(deviceId);
ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
if (behaviour == null) {
return;
}
Map<ConnectPoint, ProtectedTransportEndpointState> map = getProtectionSwitchStates(behaviour);
if (map == null) {
log.warn("Failed to get protected transport endpoint state in device {}", deviceId);
return;
}
if (map.isEmpty()) {
log.warn("No protected transport endpoint state found in device {}", deviceId);
return;
}
behaviour.switchToManual(map.keySet().toArray(new ConnectPoint[0])[0], index);
}
@Deprecated
@Override
public String getProtectionSwitchPortState(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
if (behaviour == null) {
return null;
}
Map<ConnectPoint, ProtectedTransportEndpointState> map = getProtectionSwitchStates(behaviour);
if (map == null) {
log.warn("Failed to get protected transport endpoint state in device {}", deviceId);
return null;
}
for (ProtectedTransportEndpointState state : map.values()) {
for (TransportEndpointState element : state.pathStates()) {
if (element.description().output().connectPoint().port().equals(portNumber)) {
return element.attributes().get(INPUT_PORT_STATUS);
}
}
}
// Do not need warning here for port polling.
log.debug("Unable to get port status, device: {}, port: {}", deviceId, portNumber);
return null;
}
@Override
public void configProtectionSwitch(DeviceId deviceId, String operation, ConnectPoint identifier, int index) {
checkNotNull(deviceId);
ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
if (behaviour == null) {
return;
}
// automatic operation
if (OPS_OPT_AUTO.equals(operation)) {
behaviour.switchToAutomatic(identifier);
return;
}
// force or manual operation
if (OPS_OPT_MANUAL.equals(operation)) {
behaviour.switchToManual(identifier, index);
} else if (OPS_OPT_FORCE.equals(operation)) {
behaviour.switchToForce(identifier, index);
}
}
@Override
public Map<ConnectPoint, ProtectedTransportEndpointState> getProtectionSwitchStates(DeviceId deviceId) {
checkNotNull(deviceId);
ProtectionConfigBehaviour behaviour = getProtectionConfig(deviceId);
if (behaviour == null) {
return ImmutableMap.of();
}
return getProtectionSwitchStates(behaviour);
}
@Override
public void setTargetPortPower(DeviceId deviceId, PortNumber portNumber, double 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 Double getTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
// Request target port power when it doesn't exist. Inactive updating mode.
Double power = roadmStore.getTargetPower(deviceId, portNumber);
if (power == null) {
return syncTargetPortPower(deviceId, portNumber);
}
return power;
}
@Override
public Double syncTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Double> pl = powerConfig.getTargetPower(portNumber, Direction.ALL);
if (pl.isPresent()) {
roadmStore.setTargetPower(deviceId, portNumber, pl.get());
return pl.get();
} else {
roadmStore.removeTargetPower(deviceId, portNumber);
}
}
return null;
}
@Override
public void setAttenuation(DeviceId deviceId, PortNumber portNumber,
OchSignal ochSignal, double 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 Double getAttenuation(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
checkNotNull(deviceId);
checkNotNull(portNumber);
checkNotNull(ochSignal);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Double> attenuation = powerConfig.getTargetPower(portNumber, ochSignal);
if (attenuation.isPresent()) {
return attenuation.get();
}
}
return null;
}
@Override
public Double getCurrentPortPower(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Double> currentPower = powerConfig.currentPower(portNumber, Direction.ALL);
if (currentPower.isPresent()) {
return currentPower.get();
}
}
return null;
}
@Override
public Double getCurrentChannelPower(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
checkNotNull(deviceId);
checkNotNull(portNumber);
checkNotNull(ochSignal);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Double> 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 ModulationScheme getModulation(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
Device device = deviceService.getDevice(deviceId);
Direction component = Direction.ALL;
if (device.is(ModulationConfig.class)) {
ModulationConfig<Object> modulationConfig = device.as(ModulationConfig.class);
Optional<ModulationScheme> scheme = modulationConfig.getModulationScheme(portNumber, component);
if (scheme.isPresent()) {
return scheme.get();
}
}
return null;
}
@Override
public void setModulation(DeviceId deviceId, PortNumber portNumber, String modulation) {
checkNotNull(deviceId);
checkNotNull(portNumber);
Device device = deviceService.getDevice(deviceId);
Direction component = Direction.ALL;
if (device.is(ModulationConfig.class)) {
ModulationConfig<Object> modulationConfig = device.as(ModulationConfig.class);
long bitRate = 0;
if (modulation.equals(ModulationScheme.DP_QPSK.name())) {
bitRate = 100;
} else {
bitRate = 200;
}
modulationConfig.setModulationScheme(portNumber, component, bitRate);
}
}
@Override
public FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
int timeout, PortNumber inPort, PortNumber outPort, OchSignal ochSignal) {
checkNotNull(deviceId);
checkNotNull(inPort);
checkNotNull(outPort);
TrafficSelector selector = DefaultTrafficSelector.builder()
.add(Criteria.matchInPort(inPort))
.add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
.add(Criteria.matchLambda(ochSignal))
.build();
TrafficTreatment treatment = DefaultTrafficTreatment.builder()
.add(Instructions.modL0Lambda(ochSignal))
.add(Instructions.createOutput(outPort))
.build();
FlowRule.Builder flowBuilder = DefaultFlowRule.builder()
.forDevice(deviceId)
.fromApp(appId)
.withPriority(priority)
.withSelector(selector)
.withTreatment(treatment);
if (isPermanent) {
flowBuilder.makePermanent();
} else {
flowBuilder.makeTemporary(timeout);
}
FlowRule flowRule = flowBuilder.build();
flowRuleService.applyFlowRules(flowRule);
log.info("Created connection from input port {} to output port {}",
inPort.toLong(), outPort.toLong());
return flowRule.id();
}
@Override
public FlowId createConnection(DeviceId deviceId, int priority, boolean isPermanent,
int timeout, PortNumber inPort, PortNumber outPort,
OchSignal ochSignal, Double attenuation) {
checkNotNull(deviceId);
checkNotNull(inPort);
checkNotNull(outPort);
FlowId flowId = createConnection(deviceId, priority, isPermanent, timeout, inPort, outPort, ochSignal);
delayedSetAttenuation(deviceId, outPort, ochSignal, attenuation);
return flowId;
}
@Override
public void removeConnection(DeviceId deviceId, FlowId flowId) {
checkNotNull(deviceId);
checkNotNull(flowId);
for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
if (entry.id().equals(flowId)) {
flowRuleService.removeFlowRules(entry);
log.info("Deleted connection {}", entry.id());
break;
}
}
}
@Override
public boolean hasPortTargetPower(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Range<Double>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
return range.isPresent();
}
return false;
}
@Override
public boolean portTargetPowerInRange(DeviceId deviceId, PortNumber portNumber, double power) {
checkNotNull(deviceId);
checkNotNull(portNumber);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Range<Double>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
return range.isPresent() && range.get().contains(power);
}
return false;
}
@Override
public boolean attenuationInRange(DeviceId deviceId, PortNumber outPort, double att) {
checkNotNull(deviceId);
checkNotNull(outPort);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
OchSignal stubOch = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 0);
Optional<Range<Double>> 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<Double>> range = powerConfig.getInputPowerRange(portNumber, Direction.ALL);
return range.isPresent();
}
return false;
}
@Override
public boolean validOutputPort(DeviceId deviceId, PortNumber portNumber) {
return hasPortTargetPower(deviceId, portNumber);
}
@Override
public boolean validChannel(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
checkNotNull(deviceId);
checkNotNull(portNumber);
checkNotNull(ochSignal);
LambdaQuery lambdaQuery = getLambdaQuery(deviceId);
if (lambdaQuery != null) {
Set<OchSignal> channels = lambdaQuery.queryLambdas(portNumber);
return channels.contains(ochSignal);
}
return false;
}
@Override
public boolean channelAvailable(DeviceId deviceId, OchSignal ochSignal) {
checkNotNull(deviceId);
checkNotNull(ochSignal);
for (FlowEntry entry : flowRuleService.getFlowEntries(deviceId)) {
if (ChannelData.fromFlow(entry).ochSignal().equals(ochSignal)) {
return false;
}
}
return true;
}
@Override
public boolean validConnection(DeviceId deviceId, PortNumber inPort, PortNumber outPort) {
checkNotNull(deviceId);
checkNotNull(inPort);
checkNotNull(outPort);
return validInputPort(deviceId, inPort) && validOutputPort(deviceId, outPort);
}
@Override
public Range<Double> targetPortPowerRange(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Range<Double>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
if (range.isPresent()) {
return range.get();
}
}
return null;
}
@Override
public Range<Double> attenuationRange(DeviceId deviceId, PortNumber portNumber, OchSignal ochSignal) {
checkNotNull(deviceId);
checkNotNull(portNumber);
checkNotNull(ochSignal);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Range<Double>> range = powerConfig.getTargetPowerRange(portNumber, ochSignal);
if (range.isPresent()) {
return range.get();
}
}
return null;
}
@Override
public Range<Double> inputPortPowerRange(DeviceId deviceId, PortNumber portNumber) {
checkNotNull(deviceId);
checkNotNull(portNumber);
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig != null) {
Optional<Range<Double>> range = powerConfig.getInputPowerRange(portNumber, Direction.ALL);
if (range.isPresent()) {
return range.get();
}
}
return null;
}
private PowerConfig<Object> getPowerConfig(DeviceId deviceId) {
Device device = deviceService.getDevice(deviceId);
if (device != null && device.is(PowerConfig.class)) {
return device.as(PowerConfig.class);
}
// Do not need warning here for port polling.
log.debug("Unable to load PowerConfig for {}", deviceId);
return null;
}
private LambdaQuery getLambdaQuery(DeviceId deviceId) {
Device device = deviceService.getDevice(deviceId);
if (device != null && device.is(LambdaQuery.class)) {
return device.as(LambdaQuery.class);
}
// Do not need warning here for port polling.
log.debug("Unable to load LambdaQuery for {}", deviceId);
return null;
}
private ProtectionConfigBehaviour getProtectionConfig(DeviceId deviceId) {
Device device = deviceService.getDevice(deviceId);
if (device != null && device.is(ProtectionConfigBehaviour.class)) {
return device.as(ProtectionConfigBehaviour.class);
}
// Do not need warning here for port polling.
log.debug("Unable to load ProtectionConfigBehaviour for {}", deviceId);
return null;
}
// Initialize all devices
private void initDevices() {
for (Device device : deviceService.getDevices(Device.Type.ROADM)) {
initDevice(device.id());
//FIXME
// As roadm application is a optional tool for now.
// The target power initialization will be enhanced later,
// hopefully using an formal optical subsystem.
// setAllInitialTargetPortPowers(device.id());
}
}
// Initialize RoadmStore for a device to support target power
private void initDevice(DeviceId deviceId) {
if (!roadmStore.deviceAvailable(deviceId)) {
roadmStore.addDevice(deviceId);
}
log.info("Initialized device {}", deviceId);
}
// Sets the target port powers for a port on a device
// Attempts to read target powers from store. If no value is found then
// default value is used instead.
private void setInitialTargetPortPower(DeviceId deviceId, PortNumber portNumber) {
PowerConfig<Object> powerConfig = getPowerConfig(deviceId);
if (powerConfig == null) {
log.warn("Unable to set default initial powers for port {} on device {}", portNumber, deviceId);
return;
}
Optional<Range<Double>> range = powerConfig.getTargetPowerRange(portNumber, Direction.ALL);
if (!range.isPresent()) {
log.warn("No target power range found for port {} on device {}", portNumber, deviceId);
return;
}
Double 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<Double>> range = powerConfig.getTargetPowerRange(port.number(), Direction.ALL);
if (range.isPresent()) {
Double power = roadmStore.getTargetPower(deviceId, port.number());
if (power == null) {
// Set default to middle of the range
power = (range.get().lowerEndpoint() + range.get().upperEndpoint()) / 2;
roadmStore.setTargetPower(deviceId, port.number(), power);
}
powerConfig.setTargetPower(port.number(), Direction.ALL, power);
} else {
log.warn("No target power range found for port {} on device {}", port.number(), deviceId);
}
}
}
// Delay the call to setTargetPower because the flow may not be in the store yet
// Tested with Lumentum ROADM-20 1 seconds was not enough, increased to 5 seconds
private void delayedSetAttenuation(DeviceId deviceId, PortNumber outPort,
OchSignal ochSignal, Double attenuation) {
Runnable setAtt = () -> {
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
log.warn("Thread interrupted. Setting attenuation early.");
Thread.currentThread().interrupt();
}
setAttenuation(deviceId, outPort, ochSignal, attenuation);
};
new Thread(setAtt).start();
}
// get protection endpoint states
private Map<ConnectPoint, ProtectedTransportEndpointState> getProtectionSwitchStates(
ProtectionConfigBehaviour behaviour) {
Map<ConnectPoint, ProtectedTransportEndpointState> map;
try {
map = behaviour.getProtectionEndpointStates().get();
} catch (InterruptedException e1) {
log.error("Interrupted.", e1);
Thread.currentThread().interrupt();
return ImmutableMap.of();
} catch (ExecutionException e1) {
log.error("Exception caught.", e1);
return ImmutableMap.of();
}
return map;
}
// Listens to device events.
private class InternalDeviceListener implements DeviceListener {
@Override
public void event(DeviceEvent deviceEvent) {
Device device = deviceEvent.subject();
switch (deviceEvent.type()) {
case DEVICE_ADDED:
case DEVICE_UPDATED:
initDevice(device.id());
break;
case PORT_ADDED:
case PORT_UPDATED:
//FIXME
// As roadm application is a optional tool for now.
// The target power initialization will be enhanced later,
// hopefully using an formal optical subsystem.
// setInitialTargetPortPower(device.id(), deviceEvent.port().number());
break;
default:
break;
}
}
}
}