diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalCircuitIntentCompiler.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalCircuitIntentCompiler.java index a484d66ab9..7e873d1f1e 100644 --- a/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalCircuitIntentCompiler.java +++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalCircuitIntentCompiler.java @@ -127,6 +127,11 @@ public class OpticalCircuitIntentCompiler implements IntentCompiler rules = new LinkedList<>(); // at the source: ODUCLT port mapping to OCH port + log.debug("OpticalCircuitIntent creating FlowRules"); rules.add(connectPorts(higherIntent.getSrc(), lowerIntent.getSrc(), higherIntent.priority(), slots)); // at the destination: OCH port mapping to ODUCLT port rules.add(connectPorts(lowerIntent.getDst(), higherIntent.getDst(), higherIntent.priority(), slots)); // Create flow rules for reverse path if (higherIntent.isBidirectional()) { + log.debug("OpticalCircuitIntent creating FlowRules for reverse path"); // at the destination: OCH port mapping to ODUCLT port rules.add(connectPorts(lowerIntent.getSrc(), higherIntent.getSrc(), higherIntent.priority(), slots)); // at the source: ODUCLT port mapping to OCH port diff --git a/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java b/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java index dcd97bf741..acedda2ea4 100644 --- a/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java +++ b/apps/optical-model/src/main/java/org/onosproject/net/optical/intent/impl/compiler/OpticalConnectivityIntentCompiler.java @@ -182,6 +182,7 @@ public class OpticalConnectivityIntentCompiler implements IntentCompiler r.selector().criteria().size() == NUM_CRITERIA_OPTICAL_CONNECTIVIY_RULE) .map(r -> ((OchSignalCriterion) r.selector().getCriterion(Criterion.Type.OCH_SIGID)).lambda()) .findFirst() @@ -239,7 +244,7 @@ public class OpticalIntentsWebResource extends AbstractWebResource { } nullIsNotFound(intent, "Intent Id is not found"); - if (intent instanceof OpticalConnectivityIntent) { + if ((intent instanceof OpticalConnectivityIntent) || (intent instanceof OpticalCircuitIntent)) { intentService.withdraw(intent); } else { throw new IllegalArgumentException("Specified intent is not of type OpticalConnectivityIntent"); @@ -306,8 +311,6 @@ public class OpticalIntentsWebResource extends AbstractWebResource { Link link = linkService.getLink(srcConnectPoint, dstConnectPoint); if (link == null) { - log.warn("Not existing link in the suggested path src {} dst {}", - srcConnectPoint, dstConnectPoint); throw new IllegalArgumentException("Not existing link in the suggested path"); } diff --git a/apps/optical-rest/src/main/resources/definitions/CreateIntent.json b/apps/optical-rest/src/main/resources/definitions/CreateIntent.json index da4f215f48..3106421ad8 100644 --- a/apps/optical-rest/src/main/resources/definitions/CreateIntent.json +++ b/apps/optical-rest/src/main/resources/definitions/CreateIntent.json @@ -20,11 +20,11 @@ "properties": { "device": { "type": "string", - "example": "netconf:10.255.255.14:2022" + "example": "netconf:10.10.255.14:2022" }, "port": { "type": "string", - "example": "101" + "example": "10102" } } }, @@ -38,15 +38,15 @@ "properties": { "device": { "type": "string", - "example": "netconf:10.255.255.17:2022" + "example": "netconf:10.10.255.15:2022" }, "port": { "type": "string", - "example": "201" + "example": "10102" } } }, - "bidirection": { + "bidirectional": { "type": "boolean", "example": false }, @@ -115,11 +115,11 @@ "properties" : { "src": { "type": "string", - "example": "netconf:10.255.255.14:2022/101" + "example": "netconf:10.10.255.14:2022/10102" }, "dst": { "type": "string", - "example": "netconf:10.255.255.9:2022/201" + "example": "netconf:10.10.255.15:2022/10102" } } } diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/ClientLineTerminalDeviceDiscovery.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/ClientLineTerminalDeviceDiscovery.java new file mode 100644 index 0000000000..4762bf18d1 --- /dev/null +++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/ClientLineTerminalDeviceDiscovery.java @@ -0,0 +1,598 @@ +/* + * 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 static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.OchSignal; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.PortNumber; +import org.onosproject.net.CltSignalType; +import org.onosproject.net.OduSignalType; +import org.onosproject.net.optical.device.OduCltPortHelper; +import org.slf4j.Logger; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.concurrent.CompletableFuture; + +import org.onlab.packet.ChassisId; + +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; + +import org.onosproject.drivers.utilities.XmlConfigParser; + +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceDescriptionDiscovery; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.PortDescription; + +import org.onosproject.net.driver.AbstractHandlerBehaviour; + +import org.onosproject.net.optical.device.OchPortHelper; + +import org.onosproject.netconf.NetconfController; +import org.onosproject.netconf.NetconfDevice; +import org.onosproject.netconf.NetconfException; +import org.onosproject.netconf.NetconfSession; + +import com.google.common.collect.ImmutableList; + +import org.onosproject.odtn.behaviour.OdtnDeviceDescriptionDiscovery; + + +/** + * Driver Implementation of the DeviceDescription discovery for OpenConfig terminal devices. + * + * As defined in OpenConfig each PORT component includes a subcomponent: + * --- client ports have a subcomponent of type oc-platform-types:TRANSCEIVER + * --- line ports have a subcomponent of type oc-opt-types:OPTICAL_CHANNEL + * + * Tested with a model in which each port includes the following two properties: + * --- odtn-port-type: can assume values "client" and "line" + * --- onos-index: integer value + * + * Other assumptions: + * --- The port name is in the format "port-xxx" + * --- The subcomponent of type TRANSCEIVER has a name in the format "transceiver-xxx" + * --- The subcomponent of type OPTICAL_CHANNEL has a name in the format "channel-xxx" + * --- In the section the channel with index xxx is associated to transceiver-xxx + * + * Where xxx is the value of the onos-index property (e.g., port-11801, transceiver-11801, channel-11801) + * + * See simplified example of a port component: + * + * //CHECKSTYLE:OFF + * + * port-11801 + * + * port-11801 + * oc-platform-types:PORT + * + * + * + * odtn-port-type + * + * odtn-port-type + * client + * + * + * + * onos-index + * + * onos-index + * 11801 + * + * + * + * + * + * transceiver-11801 + * + * transceiver-11801 + * + * + * + * + * + * + * + * 11801 + * + * 11801 + * Logical channel 11801 + * DISABLED + * oc-opt-types:TRIB_RATE_10G + * oc-opt-types:PROT_10GE_LAN + * oc-opt-types:PROT_ETHERNET + * NONE + * false + * UP + * + * + * + * transceiver-11801 + * + * + * + * + * + * //CHECKSTYLE:ON + */ +public class ClientLineTerminalDeviceDiscovery + extends AbstractHandlerBehaviour + implements OdtnDeviceDescriptionDiscovery, DeviceDescriptionDiscovery { + + private static final String RPC_TAG_NETCONF_BASE = + ""; + + private static final String OC_PLATFORM_TYPES_OPERATING_SYSTEM = + "oc-platform-types:OPERATING_SYSTEM"; + + private static final String RPC_CLOSE_TAG = ""; + + private static final String OC_PLATFORM_TYPES_TRANSCEIVER = + "oc-platform-types:TRANSCEIVER"; + + private static final String OC_PLATFORM_TYPES_PORT = + "oc-platform-types:PORT"; + + private static final String OC_TRANSPORT_TYPES_OPTICAL_CHANNEL = + "oc-opt-types:OPTICAL_CHANNEL"; + + private static final Logger log = getLogger(ClientLineTerminalDeviceDiscovery.class); + + + /** + * Returns the NetconfSession with the device for which the method was called. + * + * @param deviceId device indetifier + * + * @return The netconf session or null + */ + private NetconfSession getNetconfSession(DeviceId deviceId) { + NetconfController controller = handler().get(NetconfController.class); + NetconfDevice ncdev = controller.getDevicesMap().get(deviceId); + if (ncdev == null) { + log.trace("No netconf device, returning null session"); + return null; + } + return ncdev.getSession(); + } + + + /** + * Get the deviceId for which the methods apply. + * + * @return The deviceId as contained in the handler data + */ + private DeviceId did() { + return handler().data().deviceId(); + } + + + /** + * Get the device instance for which the methods apply. + * + * @return The device instance + */ + private Device getDevice() { + DeviceService deviceService = checkNotNull(handler().get(DeviceService.class)); + Device device = deviceService.getDevice(did()); + return device; + } + + + /** + * 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(""); + rpc.append(""); + rpc.append(filter); + rpc.append(""); + rpc.append(""); + rpc.append(RPC_CLOSE_TAG); + return rpc.toString(); + } + + + /** + * Construct a String with a Netconf filtered get RPC Message. + * + * @param filter A valid XPath Expression with the filter to apply in the get + * @return a String containing the RPC XML Document + * + * Note: server must support xpath capability. + + * + */ + private String xpathFilteredGetBuilder(String filter) { + StringBuilder rpc = new StringBuilder(RPC_TAG_NETCONF_BASE); + rpc.append(""); + rpc.append(""); + rpc.append(""); + rpc.append(RPC_CLOSE_TAG); + return rpc.toString(); + } + + + /** + * Builds a request to get Device details, operational data. + * + * @return A string with the Netconf RPC for a get with subtree rpcing based on + * /components/component/state/type being oc-platform-types:OPERATING_SYSTEM + */ + private String getDeviceDetailsBuilder() { + StringBuilder filter = new StringBuilder(); + filter.append(""); + filter.append(" "); + filter.append(" "); + filter.append(" "); + filter.append(OC_PLATFORM_TYPES_OPERATING_SYSTEM); + filter.append(" "); + filter.append(" "); + filter.append(" "); + filter.append(""); + return filteredGetBuilder(filter.toString()); + } + + + /** + * Builds a request to get Device Components, config and operational data. + * + * @return A string with the Netconf RPC for a get with subtree rpcing based on + * /components/ + */ + private String getDeviceComponentsBuilder() { + return filteredGetBuilder( + ""); + } + + + /** + * Builds a request to get Device Ports, config and operational data. + * + * @return A string with the Netconf RPC for a get with subtree rpcing based on + * /components/component/state/type being oc-platform-types:PORT + */ + private String getDevicePortsBuilder() { + StringBuilder rpc = new StringBuilder(); + rpc.append(""); + rpc.append(" "); + rpc.append(" oc-platform-types:PORT"); + rpc.append(" "); + rpc.append(""); + return filteredGetBuilder(rpc.toString()); + } + + + /** + * Returns a DeviceDescription with Device info. + * + * @return DeviceDescription or null + * + * //CHECKSTYLE:OFF + *
{@code
+     * 
+     * 
+     *  
+     *   
+     *     FIRMWARE
+     *     oc-platform-types:OPERATING_SYSTEM
+     *     CTTC METRO-HAUL Emulated OpenConfig TerminalDevice
+     *     0.0.1
+     *   
+     *  
+     * 
+     * 
+     *}
+ * //CHECKSTYLE:ON + */ + @Override + public DeviceDescription discoverDeviceDetails() { + boolean defaultAvailable = true; + SparseAnnotations annotations = DefaultAnnotations.builder().build(); + + log.debug("ClientLineTerminalDeviceDiscovery::discoverDeviceDetails device {}", did()); + + // Other option "OTN" or "OTHER", we use TERMINAL_DEVICE + org.onosproject.net.Device.Type type = + Device.Type.TERMINAL_DEVICE; + + // Some defaults + String vendor = "NOVENDOR"; + String serialNumber = "0xCAFEBEEF"; + String hwVersion = "0.2.1"; + String swVersion = "0.2.1"; + String chassisId = "128"; + + // Get the session, + NetconfSession session = getNetconfSession(did()); + try { + String reply = session.get(getDeviceDetailsBuilder()); + log.debug("REPLY to DeviceDescription {}", reply); + + // as root node, software hardare version requires openconfig >= 2018 + XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(reply); + vendor = xconf.getString("data.components.component.state.mfg-name", vendor); + serialNumber = xconf.getString("data.components.component.state.serial-no", serialNumber); + swVersion = xconf.getString("data.components.component.state.software-version", swVersion); + hwVersion = xconf.getString("data.components.component.state.hardware-version", hwVersion); + } catch (Exception e) { + log.error("ClientLineTerminalDeviceDiscovery::discoverDeviceDetails - Failed to retrieve session {}", + did()); + throw new IllegalStateException(new NetconfException("Failed to retrieve version info.", e)); + } + + ChassisId cid = new ChassisId(Long.valueOf(chassisId, 10)); + + log.info("Device retrieved details"); + log.info("VENDOR {}", vendor); + log.info("HWVERSION {}", hwVersion); + log.info("SWVERSION {}", swVersion); + log.info("SERIAL {}", serialNumber); + log.info("CHASSISID {}", chassisId); + + return new DefaultDeviceDescription(did().uri(), + type, vendor, hwVersion, swVersion, serialNumber, + cid, defaultAvailable, annotations); + } + + + + /** + * Returns a list of PortDescriptions for the device. + * + * @return a list of descriptions. + * + * The RPC reply follows the following pattern: + * //CHECKSTYLE:OFF + *
{@code
+     * 
+     * 
+     * 
+     *   
+     *     ....
+     *     
+     *     ....
+     *     
+     *   
+     * 
+     * 
+     * }
+ * //CHECKSTYLE:ON + */ + @Override + public List discoverPortDetails() { + try { + XPathExpressionEngine xpe = new XPathExpressionEngine(); + NetconfSession session = getNetconfSession(did()); + if (session == null) { + log.error("discoverPortDetails called with null session for {}", did()); + return ImmutableList.of(); + } + + CompletableFuture fut = session.rpc(getDeviceComponentsBuilder()); + String rpcReply = fut.get(); + + XMLConfiguration xconf = (XMLConfiguration) XmlConfigParser.loadXmlString(rpcReply); + xconf.setExpressionEngine(xpe); + + log.debug("REPLY {}", rpcReply); + HierarchicalConfiguration components = xconf.configurationAt("data/components"); + return parsePorts(components); + } catch (Exception e) { + log.error("Exception discoverPortDetails() {}", did(), e); + return ImmutableList.of(); + } + } + + /** + * Parses port information from OpenConfig XML configuration. + * + * @param components the XML document with components root. + * @return List of ports + * + * //CHECKSTYLE:OFF + *
{@code
+     *   
+     *     ....
+     *     
+     *     ....
+     *     
+     *   
+     * }
+ * //CHECKSTYLE:ON + */ + protected List parsePorts(HierarchicalConfiguration components) { + return components.configurationsAt("component").stream() + .filter(component -> { + return !component.getString("name", "unknown").equals("unknown") && + component.getString("state/type", "unknown").equals(OC_PLATFORM_TYPES_PORT); + }) + .map(component -> { + try { + // Pass the root document for cross-reference + return parsePortComponent(component, components); + } catch (Exception e) { + return null; + } + } + ) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + } + + + /** + * Checks if a given component has a subcomponent of a given type. + * + * @param component subtree to parse looking for subcomponents. + * @param components the full components tree, to cross-ref in + * case we need to check (sub)components' types. + * + * @return true or false + */ + private boolean hasSubComponentOfType( + HierarchicalConfiguration component, + HierarchicalConfiguration components, + String type) { + long count = component.configurationsAt("subcomponents/subcomponent") + .stream() + .filter(subcomponent -> { + String scName = subcomponent.getString("name"); + StringBuilder sb = new StringBuilder("component[name='"); + sb.append(scName); + sb.append("']/state/type"); + String scType = components.getString(sb.toString(), "unknown"); + return scType.equals(type); + }) + .count(); + return (count > 0); + } + + + /** + * Checks if a given component has a subcomponent of type OPTICAL_CHANNEL. + * + * @param component subtree to parse + * @param components the full components tree, to cross-ref in + * case we need to check transceivers or optical channels. + * + * @return true or false + */ + private boolean hasOpticalChannelSubComponent( + HierarchicalConfiguration component, + HierarchicalConfiguration components) { + return hasSubComponentOfType(component, components, OC_TRANSPORT_TYPES_OPTICAL_CHANNEL); + } + + + /** + * Checks if a given component has a subcomponent of type TRANSCEIVER. + * + * @param component subtree to parse + * @param components the full components tree, to cross-ref in + * case we need to check transceivers or optical channels. + * + * @return true or false + */ + private boolean hasTransceiverSubComponent( + HierarchicalConfiguration component, + HierarchicalConfiguration components) { + return hasSubComponentOfType(component, components, OC_PLATFORM_TYPES_TRANSCEIVER); + } + + + /** + * Parses a component XML doc into a PortDescription. + * + * @param component subtree to parse. It must be a component ot type PORT. + * @param components the full components tree, to cross-ref in + * case we need to check transceivers or optical channels. + * + * @return PortDescription or null if component does not have onos-index + */ + private PortDescription parsePortComponent( + HierarchicalConfiguration component, + HierarchicalConfiguration components) { + Map annotations = new HashMap<>(); + String name = component.getString("name"); + String type = component.getString("state/type"); + + log.info("Parsing Component {} type {}", name, type); + + annotations.put(OdtnDeviceDescriptionDiscovery.OC_NAME, name); + annotations.put(OdtnDeviceDescriptionDiscovery.OC_TYPE, type); + + // Store all properties as port properties + component.configurationsAt("properties/property") + .forEach(property -> { + String pn = property.getString("name"); + String pv = property.getString("state/value"); + annotations.put(pn, pv); + }); + + // Assing an ONOS port number + PortNumber portNum; + if (annotations.containsKey(ONOS_PORT_INDEX)) { + portNum = PortNumber.portNumber(Long.parseLong(annotations.get(ONOS_PORT_INDEX))); + } else { + log.warn("PORT {} does not include onos-index, hashing...", name); + portNum = PortNumber.portNumber(name.hashCode()); + } + log.debug("PORT {} number {}", name, portNum); + + // The heuristic to know if it is client or line side + if (!annotations.containsKey(PORT_TYPE)) { + if (hasTransceiverSubComponent(component, components)) { + annotations.put(PORT_TYPE, OdtnPortType.CLIENT.value()); + } else if (hasOpticalChannelSubComponent(component, components)) { + annotations.put(PORT_TYPE, OdtnPortType.LINE.value()); + } + } + + // Build the port + // NOTE: using portNumber(id, name) breaks things. Intent parsing, port resorce management, etc. There seems + // to be an issue with resource mapping + if (annotations.get(PORT_TYPE).equals(OdtnPortType.CLIENT.value())) { + log.debug("PORT {} number {} added as CLIENT port", name, portNum); + + return OduCltPortHelper.oduCltPortDescription(portNum, + true, + CltSignalType.CLT_10GBE, + DefaultAnnotations.builder().putAll(annotations).build()); + } + if (annotations.get(PORT_TYPE).equals(OdtnPortType.LINE.value())) { + log.debug("PORT {} number {} added as LINE port", name, portNum); + + // TODO: To be configured + OchSignal signalId = OchSignal.newDwdmSlot(ChannelSpacing.CHL_50GHZ, 1); + + return OchPortHelper.ochPortDescription( + portNum, true, + OduSignalType.ODU4, // TODO Client signal to be discovered + true, + signalId, + DefaultAnnotations.builder().putAll(annotations).build()); + } + log.error("PORT {} number {} is of UNKNOWN type", name, portNum); + return null; + } +} diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/ClientLineTerminalDeviceFlowRuleProgrammable.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/ClientLineTerminalDeviceFlowRuleProgrammable.java new file mode 100644 index 0000000000..f316fc07dd --- /dev/null +++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/ClientLineTerminalDeviceFlowRuleProgrammable.java @@ -0,0 +1,942 @@ +/* + * 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 = + ""; + + private static final String RPC_CLOSE_TAG = ""; + + 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 applyFlowRules(Collection rules) { + NetconfSession session = getNetconfSession(); + if (session == null) { + openConfigError("null session"); + return ImmutableList.of(); + } + + // Apply the rules on the device + Collection 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 getFlowEntries() { + Collection 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 removeFlowRules(Collection rules) { + NetconfSession session = getNetconfSession(); + if (session == null) { + openConfigError("null session"); + return ImmutableList.of(); + } + List 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(""); + rpc.append(""); + rpc.append(filter); + rpc.append(""); + rpc.append(""); + 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(""); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(""); + return filteredGetBuilder(filt.toString()); + } + + /** + * Construct a get request to retrieve Optical Channels and + * the line port they are using. + *

