blob: 840fc99370449e4867f1c715a184e9ad02dc3100 [file] [log] [blame]
/*
* 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;
}
}