| /* |
| * 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.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 LumentumFlowRuleProgrammable extends AbstractHandlerBehaviour implements FlowRuleProgrammable { |
| |
| private static final Logger log = |
| LoggerFactory.getLogger(LumentumFlowRuleProgrammable.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.stream() |
| .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.stream() |
| .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; |
| } |
| } |