/* * 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.driver.optical.power; import java.util.concurrent.atomic.AtomicInteger; import java.util.List; import com.google.common.collect.Range; import org.onosproject.driver.extensions.OplinkAttenuation; import org.onosproject.net.OchSignal; import org.onosproject.net.Direction; import org.onosproject.net.Port; import org.onosproject.net.PortNumber; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.DriverHandler; import org.onosproject.net.driver.HandlerBehaviour; import org.onosproject.net.flow.DefaultFlowRule; import org.onosproject.net.flow.DefaultTrafficTreatment; import org.onosproject.net.flow.FlowEntry; 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.Criterion; import org.onosproject.net.flow.criteria.OchSignalCriterion; import org.onosproject.net.flow.criteria.PortCriterion; import org.onosproject.net.flow.instructions.ExtensionTreatment; import org.onosproject.net.flow.instructions.ExtensionTreatmentType; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.flow.instructions.Instructions; import org.onosproject.net.optical.OpticalAnnotations; import org.onosproject.openflow.controller.Dpid; import org.onosproject.openflow.controller.OpenFlowController; import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; import org.onosproject.openflow.controller.OpenFlowSwitch; import org.onosproject.openflow.controller.PortDescPropertyType; import org.projectfloodlight.openflow.protocol.OFObject; import org.projectfloodlight.openflow.protocol.OFPortOptical; import org.slf4j.Logger; import static org.slf4j.LoggerFactory.getLogger; import static org.onosproject.net.Device.Type; /** * Oplink power config utility. */ public class OplinkPowerConfigUtil { // Parent driver handler behaviour private HandlerBehaviour behaviour; // Transaction id to use. private final AtomicInteger xidCounter = new AtomicInteger(0); // Log private final Logger log = getLogger(getClass()); // Component type private enum ComponentType { NONE, PORT, CHANNEL } // Port properties for oplink devices, currently supports EDFA and ROADM. // This type is mapped to OFPortDescPropOpticalTransport#getPortType() value. private enum PortDescType { NONE, PA_LINE_IN, PA_LINE_OUT, BA_LINE_IN, BA_LINE_OUT, EXP_IN, EXP_OUT, AUX_IN, AUX_OUT, } /** * Power threshold of each port, in 0.01 dB * Note: * These threshold configurations are just in use for a short time. * In the future, the power threshold would be obtained from physical device. */ // EDFA private static final long EDFA_POWER_IN_WEST_LOW_THRES = -1900L; private static final long EDFA_POWER_IN_WEST_HIGH_THRES = 0L; private static final long EDFA_POWER_IN_EAST_LOW_THRES = -3100L; private static final long EDFA_POWER_IN_EAST_HIGH_THRES = 700L; private static final long EDFA_POWER_OUT_LOW_THRES = 0L; private static final long EDFA_POWER_OUT_HIGH_THRES = 1900L; // ROADM private static final long ROADM_POWER_LINE_IN_LOW_THRES = -3000L; private static final long ROADM_POWER_LINE_IN_HIGH_THRES = 2350L; private static final long ROADM_POWER_LINE_OUT_LOW_THRES = 0L; private static final long ROADM_POWER_LINE_OUT_HIGH_THRES = 2350L; private static final long ROADM_POWER_OTHER_IN_LOW_THRES = -1500L; private static final long ROADM_POWER_OTHER_IN_HIGH_THRES = 2000L; private static final long ROADM_POWER_OTHER_OUT_LOW_THRES = -600L; private static final long ROADM_POWER_OTHER_OUT_HIGH_THRES = 1500L; private static final long ROADM_MIN_ATTENUATION = 0L; private static final long ROADM_MAX_ATTENUATION = 2500L; // SWITCH private static final long SWITCH_POWER_LOW_THRES = -6000L; private static final long SWITCH_POWER_HIGH_THRES = 6000L; /** * Create a new OplinkPowerConfigUtil. * @param behaviour driver handler behaviour */ public OplinkPowerConfigUtil(HandlerBehaviour behaviour) { this.behaviour = behaviour; } /** * Obtains specified port/channel target power. * * @param port the port number * @param component the port component * @return target power value in .01 dBm */ public Long getTargetPower(PortNumber port, Object component) { switch (getComponentType(component)) { case PORT: return getPortPower(port, OpticalAnnotations.TARGET_POWER); case CHANNEL: return getChannelAttenuation(port, (OchSignal) component); default: return null; } } /** * Obtains specified port/channel current power. * * @param port the port number * @param component the port component * @return current power value in .01 dBm */ public Long getCurrentPower(PortNumber port, Object component) { switch (getComponentType(component)) { case PORT: return getPortPower(port, OpticalAnnotations.CURRENT_POWER); case CHANNEL: return getCurrentChannelPower(port, (OchSignal) component); default: return null; } } /** * Sets specified port target power or channel attenuation. * * @param port the port number * @param component the port component * @param power target power in .01 dBm */ public void setTargetPower(PortNumber port, Object component, long power) { switch (getComponentType(component)) { case PORT: setPortPower(port, power); break; case CHANNEL: setChannelAttenuation(port, (OchSignal) component, power); break; default: break; } } /** * Returns the acceptable target range for an output port/channel, null otherwise. * * @param port the port number * @param component the port component * @return power range */ public Range<Long> getTargetPowerRange(PortNumber port, Object component) { switch (getComponentType(component)) { case PORT: return getTargetPortPowerRange(port); case CHANNEL: return getChannelAttenuationRange(port); default: return null; } } /** * Returns the working input power range for an input port, null otherwise. * * @param port the port number * @param component the port component * @return power range */ public Range<Long> getInputPowerRange(PortNumber port, Object component) { switch (getComponentType(component)) { case PORT: return getInputPortPowerRange(port); default: return null; } } /** * Returns specified component type. * * @param component the port component * @return component type */ private ComponentType getComponentType(Object component) { if (component == null || component instanceof Direction) { return ComponentType.PORT; } else if (component instanceof OchSignal) { return ComponentType.CHANNEL; } return ComponentType.NONE; } /** * Returns current switch known to this OF controller. * * @return current switch */ private OpenFlowSwitch getOpenFlowDevice() { final DriverHandler handler = behaviour.handler(); final OpenFlowController controller = handler.get(OpenFlowController.class); final Dpid dpid = Dpid.dpid(handler.data().deviceId().uri()); OpenFlowSwitch sw = controller.getSwitch(dpid); if (sw == null || !sw.isConnected()) { log.warn("OpenFlow handshaker driver not found or device is not connected, dpid = {}", dpid); return null; } return sw; } /** * Find oplink port description type from optical ports. * * @param opsw switch * @param portNum the port number * @return port oplink port description type */ private PortDescType getPortDescType(OpenFlowOpticalSwitch opsw, PortNumber portNum) { for (PortDescPropertyType type : opsw.getPortTypes()) { List<? extends OFObject> portsOf = opsw.getPortsOf(type); for (OFObject op : portsOf) { if (op instanceof OFPortOptical) { OFPortOptical opticalPort = (OFPortOptical) op; if ((long) opticalPort.getPortNo().getPortNumber() == portNum.toLong()) { return PortDescType.values()[opticalPort.getDesc().get(0).getPortType()]; } } } } return PortDescType.NONE; } /** * Returns the target port power range. * * @param portNum the port number * @return power range */ private Range<Long> getTargetPortPowerRange(PortNumber portNum) { OpenFlowSwitch ofs = getOpenFlowDevice(); if (ofs == null) { return null; } PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum); Type devType = ofs.deviceType(); // FIXME // Short time hard code. // The power range will be obtained from physical device in the future. switch (devType) { case OPTICAL_AMPLIFIER: if (portType == PortDescType.PA_LINE_OUT || portType == PortDescType.BA_LINE_OUT) { return Range.closed(EDFA_POWER_OUT_LOW_THRES, EDFA_POWER_OUT_HIGH_THRES); } break; case ROADM: if (portType == PortDescType.PA_LINE_OUT) { return Range.closed(ROADM_POWER_LINE_OUT_LOW_THRES, ROADM_POWER_LINE_OUT_HIGH_THRES); } else if (portType == PortDescType.EXP_OUT || portType == PortDescType.AUX_OUT) { return Range.closed(ROADM_POWER_OTHER_OUT_LOW_THRES, ROADM_POWER_OTHER_OUT_HIGH_THRES); } break; case FIBER_SWITCH: return Range.closed(SWITCH_POWER_LOW_THRES, SWITCH_POWER_HIGH_THRES); default: log.warn("Unexpected device type: {}", devType); break; } // Unexpected port or device type. Do not need warning here for port polling. return null; } /** * Returns the input port power range. * * @param portNum the port number * @return power range */ private Range<Long> getInputPortPowerRange(PortNumber portNum) { OpenFlowSwitch ofs = getOpenFlowDevice(); if (ofs == null) { return null; } PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum); Type devType = ofs.deviceType(); // FIXME // Short time hard code. // The port type and power range will be obtained from physical device in the future. switch (devType) { case OPTICAL_AMPLIFIER: if (portType == PortDescType.PA_LINE_IN) { return Range.closed(EDFA_POWER_IN_WEST_LOW_THRES, EDFA_POWER_IN_WEST_HIGH_THRES); } else if (portType == PortDescType.BA_LINE_IN) { return Range.closed(EDFA_POWER_IN_EAST_LOW_THRES, EDFA_POWER_IN_EAST_HIGH_THRES); } break; case ROADM: if (portType == PortDescType.PA_LINE_IN) { return Range.closed(ROADM_POWER_LINE_IN_LOW_THRES, ROADM_POWER_LINE_IN_HIGH_THRES); } else if (portType == PortDescType.EXP_IN || portType == PortDescType.AUX_IN) { return Range.closed(ROADM_POWER_OTHER_IN_LOW_THRES, ROADM_POWER_OTHER_IN_HIGH_THRES); } break; case FIBER_SWITCH: return Range.closed(SWITCH_POWER_LOW_THRES, SWITCH_POWER_HIGH_THRES); default: log.warn("Unexpected device type: {}", devType); break; } // Unexpected port or device type. Do not need warning here for port polling. return null; } /** * Returns the acceptable attenuation range for a connection (represented as * a flow with attenuation instruction). Port can be either the input or * output port of the connection. Returns null if the connection does not * support attenuation. * * @param portNum the port number * @return attenuation range */ private Range<Long> getChannelAttenuationRange(PortNumber portNum) { OpenFlowSwitch ofs = getOpenFlowDevice(); if (ofs == null) { return null; } if (ofs.deviceType() != Type.ROADM) { return null; } PortDescType portType = getPortDescType((OpenFlowOpticalSwitch) ofs, portNum); // Short time hard code. // The port type and attenuation range will be obtained from physical device in the future. if (portType == PortDescType.PA_LINE_OUT || portType == PortDescType.EXP_IN || portType == PortDescType.AUX_IN) { return Range.closed(ROADM_MIN_ATTENUATION, ROADM_MAX_ATTENUATION); } // Unexpected port. Do not need warning here for port polling. return null; } /** * Find specified port power from port description. * * @param portNum the port number * @param annotation annotation in port description * @return power value in 0.01 dBm */ private Long getPortPower(PortNumber portNum, String annotation) { // Check if switch is connected, otherwise do not return value in store, which is obsolete. if (getOpenFlowDevice() == null) { // Warning already exists in method getOpenFlowDevice() return null; } final DriverHandler handler = behaviour.handler(); DeviceService deviceService = handler.get(DeviceService.class); Port port = deviceService.getPort(handler.data().deviceId(), portNum); if (port == null) { log.warn("Unexpected port: {}", portNum); return null; } String power = port.annotations().value(annotation); if (power == null) { // Do not need warning here for port polling. log.debug("Cannot get {} from port {}.", annotation, portNum); return null; } return Long.valueOf(power); } /** * Sets specified port power value. * * @param portNum the port number * @param power power value */ private void setPortPower(PortNumber portNum, long power) { OpenFlowSwitch device = getOpenFlowDevice(); // Check if switch is connected if (device == null) { return; } device.sendMsg(device.factory().buildOplinkPortPowerSet() .setXid(xidCounter.getAndIncrement()) .setPort((int) portNum.toLong()) .setPowerValue((int) power) .build()); } /** * Gets specified channel attenuation. * * @param portNum the port number * @param och channel signal * @return atteuation in 0.01 dB */ private Long getChannelAttenuation(PortNumber portNum, OchSignal och) { FlowEntry flowEntry = findFlow(portNum, och); if (flowEntry == null) { return null; } List<Instruction> instructions = flowEntry.treatment().allInstructions(); for (Instruction ins : instructions) { if (ins.type() != Instruction.Type.EXTENSION) { continue; } ExtensionTreatment ext = ((Instructions.ExtensionInstructionWrapper) ins).extensionInstruction(); if (ext.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.OPLINK_ATTENUATION.type()) { return (long) ((OplinkAttenuation) ext).getAttenuation(); } } return null; } /** * Sets specified channle attenuation. * * @param portNum the port number * @param och channel signal * @param power attenuation in 0.01 dB */ private void setChannelAttenuation(PortNumber portNum, OchSignal och, long power) { FlowEntry flowEntry = findFlow(portNum, och); if (flowEntry == null) { log.warn("Target channel power not set"); return; } final DriverHandler handler = behaviour.handler(); for (Instruction ins : flowEntry.treatment().allInstructions()) { if (ins.type() != Instruction.Type.EXTENSION) { continue; } ExtensionTreatment ext = ((Instructions.ExtensionInstructionWrapper) ins).extensionInstruction(); if (ext.type() == ExtensionTreatmentType.ExtensionTreatmentTypes.OPLINK_ATTENUATION.type()) { ((OplinkAttenuation) ext).setAttenuation((int) power); FlowRuleService service = handler.get(FlowRuleService.class); service.applyFlowRules(flowEntry); return; } } addAttenuation(flowEntry, power); } /** * Gets specified channle current power. * * @param portNum the port number * @param och channel signal * @return power value in 0.01 dBm */ private Long getCurrentChannelPower(PortNumber portNum, OchSignal och) { FlowEntry flowEntry = findFlow(portNum, och); if (flowEntry != null) { // TODO put somewhere else if possible // We put channel power in packets return flowEntry.packets(); } return null; } /** * Find matching flow on device. * * @param portNum the port number * @param och channel signal * @return flow entry */ private FlowEntry findFlow(PortNumber portNum, OchSignal och) { final DriverHandler handler = behaviour.handler(); FlowRuleService service = handler.get(FlowRuleService.class); Iterable<FlowEntry> flowEntries = service.getFlowEntries(handler.data().deviceId()); // Return first matching flow for (FlowEntry entry : flowEntries) { 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"); return null; } /** * Replace flow with new flow containing Oplink attenuation extension instruction. Also resets metrics. * * @param flowEntry flow entry * @param power power value */ private void addAttenuation(FlowEntry flowEntry, long power) { FlowRule.Builder flowBuilder = new DefaultFlowRule.Builder() .withCookie(flowEntry.id().value()) .withPriority(flowEntry.priority()) .forDevice(flowEntry.deviceId()) .forTable(flowEntry.tableId()); if (flowEntry.isPermanent()) { flowBuilder.makePermanent(); } else { flowBuilder.makeTemporary(flowEntry.timeout()); } flowBuilder.withSelector(flowEntry.selector()); // Copy original instructions and add attenuation instruction TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment.builder(); flowEntry.treatment().allInstructions().forEach(ins -> treatmentBuilder.add(ins)); final DriverHandler handler = behaviour.handler(); treatmentBuilder.add(Instructions.extension(new OplinkAttenuation((int) power), handler.data().deviceId())); flowBuilder.withTreatment(treatmentBuilder.build()); FlowRuleService service = handler.get(FlowRuleService.class); service.applyFlowRules(flowBuilder.build()); } }