/* * 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.drivers.lumentum; import com.google.common.collect.Lists; import org.apache.commons.lang3.tuple.Pair; import org.onosproject.driver.optical.flowrule.CrossConnectCache; import org.onosproject.driver.optical.flowrule.CrossConnectFlowRule; import org.onosproject.net.ChannelSpacing; import org.onosproject.net.GridType; import org.onosproject.net.OchSignal; import org.onosproject.net.OchSignalType; import org.onosproject.net.Port; import org.onosproject.net.PortNumber; import org.onosproject.net.device.DeviceService; import org.onosproject.net.driver.AbstractHandlerBehaviour; import org.onosproject.net.flow.DefaultFlowEntry; 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.FlowRuleProgrammable; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.snmp4j.PDU; import org.snmp4j.event.ResponseEvent; import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.UnsignedInteger32; import org.snmp4j.smi.VariableBinding; import org.snmp4j.util.TreeEvent; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; import static com.google.common.base.Preconditions.checkArgument; // TODO: need to convert between OChSignal and XC channel number public class LumentumSdnRoadmFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable { private static final Logger log = LoggerFactory.getLogger(LumentumSdnRoadmFlowRuleProgrammable.class); // Default values private static final int DEFAULT_TARGET_GAIN_PREAMP = 150; private static final int DEFAULT_TARGET_GAIN_BOOSTER = 200; private static final int DISABLE_CHANNEL_TARGET_POWER = -650; private static final int DEFAULT_CHANNEL_TARGET_POWER = -30; private static final int DISABLE_CHANNEL_ABSOLUTE_ATTENUATION = 160; private static final int DEFAULT_CHANNEL_ABSOLUTE_ATTENUATION = 50; private static final int DISABLE_CHANNEL_ADD_DROP_PORT_INDEX = 1; private static final int OUT_OF_SERVICE = 1; private static final int IN_SERVICE = 2; private static final int OPEN_LOOP = 1; private static final int CLOSED_LOOP = 2; // First 20 ports are add/mux ports, next 20 are drop/demux private static final int DROP_PORT_OFFSET = 20; // OIDs private static final String CTRL_AMP_MODULE_SERVICE_STATE_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.2.1"; private static final String CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER = ".1.3.6.1.4.1.46184.1.4.4.1.2.2"; private static final String CTRL_AMP_MODULE_TARGET_GAIN_PREAMP = ".1.3.6.1.4.1.46184.1.4.4.1.8.1"; private static final String CTRL_AMP_MODULE_TARGET_GAIN_BOOSTER = ".1.3.6.1.4.1.46184.1.4.4.1.8.2"; private static final String CTRL_CHANNEL_STATE = ".1.3.6.1.4.1.46184.1.4.2.1.3."; private static final String CTRL_CHANNEL_MODE = ".1.3.6.1.4.1.46184.1.4.2.1.4."; private static final String CTRL_CHANNEL_TARGET_POWER = ".1.3.6.1.4.1.46184.1.4.2.1.6."; private static final String CTRL_CHANNEL_ADD_DROP_PORT_INDEX = ".1.3.6.1.4.1.46184.1.4.2.1.13."; private static final String CTRL_CHANNEL_ABSOLUTE_ATTENUATION = ".1.3.6.1.4.1.46184.1.4.2.1.5."; private LumentumSnmpDevice snmp; @Override public Collection<FlowEntry> getFlowEntries() { try { snmp = new LumentumSnmpDevice(handler().data().deviceId()); } catch (IOException e) { log.error("Failed to connect to device: ", e); return Collections.emptyList(); } // Line in is last but one port, line out is last DeviceService deviceService = this.handler().get(DeviceService.class); List<Port> ports = deviceService.getPorts(data().deviceId()); if (ports.size() < 2) { return Collections.emptyList(); } PortNumber lineIn = ports.get(ports.size() - 2).number(); PortNumber lineOut = ports.get(ports.size() - 1).number(); Collection<FlowEntry> entries = Lists.newLinkedList(); // Add rules OID addOid = new OID(CTRL_CHANNEL_STATE + "1"); entries.addAll( fetchRules(addOid, true, lineOut).stream() .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0)) .collect(Collectors.toList()) ); // Drop rules OID dropOid = new OID(CTRL_CHANNEL_STATE + "2"); entries.addAll( fetchRules(dropOid, false, lineIn).stream() .map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0)) .collect(Collectors.toList()) ); return entries; } @Override public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) { try { snmp = new LumentumSnmpDevice(data().deviceId()); } catch (IOException e) { log.error("Failed to connect to device: ", e); } // Line ports DeviceService deviceService = this.handler().get(DeviceService.class); List<Port> ports = deviceService.getPorts(data().deviceId()); List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream() .map(p -> p.number()) .collect(Collectors.toList()); // Apply the valid rules on the device Collection<FlowRule> added = rules.stream() .map(r -> new CrossConnectFlowRule(r, linePorts)) .filter(xc -> installCrossConnect(xc)) .collect(Collectors.toList()); // Cache the cookie/priority CrossConnectCache cache = this.handler().get(CrossConnectCache.class); added.forEach(xc -> cache.set( Objects.hash(data().deviceId(), xc.selector(), xc.treatment()), xc.id(), xc.priority())); return added; } @Override public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) { try { snmp = new LumentumSnmpDevice(data().deviceId()); } catch (IOException e) { log.error("Failed to connect to device: ", e); } // Line ports DeviceService deviceService = this.handler().get(DeviceService.class); List<Port> ports = deviceService.getPorts(data().deviceId()); List<PortNumber> linePorts = ports.subList(ports.size() - 2, ports.size()).stream() .map(p -> p.number()) .collect(Collectors.toList()); // Apply the valid rules on the device Collection<FlowRule> removed = rules.stream() .map(r -> new CrossConnectFlowRule(r, linePorts)) .filter(xc -> removeCrossConnect(xc)) .collect(Collectors.toList()); // Remove flow rule from cache CrossConnectCache cache = this.handler().get(CrossConnectCache.class); removed.forEach(xc -> cache.remove( Objects.hash(data().deviceId(), xc.selector(), xc.treatment()))); return removed; } // Installs cross connect on device private boolean installCrossConnect(CrossConnectFlowRule xc) { int channel = toChannel(xc.ochSignal()); long addDrop = xc.addDrop().toLong(); if (!xc.isAddRule()) { addDrop -= DROP_PORT_OFFSET; } // Create the PDU object PDU pdu = new PDU(); pdu.setType(PDU.SET); // Enable preamp & booster List<OID> oids = Arrays.asList(new OID(CTRL_AMP_MODULE_SERVICE_STATE_PREAMP), new OID(CTRL_AMP_MODULE_SERVICE_STATE_BOOSTER)); oids.forEach( oid -> pdu.add(new VariableBinding(oid, new Integer32(IN_SERVICE))) ); // Set target gain on preamp & booster OID ctrlAmpModuleTargetGainPreamp = new OID(CTRL_AMP_MODULE_TARGET_GAIN_PREAMP); pdu.add(new VariableBinding(ctrlAmpModuleTargetGainPreamp, new Integer32(DEFAULT_TARGET_GAIN_PREAMP))); OID ctrlAmpModuleTargetGainBooster = new OID(CTRL_AMP_MODULE_TARGET_GAIN_BOOSTER); pdu.add(new VariableBinding(ctrlAmpModuleTargetGainBooster, new Integer32(DEFAULT_TARGET_GAIN_BOOSTER))); // Make cross connect OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (xc.isAddRule() ? "1." : "2.") + channel); pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(addDrop))); // Add rules use closed loop, drop rules open loop // Add rules are set to target power, drop rules are attenuated if (xc.isAddRule()) { OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + "1." + channel); pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(CLOSED_LOOP))); OID ctrlChannelTargetPower = new OID(CTRL_CHANNEL_TARGET_POWER + "1." + channel); pdu.add(new VariableBinding(ctrlChannelTargetPower, new Integer32(DEFAULT_CHANNEL_TARGET_POWER))); } else { OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + "2." + channel); pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(OPEN_LOOP))); OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION + "2." + channel); pdu.add(new VariableBinding( ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DEFAULT_CHANNEL_ABSOLUTE_ATTENUATION))); } // Final step is to enable the channel OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel); pdu.add(new VariableBinding(ctrlChannelState, new Integer32(IN_SERVICE))); try { ResponseEvent response = snmp.set(pdu); // TODO: parse response } catch (IOException e) { log.error("Failed to create cross connect, unable to connect to device: ", e); } return true; } // Removes cross connect on device private boolean removeCrossConnect(CrossConnectFlowRule xc) { int channel = toChannel(xc.ochSignal()); // Create the PDU object PDU pdu = new PDU(); pdu.setType(PDU.SET); // Disable the channel OID ctrlChannelState = new OID(CTRL_CHANNEL_STATE + (xc.isAddRule() ? "1." : "2.") + channel); pdu.add(new VariableBinding(ctrlChannelState, new Integer32(OUT_OF_SERVICE))); // Put cross connect back into default port 1 OID ctrlChannelAddDropPortIndex = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (xc.isAddRule() ? "1." : "2.") + channel); pdu.add(new VariableBinding(ctrlChannelAddDropPortIndex, new UnsignedInteger32(DISABLE_CHANNEL_ADD_DROP_PORT_INDEX))); // Put port/channel back to open loop OID ctrlChannelMode = new OID(CTRL_CHANNEL_MODE + (xc.isAddRule() ? "1." : "2.") + channel); pdu.add(new VariableBinding(ctrlChannelMode, new Integer32(OPEN_LOOP))); // Add rules are set to target power, drop rules are attenuated if (xc.isAddRule()) { OID ctrlChannelTargetPower = new OID(CTRL_CHANNEL_TARGET_POWER + "1." + channel); pdu.add(new VariableBinding(ctrlChannelTargetPower, new Integer32(DISABLE_CHANNEL_TARGET_POWER))); } else { OID ctrlChannelAbsoluteAttenuation = new OID(CTRL_CHANNEL_ABSOLUTE_ATTENUATION + "2." + channel); pdu.add(new VariableBinding( ctrlChannelAbsoluteAttenuation, new UnsignedInteger32(DISABLE_CHANNEL_ABSOLUTE_ATTENUATION))); } try { ResponseEvent response = snmp.set(pdu); // TODO: parse response } catch (IOException e) { log.error("Failed to remove cross connect, unable to connect to device: ", e); return false; } return true; } /** * Convert OCh signal to Lumentum channel ID. * * @param ochSignal OCh signal * @return Lumentum channel ID */ public static int toChannel(OchSignal ochSignal) { // FIXME: move to cross connect validation checkArgument(ochSignal.channelSpacing() == ChannelSpacing.CHL_50GHZ); checkArgument(LumentumSnmpDevice.START_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) <= 0); checkArgument(LumentumSnmpDevice.END_CENTER_FREQ.compareTo(ochSignal.centralFrequency()) >= 0); return ochSignal.spacingMultiplier() + LumentumSnmpDevice.MULTIPLIER_SHIFT; } /** * Convert Lumentum channel ID to OCh signal. * * @param channel Lumentum channel ID * @return OCh signal */ public static OchSignal toOchSignal(int channel) { checkArgument(1 <= channel); checkArgument(channel <= 96); return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, channel - LumentumSnmpDevice.MULTIPLIER_SHIFT, 4); } // Returns the currently configured add/drop port for the given channel. private PortNumber getAddDropPort(int channel, boolean isAddPort) { OID oid = new OID(CTRL_CHANNEL_ADD_DROP_PORT_INDEX + (isAddPort ? "1" : "2")); for (TreeEvent event : snmp.get(oid)) { if (event == null) { return null; } VariableBinding[] varBindings = event.getVariableBindings(); for (VariableBinding varBinding : varBindings) { if (varBinding.getOid().last() == channel) { int port = varBinding.getVariable().toInt(); if (!isAddPort) { port += DROP_PORT_OFFSET; } return PortNumber.portNumber(port); } } } return null; } // Returns the currently installed flow entries on the device. private List<FlowRule> fetchRules(OID oid, boolean isAdd, PortNumber linePort) { List<FlowRule> rules = new LinkedList<>(); for (TreeEvent event : snmp.get(oid)) { if (event == null) { continue; } VariableBinding[] varBindings = event.getVariableBindings(); for (VariableBinding varBinding : varBindings) { CrossConnectCache cache = this.handler().get(CrossConnectCache.class); if (varBinding.getVariable().toInt() == IN_SERVICE) { int channel = varBinding.getOid().removeLast(); PortNumber addDropPort = getAddDropPort(channel, isAdd); if (addDropPort == null) { continue; } TrafficSelector selector = DefaultTrafficSelector.builder() .matchInPort(isAdd ? addDropPort : linePort) .add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID)) .add(Criteria.matchLambda(toOchSignal(channel))) .build(); TrafficTreatment treatment = DefaultTrafficTreatment.builder() .setOutput(isAdd ? linePort : addDropPort) .build(); // Lookup flow ID and priority int hash = Objects.hash(data().deviceId(), selector, treatment); Pair<FlowId, Integer> lookup = cache.get(hash); if (lookup == null) { continue; } FlowRule fr = DefaultFlowRule.builder() .forDevice(data().deviceId()) .makePermanent() .withSelector(selector) .withTreatment(treatment) .withPriority(lookup.getRight()) .withCookie(lookup.getLeft().value()) .build(); rules.add(fr); } } } return rules; } }