+ * 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(""); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(" "); + filt.append(""); + 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(""); + sb.append(""); + sb.append(""); + sb.append("" + logChannel + ""); + sb.append(""); + sb.append("" + operation + ""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + + 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(""); + sb.append(""); + sb.append("" + PREFIX_CHANNEL + optChannel + ""); + sb.append(""); + sb.append(""); + sb.append("" + (long) freq.asMHz() + ""); + sb.append("" + DEFAULT_TARGET_POWER + ""); + sb.append("" + DEFAULT_OPERATIONAL_MODE + ""); + sb.append("" + PREFIX_PORT + optChannel + ""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + + 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(""); + sb.append(""); + sb.append(""); + sb.append("" + client + ""); + sb.append(""); + sb.append("" + operation + ""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("" + assignmentIndex + ""); + sb.append(""); + sb.append("" + line + ""); + sb.append("" + allocationIndex + ""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append(""); + + 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 "" + 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 fetchLineConnectionFromDevice(String channel, Frequency centralFreq) { + List 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 fetchClientConnectionFromDevice(PortNumber clientPortNumber, PortNumber linePortNumber) { + List 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 fetchConnectionsFromDevice() { + List confirmedRules = new ArrayList<>(); + String reply; + FlowRule cacheAddRule; + FlowRule cacheDropRule; + NetconfSession session = getNetconfSession(); + + //Get relevant information from the device + StringBuilder requestFilter = new StringBuilder(); + requestFilter.append(""); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(""); + requestFilter.append(""); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(" "); + requestFilter.append(""); + + 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 logicalChannels = + cfg.configurationsAt("data.terminal-device.logical-channels.channel"); + + List components = + cfg.configurationsAt("data.components.component"); + + //Retrieve the ENABLED line ports + List 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 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 getLinePorts() { + List 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; + + } +} diff --git a/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceFlowRule.java b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceFlowRule.java new file mode 100644 index 0000000000..fb6eeadc65 --- /dev/null +++ b/drivers/odtn-driver/src/main/java/org/onosproject/drivers/odtn/openconfig/TerminalDeviceFlowRule.java @@ -0,0 +1,244 @@ +/* + * 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 org.onosproject.net.OchSignal; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.OchSignalCriterion; +import org.onosproject.net.flow.criteria.OchSignalTypeCriterion; +import org.onosproject.net.flow.criteria.PortCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Set; + +import static com.google.common.base.Preconditions.checkArgument; + +public class TerminalDeviceFlowRule extends DefaultFlowRule { + private static final Logger log = LoggerFactory.getLogger(TerminalDeviceFlowRule.class); + + public enum Type { + CLIENT_INGRESS, + CLIENT_EGRESS, + LINE_INGRESS, + LINE_EGRESS + } + + //As generated by the OpticalConnectivityIntentCompiler + private static final int NUM_CRITERIA_LINE_EGRESS_RULE = 3; + private static final int NUM_INSTRUCTIONS_LINE_EGRESS_RULE = 1; + private static final int NUM_CRITERIA_LINE_INGRESS_RULE = 1; + private static final int NUM_INSTRUCTIONS_LINE_INGRESS_RULE = 2; + + //As generated by the OpticalCoircuitIntentCompiler + private static final int NUM_CRITERIA_CLIENT_RULES = 1; + private static final int NUM_INSTRUCTIONS_CLIENT_RULES = 1; + + public Type type; + + private PortNumber inPortNumber; + private PortNumber outPortNumber; + private OchSignal ochSignal; + private String connectionName; + + + public TerminalDeviceFlowRule(FlowRule rule, List linePorts) { + super(rule); + + Set criteria = rule.selector().criteria(); + List instructions = rule.treatment().immediate(); + + /*Rules for TerminalDevice are generated in OpticalPathIntentCompiler with two types of intents + --- OpticalConnectivity intent compilation generates following flow rules + OPTICAL LINE level at INGRESS node -- criteria: input port; output port, OChSignal; + OPTICAL LINE level at EGRESS node -- criteria: input port, OChSignal and OCh type; instruction: output port + --- OpticalCircuit intent compilation generates following flow rules + CLIENT PORT at INGRESS node -- criteria: input port (OduClt); instruction output port (Och) + CLIENT PORT at EGRESS node -- criteria: input port (Och); instruction output port (OduClt)*/ + + checkArgument((criteria.size() == NUM_CRITERIA_LINE_EGRESS_RULE) || + (criteria.size() == NUM_CRITERIA_LINE_INGRESS_RULE) || + (criteria.size() == NUM_CRITERIA_CLIENT_RULES), + "Wrong size of flow rule criteria for TerminalDevice size" + criteria.size()); + + checkArgument((instructions.size() == NUM_INSTRUCTIONS_LINE_EGRESS_RULE) || + (instructions.size() == NUM_INSTRUCTIONS_LINE_INGRESS_RULE) || + (instructions.size() == NUM_INSTRUCTIONS_CLIENT_RULES), + "Wrong size of flow rule instructions for TerminalDevice size " + instructions.size()); + + //This is EGRESS rule on the LINE side + if ((criteria.size() == NUM_CRITERIA_LINE_EGRESS_RULE) && + (instructions.size() == NUM_INSTRUCTIONS_LINE_EGRESS_RULE)) { + log.debug("Building the TerminalDeviceFlowRule for LINE_EGRESS"); + type = Type.LINE_EGRESS; + + criteria.forEach( + c -> checkArgument(c instanceof OchSignalCriterion || + c instanceof OchSignalTypeCriterion || + c instanceof PortCriterion, + "Incompatible flow rule criteria for ADD TerminalDevice: " + criteria + ) + ); + instructions.forEach( + c -> checkArgument(c instanceof Instructions.OutputInstruction, + "Incompatible flow rule instruction for ADD TerminalDevice: " + instructions + ) + ); + + ochSignal = criteria.stream() + .filter(c -> c instanceof OchSignalCriterion) + .map(c -> ((OchSignalCriterion) c).lambda()) + .findAny() + .orElse(null); + + inPortNumber = criteria.stream() + .filter(c -> c instanceof PortCriterion) + .map(c -> ((PortCriterion) c).port()) + .findAny() + .orElse(null); + + outPortNumber = ((Instructions.OutputInstruction) instructions.get(0)).port(); + + checkArgument(linePorts.contains(outPortNumber), + "Incompatible output port for DROP TerminalDevice"); + + } + + //This is INGRESS rule on the LINE side + if ((criteria.size() == NUM_CRITERIA_LINE_INGRESS_RULE) && + (instructions.size() == NUM_INSTRUCTIONS_LINE_INGRESS_RULE)) { + log.debug("Building the TerminalDeviceFlowRule LINE_INGRESS"); + type = Type.LINE_INGRESS; + + criteria.forEach( + c -> checkArgument( + c instanceof PortCriterion, + "Incompatible flow rule criteria for ADD TerminalDevice: " + criteria + ) + ); + instructions.forEach( + c -> checkArgument(c.type() == Instruction.Type.L0MODIFICATION || + c.type() == Instruction.Type.OUTPUT, + "Incompatible flow rule instruction for ADD TerminalDevice: " + instructions + ) + ); + + inPortNumber = criteria.stream() + .filter(c -> c instanceof PortCriterion) + .map(c -> ((PortCriterion) c).port()) + .findAny() + .orElse(null); + + checkArgument(linePorts.contains(inPortNumber), + "Incompatible input port for DROP TerminalDevice"); + + ochSignal = instructions.stream() + .filter(c -> c.type() == Instruction.Type.L0MODIFICATION) + .map(c -> ((L0ModificationInstruction.ModOchSignalInstruction) c).lambda()) + .findAny() + .orElse(null); + + outPortNumber = instructions.stream() + .filter(c -> c.type() == Instruction.Type.OUTPUT) + .map(c -> ((Instructions.OutputInstruction) c).port()) + .findAny() + .orElse(null); + } + + //This is INGRESS or EGRESS rule on the CLIENT side + if ((criteria.size() == NUM_CRITERIA_CLIENT_RULES) && + (instructions.size() == NUM_INSTRUCTIONS_CLIENT_RULES)) { + + criteria.forEach( + c -> checkArgument( + c instanceof PortCriterion, + "Incompatible flow rule criteria for ADD TerminalDevice: " + criteria + ) + ); + instructions.forEach( + c -> checkArgument(c.type() == Instruction.Type.OUTPUT, + "Incompatible flow rule instruction for ADD TerminalDevice: " + instructions + ) + ); + + inPortNumber = criteria.stream() + .filter(c -> c instanceof PortCriterion) + .map(c -> ((PortCriterion) c).port()) + .findAny() + .orElse(null); + + outPortNumber = instructions.stream() + .filter(c -> c.type() == Instruction.Type.OUTPUT) + .map(c -> ((Instructions.OutputInstruction) c).port()) + .findAny() + .orElse(null); + + ochSignal = null; + + if (linePorts.contains(outPortNumber)) { + type = Type.CLIENT_INGRESS; + } else { + type = Type.CLIENT_EGRESS; + } + } + + if (type == Type.LINE_EGRESS) { + connectionName = "LineEgress-LinePort-" + inPortNumber.toString() + + "-ochSig-" + ochSignal.centralFrequency().toString(); + } + if (type == Type.LINE_INGRESS) { + connectionName = "LineIngress-LinePort-" + outPortNumber.toString() + + "-ochSig-" + ochSignal.centralFrequency().toString(); + } + if (type == Type.CLIENT_EGRESS) { + connectionName = "ClientEgress-LinePort-" + inPortNumber.toString() + + "-ClientPort-" + outPortNumber.toString(); + } + if (type == Type.CLIENT_INGRESS) { + connectionName = "ClientIngress-ClientPort-" + inPortNumber.toString() + + "-LinePort-" + outPortNumber.toString(); + } + + log.info("TerminalFlowRule built with name {}", connectionName); + } + + public PortNumber inPort() { + return inPortNumber; + } + + public PortNumber outPort() { + return outPortNumber; + } + + public OchSignal ochSignal() { + return ochSignal; + } + + public String connectionName() { + return connectionName; + } +} + diff --git a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml index 2028af7a12..dfc418a7bc 100644 --- a/drivers/odtn-driver/src/main/resources/odtn-drivers.xml +++ b/drivers/odtn-driver/src/main/resources/odtn-drivers.xml @@ -45,6 +45,17 @@ impl="org.onosproject.drivers.odtn.openconfig.TerminalDeviceFlowRuleProgrammable"/> + + + + + + + diff --git a/drivers/odtn-driver/src/test/resources/terminal-device.xml b/drivers/odtn-driver/src/test/resources/terminal-device.xml new file mode 100644 index 0000000000..0af7eaaa8f --- /dev/null +++ b/drivers/odtn-driver/src/test/resources/terminal-device.xml @@ -0,0 +1,1161 @@ + + + + + + + 10103 + + 10103 + Logical channel 10103 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + + + 10103 + Logical channel 10103 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + UP + + + + transceiver-10103 + + + + transceiver-10103 + + + + + + 1 + + 1 + Logical channel assigned + LOGICAL_CHANNEL + 10101 + 10 + + + 1 + 0 + LOGICAL_CHANNEL + 10101 + 10 + + + + + + + 10104 + + 10104 + Logical channel 10104 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + + + 11104 + Logical channel 10104 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + DOWN + + + + transceiver-10104 + + + transceiver-10104 + + + + + 1 + + 1 + Logical channel assigned + LOGICAL_CHANNEL + 10101 + 10 + + + 1 + 0 + LOGICAL_CHANNEL + 10101 + 10 + + + + + + + 10105 + + 10105 + Logical channel 10105 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + + + 10105 + Logical channel 10105 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + DOWN + + + + transceiver-10105 + + + + transceiver-10105 + + + + + + 1 + + 1 + Logical channel assigned + LOGICAL_CHANNEL + 10101 + 10 + + + 1 + 0 + LOGICAL_CHANNEL + 10101 + 10 + + + + + + + + 10101 + + 10101 + Logical channel 10101 + DISABLED + type:PROT_OTN + + NONE + + + 10101 + Logical channel 10101 + DISABLED + type:PROT_OTN + + NONE + UP + + + + channel-10101 + + + + channel-10101 + + + + + + + test1 + test1 + + + test1 + test1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + 0.0 + 0.0 + 0.0 + 0.0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + + + + 1 + + 1 + Optical channel assigned 100 + 100 + OPTICAL_CHANNEL + channel-10101 + + + 1 + Optical channel assigned 100 + 100 + OPTICAL_CHANNEL + channel-10101 + + + + + + + + + 10106 + + 10106 + Logical channel 10106 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + + + 10106 + Logical channel 10106 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + UP + + + + transceiver-10106 + + + + transceiver-10106 + + + + + + 1 + + 1 + Logical channel assigned + LOGICAL_CHANNEL + 10102 + 10 + + + 1 + 0 + LOGICAL_CHANNEL + 10102 + 10 + + + + + + + 10107 + + 10107 + Logical channel 10107 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + + + 11104 + Logical channel 10107 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + DOWN + + + + transceiver-10107 + + + transceiver-10107 + + + + + 1 + + 1 + Logical channel assigned + LOGICAL_CHANNEL + 10102 + 10 + + + 1 + 0 + LOGICAL_CHANNEL + 10102 + 10 + + + + + + + 10108 + + 10108 + Logical channel 10108 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + + + 10108 + Logical channel 10108 + DISABLED + type:TRIB_RATE_10G + type:PROT_10GE_LAN + + type:PROT_ETHERNET + + NONE + 0 + DOWN + + + + transceiver-10108 + + + + transceiver-10108 + + + + + + 1 + + 1 + Logical channel assigned + LOGICAL_CHANNEL + 10102 + 10 + + + 1 + 0 + LOGICAL_CHANNEL + 10102 + 10 + + + + + + + + 10102 + + 10102 + Logical channel 10102 + DISABLED + type:PROT_OTN + + NONE + + + 10102 + Logical channel 10102 + DISABLED + type:PROT_OTN + + NONE + UP + + + + channel-10102 + + + + channel-10102 + + + + + + + test1 + test1 + + + test1 + test1 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + + 0.0 + 0.0 + 0.0 + 0.0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + + + + 1 + + 1 + Optical channel assigned 100 + 100 + OPTICAL_CHANNEL + channel-10102 + + + 1 + Optical channel assigned 100 + 100 + OPTICAL_CHANNEL + channel-10102 + + + + + + + + + + device + + device + + + DC-USA + SSSA-CNIT + 1.0.0 + 1.0.0 + 1.0.0 + 610610 + typex:OPERATING_SYSTEM + + + + + port-10101 + + port-10101 + + + port-10101 + typex:PORT + + + + channel-10101 + + channel-10101 + + + channel-10101 + + + + + + onos-index + + onos-index + 10101 + + + onos-index + 10101 + + + + odtn-port-type + + odtn-port-type + line + + + odtn-port-type + line + + + + + + port-10103 + + port-10103 + + + port-10103 + typex:PORT + + + + transceiver-10103 + + transceiver-10103 + + + transceiver-10103 + + + + + + onos-index + + onos-index + 10103 + + + onos-index + 10103 + + + + odtn-port-type + + odtn-port-type + client + + + odtn-port-type + client + + + + + + port-10104 + + port-10104 + + + port-10104 + typex:PORT + + + + transceiver-10104 + + transceiver-10104 + + + transceiver-10104 + + + + + + onos-index + + onos-index + 10104 + + + onos-index + 10104 + + + + odtn-port-type + + odtn-port-type + client + + + odtn-port-type + client + + + + + + port-10105 + + port-10105 + + + port-10105 + typex:PORT + + + + transceiver-10105 + + transceiver-10105 + + + transceiver-10105 + + + + + + onos-index + + onos-index + 10105 + + + onos-index + 10105 + + + + odtn-port-type + + odtn-port-type + client + + + odtn-port-type + client + + + + + + transceiver-10103 + + transceiver-10103 + + + transceiver-10103 + typex:TRANSCEIVER + + + + transceiver-10104 + + transceiver-10104 + + + transceiver-10104 + typex:TRANSCEIVER + + + + transceiver-10105 + + transceiver-10105 + + + transceiver-10105 + typex:TRANSCEIVER + + + + channel-10101 + + channel-10101 + + + channel-10101 + typex:OPTICAL_CHANNEL + + + + 191600000 + 100 + 0 + port-10101 + + + 191600000 + 0 + 0 + port-10101 + 1 + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + + + + + + + port-10102 + + port-10102 + + + port-10102 + typex:PORT + + + + channel-10102 + + channel-10102 + + + channel-10102 + + + + + + onos-index + + onos-index + 10102 + + + onos-index + 10102 + + + + odtn-port-type + + odtn-port-type + line + + + odtn-port-type + line + + + + + + port-10106 + + port-10106 + + + port-10106 + typex:PORT + + + + transceiver-10106 + + transceiver-10106 + + + transceiver-10106 + + + + + + onos-index + + onos-index + 10106 + + + onos-index + 10106 + + + + odtn-port-type + + odtn-port-type + client + + + odtn-port-type + client + + + + + + port-10107 + + port-10107 + + + port-10107 + typex:PORT + + + + transceiver-10107 + + transceiver-10107 + + + transceiver-10107 + + + + + + onos-index + + onos-index + 10107 + + + onos-index + 10107 + + + + odtn-port-type + + odtn-port-type + client + + + odtn-port-type + client + + + + + + port-10108 + + port-10108 + + + port-10108 + typex:PORT + + + + transceiver-10108 + + transceiver-10108 + + + transceiver-10108 + + + + + + onos-index + + onos-index + 10108 + + + onos-index + 10108 + + + + odtn-port-type + + odtn-port-type + client + + + odtn-port-type + client + + + + + + transceiver-10106 + + transceiver-10106 + + + transceiver-10106 + typex:TRANSCEIVER + + + + transceiver-10107 + + transceiver-10107 + + + transceiver-10107 + typex:TRANSCEIVER + + + + transceiver-10108 + + transceiver-10108 + + + transceiver-10108 + typex:TRANSCEIVER + + + + channel-10102 + + channel-10102 + + + channel-10102 + typex:OPTICAL_CHANNEL + + + + 191600000 + 100 + 0 + port-10102 + + + 191600000 + 0 + 0 + port-10102 + 1 + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + + + 0 + 0 + 0 + 0 + 0 + + + + + + + +