blob: f316fc07dd2271c7036c5d0ab7f6293bedc8374c [file] [log] [blame]
/*
* Copyright 2018-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.
* This work was partially supported by EC H2020 project METRO-HAUL (761727).
*/
package org.onosproject.drivers.odtn.openconfig;
import com.google.common.collect.ImmutableList;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.onlab.util.Frequency;
import org.onlab.util.Spectrum;
import org.onosproject.drivers.odtn.impl.DeviceConnectionCache;
import org.onosproject.drivers.odtn.impl.FlowRuleParser;
import org.onosproject.drivers.utilities.XmlConfigParser;
import org.onosproject.net.PortNumber;
import org.onosproject.net.OchSignal;
import org.onosproject.net.OchSignalType;
import org.onosproject.net.ChannelSpacing;
import org.onosproject.net.GridType;
import org.onosproject.net.DeviceId;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.AbstractHandlerBehaviour;
import org.onosproject.net.flow.FlowRule;
import org.onosproject.net.flow.DefaultFlowRule;
import org.onosproject.net.flow.FlowEntry;
import org.onosproject.net.flow.DefaultFlowEntry;
import org.onosproject.net.flow.TrafficTreatment;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.FlowRuleProgrammable;
import org.onosproject.net.flow.criteria.Criteria;
import org.onosproject.net.flow.instructions.Instructions;
import org.onosproject.netconf.DatastoreId;
import org.onosproject.netconf.NetconfController;
import org.onosproject.netconf.NetconfException;
import org.onosproject.netconf.NetconfSession;
import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import java.io.ByteArrayInputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Implementation of FlowRuleProgrammable interface for
* OpenConfig terminal devices.
*/
public class ClientLineTerminalDeviceFlowRuleProgrammable
extends AbstractHandlerBehaviour implements FlowRuleProgrammable {
private static final Logger log =
LoggerFactory.getLogger(ClientLineTerminalDeviceFlowRuleProgrammable.class);
private static final String RPC_TAG_NETCONF_BASE =
"<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">";
private static final String RPC_CLOSE_TAG = "</rpc>";
private static final String PREFIX_PORT = "port-";
private static final String PREFIX_CHANNEL = "channel-";
private static final String DEFAULT_OPERATIONAL_MODE = "0";
private static final String DEFAULT_TARGET_POWER = "0";
private static final String DEFAULT_ASSIGNMENT_INDEX = "1";
private static final String DEFAULT_ALLOCATION_INDEX = "10";
private static final int DEFAULT_RULE_PRIORITY = 10;
private static final long DEFAULT_RULE_COOKIE = 1234L;
private static final String OPERATION_DISABLE = "DISABLED";
private static final String OPERATION_ENABLE = "ENABLED";
private static final String OC_TYPE_PROT_OTN = "oc-opt-types:PROT_OTN";
private static final String OC_TYPE_PROT_ETH = "oc-opt-types:PROT_ETHERNET";
/**
* Apply the flow entries specified in the collection rules.
*
* @param rules A collection of Flow Rules to be applied
* @return The collection of added Flow Entries
*/
@Override
public Collection<FlowRule> applyFlowRules(Collection<FlowRule> rules) {
NetconfSession session = getNetconfSession();
if (session == null) {
openConfigError("null session");
return ImmutableList.of();
}
// Apply the rules on the device
Collection<FlowRule> added = rules.stream()
.map(r -> new TerminalDeviceFlowRule(r, getLinePorts()))
.filter(xc -> applyFlowRule(session, xc))
.collect(Collectors.toList());
for (FlowRule flowRule : added) {
log.info("OpenConfig added flowrule {}", flowRule);
getConnectionCache().add(did(), ((TerminalDeviceFlowRule) flowRule).connectionName(), flowRule);
}
//Print out number of rules sent to the device (without receiving errors)
openConfigLog("applyFlowRules added {}", added.size());
return added;
}
/**
* Get the flow entries that are present on the device.
*
* @return A collection of Flow Entries
*/
@Override
public Collection<FlowEntry> getFlowEntries() {
Collection<FlowEntry> fetched = fetchConnectionsFromDevice().stream()
.map(fr -> new DefaultFlowEntry(fr, FlowEntry.FlowEntryState.ADDED, 0, 0, 0))
.collect(Collectors.toList());
//Print out number of rules actually found on the device that are also included in the cache
openConfigLog("getFlowEntries fetched connections {}", fetched.size());
return fetched;
}
/**
* Remove the specified flow rules.
*
* @param rules A collection of Flow Rules to be removed
* @return The collection of removed Flow Entries
*/
@Override
public Collection<FlowRule> removeFlowRules(Collection<FlowRule> rules) {
NetconfSession session = getNetconfSession();
if (session == null) {
openConfigError("null session");
return ImmutableList.of();
}
List<FlowRule> removed = new ArrayList<>();
for (FlowRule r : rules) {
try {
TerminalDeviceFlowRule termFlowRule = new TerminalDeviceFlowRule(r, getLinePorts());
removeFlowRule(session, termFlowRule);
getConnectionCache().remove(did(), r);
removed.add(r);
} catch (Exception e) {
openConfigError("Error {}", e);
continue;
}
}
//Print out number of removed rules from the device (without receiving errors)
openConfigLog("removeFlowRules removed {}", removed.size());
return removed;
}
private DeviceConnectionCache getConnectionCache() {
return DeviceConnectionCache.init();
}
// Context so XPath expressions are aware of XML namespaces
private static final NamespaceContext NS_CONTEXT = new NamespaceContext() {
@Override
public String getNamespaceURI(String prefix) {
if (prefix.equals("oc-platform-types")) {
return "http://openconfig.net/yang/platform-types";
}
if (prefix.equals("oc-opt-term")) {
return "http://openconfig.net/yang/terminal-device";
}
return null;
}
@Override
public Iterator getPrefixes(String val) {
return null;
}
@Override
public String getPrefix(String uri) {
return null;
}
};
/**
* Helper method to get the device id.
*/
private DeviceId did() {
return data().deviceId();
}
/**
* Helper method to log from this class adding DeviceId.
*/
private void openConfigLog(String format, Object... arguments) {
log.info("OPENCONFIG {}: " + format, did(), arguments);
}
/**
* Helper method to log an error from this class adding DeviceId.
*/
private void openConfigError(String format, Object... arguments) {
log.error("OPENCONFIG {}: " + format, did(), arguments);
}
/**
* Helper method to get the Netconf Session.
*/
private NetconfSession getNetconfSession() {
NetconfController controller =
checkNotNull(handler().get(NetconfController.class));
return controller.getNetconfDevice(did()).getSession();
}
/**
* Construct a String with a Netconf filtered get RPC Message.
*
* @param filter A valid XML tree with the filter to apply in the get
* @return a String containing the RPC XML Document
*/
private String filteredGetBuilder(String filter) {
StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE);
rpc.append("<get>");
rpc.append("<filter type='subtree'>");
rpc.append(filter);
rpc.append("</filter>");
rpc.append("</get>");
rpc.append(RPC_CLOSE_TAG);
return rpc.toString();
}
/**
* Construct a get request to retrieve Components and their
* properties (for the ONOS port, index).
*
* @return The filt content to send to the device.
*/
private String getComponents() {
StringBuilder filt = new StringBuilder();
filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
filt.append(" <component>");
filt.append(" <name/>");
filt.append(" <properties/>");
filt.append(" </component>");
filt.append("</components>");
return filteredGetBuilder(filt.toString());
}
/**
* Construct a get request to retrieve Optical Channels and
* the line port they are using.
* <p>
* This method is used to query the device so we can find the
* OpticalChannel component name that used a given line port.
*
* @return The filt content to send to the device.
*/
private String getOpticalChannels() {
StringBuilder filt = new StringBuilder();
filt.append("<components xmlns='http://openconfig.net/yang/platform'>");
filt.append(" <component>");
filt.append(" <name/>");
filt.append(" <state/>");
filt.append(" <oc-opt-term:optical-channel xmlns:oc-opt-term"
+ " = 'http://openconfig.net/yang/terminal-device'>");
filt.append(" <oc-opt-term:config>");
filt.append(" <oc-opt-term:line-port/>");
filt.append(" </oc-opt-term:config>");
filt.append(" </oc-opt-term:optical-channel>");
filt.append(" </component>");
filt.append("</components>");
return filteredGetBuilder(filt.toString());
}
/**
* Get the OpenConfig component name for the OpticalChannel component
* associated to the passed port number (typically a line side port, already
* mapped to ONOS port).
*
* @param session The netconf session to the device.
* @param portNumber ONOS port number of the Line port ().
* @return the channel component name or null
*/
private String getOpticalChannel(NetconfSession session,
PortNumber portNumber) {
try {
checkNotNull(session);
checkNotNull(portNumber);
XPath xp = XPathFactory.newInstance().newXPath();
xp.setNamespaceContext(NS_CONTEXT);
// Get the port name for a given port number
// We could iterate the port annotations too, no need to
// interact with device.
String xpGetPortName =
"/rpc-reply/data/components/"
+
"component[./properties/property[name='onos-index']/config/value ='" +
portNumber.toLong() + "']/"
+ "name/text()";
// Get all the components and their properties
String compReply = session.rpc(getComponents()).get();
DocumentBuilderFactory builderFactory =
DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder();
Document document =
builder.parse(new InputSource(new StringReader(compReply)));
String portName = xp.evaluate(xpGetPortName, document);
String xpGetOptChannelName =
"/rpc-reply/data/components/"
+ "component[./optical-channel/config/line-port='" + portName +
"']/name/text()";
String optChannelReply = session.rpc(getOpticalChannels()).get();
document =
builder.parse(new InputSource(new StringReader(optChannelReply)));
return xp.evaluate(xpGetOptChannelName, document);
} catch (Exception e) {
openConfigError("Exception {}", e);
return null;
}
}
private void setLogicalChannel(NetconfSession session, String operation, String logChannel)
throws NetconfException {
StringBuilder sb = new StringBuilder();
sb.append("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'>");
sb.append("<logical-channels>");
sb.append("<channel>");
sb.append("<index>" + logChannel + "</index>");
sb.append("<config>");
sb.append("<admin-state>" + operation + "</admin-state>");
sb.append("</config>");
sb.append("</channel>");
sb.append("</logical-channels>");
sb.append("</terminal-device>");
boolean ok =
session.editConfig(DatastoreId.RUNNING, null, sb.toString());
if (!ok) {
throw new NetconfException("error writing the logical channel");
}
}
private void setOpticalChannelFrequency(NetconfSession session, String optChannel, Frequency freq)
throws NetconfException {
StringBuilder sb = new StringBuilder();
sb.append("<components xmlns='http://openconfig.net/yang/platform'>");
sb.append("<component>");
sb.append("<name>" + PREFIX_CHANNEL + optChannel + "</name>");
sb.append("<oc-opt-term:optical-channel xmlns:oc-opt-term='http://openconfig.net/yang/terminal-device'>");
sb.append("<oc-opt-term:config>");
sb.append("<oc-opt-term:frequency>" + (long) freq.asMHz() + "</oc-opt-term:frequency>");
sb.append("<oc-opt-term:target-output-power>" + DEFAULT_TARGET_POWER + "</oc-opt-term:target-output-power>");
sb.append("<oc-opt-term:operational-mode>" + DEFAULT_OPERATIONAL_MODE + "</oc-opt-term:operational-mode>");
sb.append("<oc-opt-term:line-port>" + PREFIX_PORT + optChannel + "</oc-opt-term:line-port>");
sb.append("</oc-opt-term:config>");
sb.append("</oc-opt-term:optical-channel>");
sb.append("</component>");
sb.append("</components>");
boolean ok =
session.editConfig(DatastoreId.RUNNING, null, sb.toString());
if (!ok) {
throw new NetconfException("error writing channel frequency");
}
}
private void setLogicalChannelAssignment(NetconfSession session, String operation, String client, String line,
String assignmentIndex, String allocationIndex)
throws NetconfException {
StringBuilder sb = new StringBuilder();
sb.append("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'>");
sb.append("<logical-channels>");
sb.append("<channel>");
sb.append("<index>" + client + "</index>");
sb.append("<config>");
sb.append("<admin-state>" + operation + "</admin-state>");
sb.append("</config>");
sb.append("<logical-channel-assignments>");
sb.append("<assignment>");
sb.append("<index>" + assignmentIndex + "</index>");
sb.append("<config>");
sb.append("<logical-channel>" + line + "</logical-channel>");
sb.append("<allocation>" + allocationIndex + "</allocation>");
sb.append("</config>");
sb.append("</assignment>");
sb.append("</logical-channel-assignments>");
sb.append("</channel>");
sb.append("</logical-channels>");
sb.append("</terminal-device>");
boolean ok =
session.editConfig(DatastoreId.RUNNING, null, sb.toString());
if (!ok) {
throw new NetconfException("error writing logical channel assignment");
}
}
/**
* Apply a single flowrule to the device.
*
* --- Directionality details:
* Driver supports ADD (INGRESS) and DROP (EGRESS) rules generated by OpticalCircuit/OpticalConnectivity intents
* the format of the rules are checked in class TerminalDeviceFlowRule
*
* However, the physical transponder is always bidirectional as specified in OpenConfig YANG models
* therefore ADD and DROP rules are mapped in the same xml that ENABLE (and tune) a transponder port.
*
* If the intent is generated as bidirectional both ADD and DROP flowrules are generated for each device, thus
* the same xml is sent twice to the device.
*
* @param session The Netconf session.
* @param rule Flow Rules to be applied.
* @return true if no Netconf errors are received from the device when xml is sent
* @throws NetconfException if exchange goes wrong
*/
protected boolean applyFlowRule(NetconfSession session, TerminalDeviceFlowRule rule) {
//Configuration of LINE side, used for OpticalConnectivity intents
//--- configure central frequency
//--- enable the line port
if (rule.type == TerminalDeviceFlowRule.Type.LINE_INGRESS ||
rule.type == TerminalDeviceFlowRule.Type.LINE_EGRESS) {
FlowRuleParser frp = new FlowRuleParser(rule);
String componentName = frp.getPortNumber().toString();
Frequency centralFrequency = frp.getCentralFrequency();
StringBuilder componentConf = new StringBuilder();
log.info("Sending LINE FlowRule to device {} LINE port {}, frequency {}",
did(), componentName, centralFrequency);
try {
setOpticalChannelFrequency(session, componentName, centralFrequency);
} catch (NetconfException e) {
log.error("Error writing central frequency in the component");
return false;
}
try {
setLogicalChannel(session, OPERATION_ENABLE, componentName);
} catch (NetconfException e) {
log.error("Error enabling the logical channel");
return false;
}
}
//Configuration of CLIENT side, used for OpticalCircuit intents
//--- associate the client port to the line port
//--- enable the client port
//
//Assumes only one "assignment" per logical-channel with index 1
//TODO check the OTN mapping of client ports into the line port frame specified by parameter "<allocation>"
if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS ||
rule.type == TerminalDeviceFlowRule.Type.CLIENT_EGRESS) {
String clientPortName;
String linePortName;
if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS) {
clientPortName = rule.inPort().toString();
linePortName = rule.outPort().toString();
} else {
clientPortName = rule.outPort().toString();
linePortName = rule.inPort().toString();
}
log.info("Sending CLIENT FlowRule to device {} CLIENT port: {}, LINE port {}",
did(), clientPortName, linePortName);
try {
setLogicalChannelAssignment(session, OPERATION_ENABLE, clientPortName, linePortName,
DEFAULT_ASSIGNMENT_INDEX, DEFAULT_ALLOCATION_INDEX);
} catch (NetconfException e) {
log.error("Error setting the logical channel assignment");
return false;
}
}
return true;
}
protected boolean removeFlowRule(NetconfSession session, TerminalDeviceFlowRule rule)
throws NetconfException {
//Configuration of LINE side, used for OpticalConnectivity intents
//--- configure central frequency to ZERO
//--- disable the line port
if (rule.type == TerminalDeviceFlowRule.Type.LINE_INGRESS ||
rule.type == TerminalDeviceFlowRule.Type.LINE_EGRESS) {
FlowRuleParser frp = new FlowRuleParser(rule);
String componentName = frp.getPortNumber().toString();
log.info("Removing LINE FlowRule device {} line port {}",
did(), componentName);
try {
setLogicalChannel(session, OPERATION_DISABLE, componentName);
} catch (NetconfException e) {
log.error("Error disabling the logical channel line side");
return false;
}
}
//Configuration of CLIENT side, used for OpticalCircuit intents
//--- configure central frequency to ZERO
//--- disable the line port
if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS ||
rule.type == TerminalDeviceFlowRule.Type.CLIENT_EGRESS) {
String clientPortName;
String linePortName;
if (rule.type == TerminalDeviceFlowRule.Type.CLIENT_INGRESS) {
clientPortName = rule.inPort().toString();
linePortName = rule.outPort().toString();
} else {
clientPortName = rule.outPort().toString();
linePortName = rule.inPort().toString();
}
log.debug("Removing CLIENT FlowRule device {} client port: {}, line port {}",
did(), clientPortName, linePortName);
try {
setLogicalChannelAssignment(session, OPERATION_DISABLE, clientPortName, linePortName,
DEFAULT_ASSIGNMENT_INDEX, DEFAULT_ALLOCATION_INDEX);
} catch (NetconfException e) {
log.error("Error disabling the logical channel assignment");
return false;
}
}
return true;
}
private List<FlowRule> fetchLineConnectionFromDevice(String channel, Frequency centralFreq) {
List<FlowRule> confirmedRules = new ArrayList<>();
FlowRule cacheAddRule;
FlowRule cacheDropRule;
NetconfSession session = getNetconfSession();
log.debug("fetchOpticalConnectionsFromDevice {} frequency {}", did(), centralFreq);
//Build the corresponding flow rule as expected
//Selector including port and ochSignal
//Treatment including port
PortNumber inputPortNumber = PortNumber.portNumber(channel);
PortNumber outputPortNumber = PortNumber.portNumber(channel);
log.debug("fetchOpticalConnectionsFromDevice {} port {}-{}", did(), inputPortNumber, outputPortNumber);
TrafficSelector selectorDrop = DefaultTrafficSelector.builder()
.matchInPort(inputPortNumber)
.add(Criteria.matchOchSignalType(OchSignalType.FIXED_GRID))
.add(Criteria.matchLambda(toOchSignal(centralFreq, 50.0)))
.build();
TrafficTreatment treatmentDrop = DefaultTrafficTreatment.builder()
.setOutput(outputPortNumber)
.build();
TrafficSelector selectorAdd = DefaultTrafficSelector.builder()
.matchInPort(inputPortNumber)
.build();
TrafficTreatment treatmentAdd = DefaultTrafficTreatment.builder()
.add(Instructions.modL0Lambda(toOchSignal(centralFreq, 50.0)))
.setOutput(outputPortNumber)
.build();
//Retrieved rules and cached rules are considered equal if both selector and treatment are equal
cacheAddRule = null;
cacheDropRule = null;
if (getConnectionCache().size(did()) != 0) {
cacheDropRule = getConnectionCache().get(did()).stream()
.filter(r -> (r.selector().equals(selectorDrop) && r.treatment().equals(treatmentDrop)))
.findFirst()
.orElse(null);
cacheAddRule = getConnectionCache().get(did()).stream()
.filter(r -> (r.selector().equals(selectorAdd) && r.treatment().equals(treatmentAdd)))
.findFirst()
.orElse(null);
}
//Include the DROP rule to the retrieved rules if found in cache
if ((cacheDropRule != null)) {
confirmedRules.add(cacheDropRule);
log.debug("fetchOpticalConnectionsFromDevice {} DROP LINE rule included in the cache {}",
did(), cacheDropRule);
} else {
log.warn("fetchOpticalConnectionsFromDevice {} DROP LINE rule not included in cache", did());
}
//Include the ADD rule to the retrieved rules if found in cache
if ((cacheAddRule != null)) {
confirmedRules.add(cacheAddRule);
log.debug("fetchOpticalConnectionsFromDevice {} ADD LINE rule included in the cache {}",
did(), cacheAddRule.selector());
} else {
log.warn("fetchOpticalConnectionsFromDevice {} ADD LINE rule not included in cache", did());
}
//If neither Add or Drop rules are present in the cache, remove configuration from the device
if ((cacheDropRule == null) && (cacheAddRule == null)) {
log.warn("fetchOpticalConnectionsFromDevice {} ADD and DROP rule not included in the cache", did());
FlowRule deviceDropRule = DefaultFlowRule.builder()
.forDevice(data().deviceId())
.makePermanent()
.withSelector(selectorDrop)
.withTreatment(treatmentDrop)
.withCookie(DEFAULT_RULE_COOKIE)
.withPriority(DEFAULT_RULE_PRIORITY)
.build();
FlowRule deviceAddRule = DefaultFlowRule.builder()
.forDevice(data().deviceId())
.makePermanent()
.withSelector(selectorAdd)
.withTreatment(treatmentAdd)
.withCookie(DEFAULT_RULE_COOKIE)
.withPriority(DEFAULT_RULE_PRIORITY)
.build();
try {
//TODO this is not required if allowExternalFlowRules
TerminalDeviceFlowRule addRule = new TerminalDeviceFlowRule(deviceAddRule, getLinePorts());
removeFlowRule(session, addRule);
TerminalDeviceFlowRule dropRule = new TerminalDeviceFlowRule(deviceDropRule, getLinePorts());
removeFlowRule(session, dropRule);
} catch (NetconfException e) {
openConfigError("Error removing LINE rule from device", e);
}
}
return confirmedRules;
}
private List<FlowRule> fetchClientConnectionFromDevice(PortNumber clientPortNumber, PortNumber linePortNumber) {
List<FlowRule> confirmedRules = new ArrayList<>();
FlowRule cacheAddRule;
FlowRule cacheDropRule;
NetconfSession session = getNetconfSession();
//Build the corresponding flow rule as expected
//Selector including port
//Treatment including port
log.debug("fetchClientConnectionsFromDevice {} client {} line {}", did(), clientPortNumber, linePortNumber);
TrafficSelector selectorDrop = DefaultTrafficSelector.builder()
.matchInPort(linePortNumber)
.build();
TrafficTreatment treatmentDrop = DefaultTrafficTreatment.builder()
.setOutput(clientPortNumber)
.build();
TrafficSelector selectorAdd = DefaultTrafficSelector.builder()
.matchInPort(clientPortNumber)
.build();
TrafficTreatment treatmentAdd = DefaultTrafficTreatment.builder()
.setOutput(linePortNumber)
.build();
//Retrieved rules and cached rules are considered equal if both selector and treatment are equal
cacheAddRule = null;
cacheDropRule = null;
if (getConnectionCache().size(did()) != 0) {
cacheDropRule = getConnectionCache().get(did()).stream()
.filter(r -> (r.selector().equals(selectorDrop) && r.treatment().equals(treatmentDrop)))
.findFirst()
.orElse(null);
cacheAddRule = getConnectionCache().get(did()).stream()
.filter(r -> (r.selector().equals(selectorAdd) && r.treatment().equals(treatmentAdd)))
.findFirst()
.orElse(null);
}
//Include the DROP rule to the retrieved rules if found in cache
if ((cacheDropRule != null)) {
confirmedRules.add(cacheDropRule);
log.debug("fetchClientConnectionsFromDevice {} DROP CLIENT rule in the cache {}",
did(), cacheDropRule);
} else {
log.warn("fetchClientConnectionsFromDevice {} DROP CLIENT rule not found in cache", did());
}
//Include the ADD rule to the retrieved rules if found in cache
if ((cacheAddRule != null)) {
confirmedRules.add(cacheAddRule);
log.debug("fetchClientConnectionsFromDevice {} ADD CLIENT rule in the cache {}",
did(), cacheAddRule);
} else {
log.warn("fetchClientConnectionsFromDevice {} ADD CLIENT rule not found in cache", did());
}
if ((cacheDropRule == null) && (cacheAddRule == null)) {
log.warn("fetchClientConnectionsFromDevice {} ADD and DROP rule not included in the cache", did());
FlowRule deviceDropRule = DefaultFlowRule.builder()
.forDevice(data().deviceId())
.makePermanent()
.withSelector(selectorDrop)
.withTreatment(treatmentDrop)
.withCookie(DEFAULT_RULE_COOKIE)
.withPriority(DEFAULT_RULE_PRIORITY)
.build();
FlowRule deviceAddRule = DefaultFlowRule.builder()
.forDevice(data().deviceId())
.makePermanent()
.withSelector(selectorAdd)
.withTreatment(treatmentAdd)
.withCookie(DEFAULT_RULE_COOKIE)
.withPriority(DEFAULT_RULE_PRIORITY)
.build();
try {
//TODO this is not required if allowExternalFlowRules
TerminalDeviceFlowRule addRule = new TerminalDeviceFlowRule(deviceAddRule, getLinePorts());
removeFlowRule(session, addRule);
TerminalDeviceFlowRule dropRule = new TerminalDeviceFlowRule(deviceDropRule, getLinePorts());
removeFlowRule(session, dropRule);
} catch (NetconfException e) {
openConfigError("Error removing CLIENT rule from device", e);
}
}
return confirmedRules;
}
/**
* Fetches list of connections from device.
*
* TODO manage allow external flow rules (allowExternalFlowRules)
* Currently removes from the device all connections that are not currently present in the DeviceConnectionCache.
*
* @return connections that are present on the device and in the DeviceConnectionCache.
*/
private List<FlowRule> fetchConnectionsFromDevice() {
List<FlowRule> confirmedRules = new ArrayList<>();
String reply;
FlowRule cacheAddRule;
FlowRule cacheDropRule;
NetconfSession session = getNetconfSession();
//Get relevant information from the device
StringBuilder requestFilter = new StringBuilder();
requestFilter.append("<components xmlns='http://openconfig.net/yang/platform'>");
requestFilter.append(" <component>");
requestFilter.append(" <name/>");
requestFilter.append(" <oc-opt-term:optical-channel " +
"xmlns:oc-opt-term='http://openconfig.net/yang/terminal-device'>");
requestFilter.append(" <oc-opt-term:config/>");
requestFilter.append(" </oc-opt-term:optical-channel>");
requestFilter.append(" </component>");
requestFilter.append("</components>");
requestFilter.append("<terminal-device xmlns='http://openconfig.net/yang/terminal-device'>");
requestFilter.append(" <logical-channels>");
requestFilter.append(" <channel>");
requestFilter.append(" <index/>");
requestFilter.append(" <config>");
requestFilter.append(" <admin-state/>");
requestFilter.append(" <logical-channel-type/>");
requestFilter.append(" </config>");
requestFilter.append(" <logical-channel-assignments>");
requestFilter.append(" <assignment>");
requestFilter.append(" <config>");
requestFilter.append(" <logical-channel/>");
requestFilter.append(" </config>");
requestFilter.append(" </assignment>");
requestFilter.append(" </logical-channel-assignments>");
requestFilter.append(" </channel>");
requestFilter.append(" </logical-channels>");
requestFilter.append("</terminal-device>");
try {
reply = session.get(requestFilter.toString(), null);
//log.debug("TRANSPONDER CONNECTIONS - fetchConnectionsFromDevice {} reply {}", did(), reply);
} catch (NetconfException e) {
log.error("Failed to retrieve configuration details for device {}", handler().data().deviceId(), e);
return ImmutableList.of();
}
HierarchicalConfiguration cfg = XmlConfigParser.loadXml(new ByteArrayInputStream(reply.getBytes()));
List<HierarchicalConfiguration> logicalChannels =
cfg.configurationsAt("data.terminal-device.logical-channels.channel");
List<HierarchicalConfiguration> components =
cfg.configurationsAt("data.components.component");
//Retrieve the ENABLED line ports
List<String> enabledOpticalChannels = logicalChannels.stream()
.filter(r -> r.getString("config.logical-channel-type").equals(OC_TYPE_PROT_OTN))
.filter(r -> r.getString("config.admin-state").equals(OPERATION_ENABLE))
.map(r -> r.getString("index"))
.collect(Collectors.toList());
log.debug("fetchConnectionsFromDevice {} enabledOpticalChannelsIndex {}", did(), enabledOpticalChannels);
if (enabledOpticalChannels.size() != 0) {
for (String channel : enabledOpticalChannels) {
log.debug("fetchOpticalConnectionsFromDevice {} channel {}", did(), channel);
//Retrieve the corresponding central frequency from the associated component
//TODO correlate the components instead of relying on naming
Frequency centralFreq = components.stream()
.filter(c -> c.getString("name").equals(PREFIX_CHANNEL + channel))
.map(c -> c.getDouble("optical-channel.config.frequency"))
.map(c -> Frequency.ofMHz(c))
.findFirst()
.orElse(null);
confirmedRules.addAll(fetchLineConnectionFromDevice(channel, centralFreq));
}
}
//Retrieve the ENABLED client ports
List<String> enabledClientChannels = logicalChannels.stream()
.filter(r -> r.getString("config.logical-channel-type").equals(OC_TYPE_PROT_ETH))
.filter(r -> r.getString("config.admin-state").equals(OPERATION_ENABLE))
.map(r -> r.getString("index"))
.collect(Collectors.toList());
log.debug("fetchClientConnectionsFromDevice {} enabledClientChannelsIndex {}", did(), enabledClientChannels);
if (enabledClientChannels.size() != 0) {
for (String clientPort : enabledClientChannels) {
log.debug("fetchClientConnectionsFromDevice {} channel {}", did(), clientPort);
String linePort = logicalChannels.stream()
.filter(r -> r.getString("config.logical-channel-type").equals(OC_TYPE_PROT_ETH))
.filter(r -> r.getString("config.admin-state").equals(OPERATION_ENABLE))
.filter(r -> r.getString("index").equals(clientPort))
.map(r -> r.getString("logical-channel-assignments.assignment.config.logical-channel"))
.findFirst()
.orElse(null);
//Build the corresponding flow rule as expected
//Selector including port
//Treatment including port
PortNumber clientPortNumber = PortNumber.portNumber(clientPort);
PortNumber linePortNumber = PortNumber.portNumber(linePort);
confirmedRules.addAll(fetchClientConnectionFromDevice(clientPortNumber, linePortNumber));
}
}
//Returns rules that are both on the device and on the cache
if (confirmedRules.size() != 0) {
log.info("fetchConnectionsFromDevice {} number of confirmed rules {}", did(), confirmedRules.size());
return confirmedRules;
} else {
return ImmutableList.of();
}
}
/**
* Convert start and end frequencies to OCh signal.
*
* FIXME: supports channel spacing 50 and 100
*
* @param central central frequency as double in THz
* @param width width of the channel arounf the central frequency as double in GHz
* @return OCh signal
*/
public static OchSignal toOchSignal(Frequency central, double width) {
int slots = (int) (width / ChannelSpacing.CHL_12P5GHZ.frequency().asGHz());
int multiplier = 0;
double centralAsGHz = central.asGHz();
if (width == 50) {
multiplier = (int) ((centralAsGHz - Spectrum.CENTER_FREQUENCY.asGHz())
/ ChannelSpacing.CHL_50GHZ.frequency().asGHz());
return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, multiplier, slots);
}
if (width == 100) {
multiplier = (int) ((centralAsGHz - Spectrum.CENTER_FREQUENCY.asGHz())
/ ChannelSpacing.CHL_100GHZ.frequency().asGHz());
return new OchSignal(GridType.DWDM, ChannelSpacing.CHL_100GHZ, multiplier, slots);
}
return null;
}
private List<PortNumber> getLinePorts() {
List<PortNumber> linePorts;
DeviceService deviceService = this.handler().get(DeviceService.class);
linePorts = deviceService.getPorts(data().deviceId()).stream()
.filter(p -> p.annotations().value(OdtnDeviceDescriptionDiscovery.PORT_TYPE)
.equals(OdtnDeviceDescriptionDiscovery.OdtnPortType.LINE.value()))
.map(p -> p.number())
.collect(Collectors.toList());
return linePorts;
}
}