diff options
Diffstat (limited to 'framework/src/onos/providers')
127 files changed, 19413 insertions, 0 deletions
diff --git a/framework/src/onos/providers/host/pom.xml b/framework/src/onos/providers/host/pom.xml new file mode 100644 index 00000000..d5231f8d --- /dev/null +++ b/framework/src/onos/providers/host/pom.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-host-provider</artifactId> + <packaging>bundle</packaging> + + <description>ONOS host tracking provider</description> + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-osgi</artifactId> + <version>${project.version}</version> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java b/framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java new file mode 100644 index 00000000..9a823630 --- /dev/null +++ b/framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java @@ -0,0 +1,439 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.host.impl; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.ARP; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP6; +import org.onlab.packet.IPacket; +import org.onlab.packet.IPv6; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onlab.packet.ipv6.IExtensionHeader; +import org.onlab.packet.ndp.NeighborAdvertisement; +import org.onlab.packet.ndp.NeighborSolicitation; +import org.onlab.packet.ndp.RouterAdvertisement; +import org.onlab.packet.ndp.RouterSolicitation; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.host.DefaultHostDescription; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.HostProviderRegistry; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.host.HostService; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyService; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.util.Dictionary; +import java.util.Set; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to detect network end-station + * hosts. + */ +@Component(immediate = true) +public class HostLocationProvider extends AbstractProvider implements HostProvider { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TopologyService topologyService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private HostProviderService providerService; + + private final InternalHostProvider processor = new InternalHostProvider(); + private final DeviceListener deviceListener = new InternalDeviceListener(); + + private ApplicationId appId; + + @Property(name = "hostRemovalEnabled", boolValue = true, + label = "Enable host removal on port/device down events") + private boolean hostRemovalEnabled = true; + + @Property(name = "ipv6NeighborDiscovery", boolValue = false, + label = "Enable using IPv6 Neighbor Discovery by the " + + "Host Location Provider; default is false") + private boolean ipv6NeighborDiscovery = false; + + /** + * Creates an OpenFlow host provider. + */ + public HostLocationProvider() { + super(new ProviderId("of", "org.onosproject.provider.host")); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + appId = coreService.registerApplication("org.onosproject.provider.host"); + + providerService = providerRegistry.register(this); + packetService.addProcessor(processor, PacketProcessor.advisor(1)); + deviceService.addListener(deviceListener); + readComponentConfiguration(context); + requestIntercepts(); + + log.info("Started with Application ID {}", appId.id()); + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + + withdrawIntercepts(); + + providerRegistry.unregister(this); + packetService.removeProcessor(processor); + deviceService.removeListener(deviceListener); + providerService = null; + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + readComponentConfiguration(context); + requestIntercepts(); + } + + /** + * Request packet intercepts. + */ + private void requestIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_ARP); + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + + // IPv6 Neighbor Solicitation packet. + selector.matchEthType(Ethernet.TYPE_IPV6); + selector.matchIPProtocol(IPv6.PROTOCOL_ICMP6); + selector.matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION); + if (ipv6NeighborDiscovery) { + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + } else { + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + + // IPv6 Neighbor Advertisement packet. + selector.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT); + if (ipv6NeighborDiscovery) { + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + } else { + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + } + + /** + * Withdraw packet intercepts. + */ + private void withdrawIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_ARP); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + + // IPv6 Neighbor Solicitation packet. + selector.matchEthType(Ethernet.TYPE_IPV6); + selector.matchIPProtocol(IPv6.PROTOCOL_ICMP6); + selector.matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + + // IPv6 Neighbor Advertisement packet. + selector.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + + /** + * Extracts properties from the component configuration context. + * + * @param context the component context + */ + private void readComponentConfiguration(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + Boolean flag; + + flag = isPropertyEnabled(properties, "hostRemovalEnabled"); + if (flag == null) { + log.info("Host removal on port/device down events is not configured, " + + "using current value of {}", hostRemovalEnabled); + } else { + hostRemovalEnabled = flag; + log.info("Configured. Host removal on port/device down events is {}", + hostRemovalEnabled ? "enabled" : "disabled"); + } + + flag = isPropertyEnabled(properties, "ipv6NeighborDiscovery"); + if (flag == null) { + log.info("Using IPv6 Neighbor Discovery is not configured, " + + "using current value of {}", ipv6NeighborDiscovery); + } else { + ipv6NeighborDiscovery = flag; + log.info("Configured. Using IPv6 Neighbor Discovery is {}", + ipv6NeighborDiscovery ? "enabled" : "disabled"); + } + } + + /** + * Check property name is defined and set to true. + * + * @param properties properties to be looked up + * @param propertyName the name of the property to look up + * @return value when the propertyName is defined or return null + */ + private static Boolean isPropertyEnabled(Dictionary<?, ?> properties, + String propertyName) { + Boolean value = null; + try { + String s = (String) properties.get(propertyName); + value = isNullOrEmpty(s) ? null : s.trim().equals("true"); + } catch (ClassCastException e) { + // No propertyName defined. + value = null; + } + return value; + } + + @Override + public void triggerProbe(Host host) { + log.info("Triggering probe on device {}", host); + } + + private class InternalHostProvider implements PacketProcessor { + /** + * Update host location only. + * + * @param hid host ID + * @param mac source Mac address + * @param vlan VLAN ID + * @param hloc host location + */ + private void updateLocation(HostId hid, MacAddress mac, + VlanId vlan, HostLocation hloc) { + HostDescription desc = new DefaultHostDescription(mac, vlan, hloc); + try { + providerService.hostDetected(hid, desc); + } catch (IllegalStateException e) { + log.debug("Host {} suppressed", hid); + } + } + + /** + * Update host location and IP address. + * + * @param hid host ID + * @param mac source Mac address + * @param vlan VLAN ID + * @param hloc host location + * @param ip source IP address + */ + private void updateLocationIP(HostId hid, MacAddress mac, + VlanId vlan, HostLocation hloc, + IpAddress ip) { + HostDescription desc = ip.isZero() || ip.isSelfAssigned() ? + new DefaultHostDescription(mac, vlan, hloc) : + new DefaultHostDescription(mac, vlan, hloc, ip); + try { + providerService.hostDetected(hid, desc); + } catch (IllegalStateException e) { + log.debug("Host {} suppressed", hid); + } + } + + @Override + public void process(PacketContext context) { + if (context == null) { + return; + } + + Ethernet eth = context.inPacket().parsed(); + if (eth == null) { + return; + } + MacAddress srcMac = eth.getSourceMAC(); + + VlanId vlan = VlanId.vlanId(eth.getVlanID()); + ConnectPoint heardOn = context.inPacket().receivedFrom(); + + // If this arrived on control port, bail out. + if (heardOn.port().isLogical()) { + return; + } + + // If this is not an edge port, bail out. + Topology topology = topologyService.currentTopology(); + if (topologyService.isInfrastructure(topology, heardOn)) { + return; + } + + HostLocation hloc = new HostLocation(heardOn, System.currentTimeMillis()); + HostId hid = HostId.hostId(eth.getSourceMAC(), vlan); + + // ARP: possible new hosts, update both location and IP + if (eth.getEtherType() == Ethernet.TYPE_ARP) { + ARP arp = (ARP) eth.getPayload(); + IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET, + arp.getSenderProtocolAddress()); + updateLocationIP(hid, srcMac, vlan, hloc, ip); + + // IPv4: update location only + } else if (eth.getEtherType() == Ethernet.TYPE_IPV4) { + updateLocation(hid, srcMac, vlan, hloc); + + // + // NeighborAdvertisement and NeighborSolicitation: possible + // new hosts, update both location and IP. + // + // IPv6: update location only + } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) { + IPv6 ipv6 = (IPv6) eth.getPayload(); + IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET6, + ipv6.getSourceAddress()); + + // skip extension headers + IPacket pkt = ipv6; + while (pkt.getPayload() != null && + pkt.getPayload() instanceof IExtensionHeader) { + pkt = pkt.getPayload(); + } + + // Neighbor Discovery Protocol + pkt = pkt.getPayload(); + if (pkt != null && pkt instanceof ICMP6) { + pkt = pkt.getPayload(); + // RouterSolicitation, RouterAdvertisement + if (pkt != null && (pkt instanceof RouterAdvertisement || + pkt instanceof RouterSolicitation)) { + return; + } + if (pkt != null && (pkt instanceof NeighborSolicitation || + pkt instanceof NeighborAdvertisement)) { + // Duplicate Address Detection + if (ip.isZero()) { + return; + } + // NeighborSolicitation, NeighborAdvertisement + updateLocationIP(hid, srcMac, vlan, hloc, ip); + return; + } + } + + // multicast + if (eth.isMulticast()) { + return; + } + + // normal IPv6 packets + updateLocation(hid, srcMac, vlan, hloc); + } + } + } + + // Auxiliary listener to device events. + private class InternalDeviceListener implements DeviceListener { + @Override + public void event(DeviceEvent event) { + Device device = event.subject(); + switch (event.type()) { + case DEVICE_ADDED: + break; + case DEVICE_AVAILABILITY_CHANGED: + if (hostRemovalEnabled && + !deviceService.isAvailable(device.id())) { + removeHosts(hostService.getConnectedHosts(device.id())); + } + break; + case DEVICE_SUSPENDED: + case DEVICE_UPDATED: + // Nothing to do? + break; + case DEVICE_REMOVED: + if (hostRemovalEnabled) { + removeHosts(hostService.getConnectedHosts(device.id())); + } + break; + case PORT_ADDED: + break; + case PORT_UPDATED: + if (hostRemovalEnabled) { + ConnectPoint point = + new ConnectPoint(device.id(), event.port().number()); + removeHosts(hostService.getConnectedHosts(point)); + } + break; + case PORT_REMOVED: + // Nothing to do? + break; + default: + break; + } + } + } + + // Signals host vanish for all specified hosts. + private void removeHosts(Set<Host> hosts) { + for (Host host : hosts) { + providerService.hostVanished(host.id()); + } + } + +} diff --git a/framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/package-info.java b/framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/package-info.java new file mode 100644 index 00000000..57c5c3f6 --- /dev/null +++ b/framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses packet service as a means of host discovery and tracking. + */ +package org.onosproject.provider.host.impl; diff --git a/framework/src/onos/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java b/framework/src/onos/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java new file mode 100644 index 00000000..b9d90976 --- /dev/null +++ b/framework/src/onos/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java @@ -0,0 +1,369 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.host.impl; + +import com.google.common.collect.ImmutableSet; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.osgi.ComponentContextAdapter; +import org.onlab.packet.ARP; +import org.onlab.packet.ChassisId; +import org.onlab.packet.Ethernet; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultHost; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.HostProviderRegistry; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.host.HostServiceAdapter; +import org.onosproject.net.packet.DefaultInboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketServiceAdapter; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.topology.Topology; +import org.onosproject.net.topology.TopologyServiceAdapter; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Set; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; +import static org.onlab.packet.VlanId.vlanId; +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.HostId.hostId; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.device.DeviceEvent.Type.*; + +public class HostLocationProviderTest { + + private static final Integer INPORT = 10; + private static final String DEV1 = "of:1"; + private static final String DEV2 = "of:2"; + private static final String DEV3 = "of:3"; + + private static final VlanId VLAN = vlanId(); + private static final MacAddress MAC = MacAddress.valueOf("00:00:11:00:00:01"); + private static final MacAddress BCMAC = MacAddress.valueOf("ff:ff:ff:ff:ff:ff"); + private static final byte[] IP = new byte[]{10, 0, 0, 1}; + + private static final IpAddress IP_ADDRESS = + IpAddress.valueOf(IpAddress.Version.INET, IP); + private static final HostLocation LOCATION = + new HostLocation(deviceId(DEV1), portNumber(INPORT), 0L); + + private static final DefaultHost HOST = + new DefaultHost(ProviderId.NONE, hostId(MAC), MAC, + vlanId(VlanId.UNTAGGED), LOCATION, + ImmutableSet.of(IP_ADDRESS)); + + private static final ComponentContextAdapter CTX_FOR_REMOVE = + new ComponentContextAdapter() { + @Override + public Dictionary getProperties() { + Hashtable<String, String> props = new Hashtable<>(); + props.put("hostRemovalEnabled", "true"); + return props; + } + }; + + public static final ComponentContextAdapter CTX_FOR_NO_REMOVE = + new ComponentContextAdapter() { + @Override + public Dictionary getProperties() { + return new Hashtable(); + } + }; + + private final HostLocationProvider provider = new HostLocationProvider(); + private final TestHostRegistry hostRegistry = new TestHostRegistry(); + private final TestTopologyService topoService = new TestTopologyService(); + private final TestDeviceService deviceService = new TestDeviceService(); + private final TestHostService hostService = new TestHostService(); + private final TestPacketService packetService = new TestPacketService(); + + private PacketProcessor testProcessor; + private CoreService coreService; + private TestHostProviderService providerService; + + private ApplicationId appId = + new DefaultApplicationId(100, "org.onosproject.provider.host"); + + @Before + public void setUp() { + + coreService = createMock(CoreService.class); + expect(coreService.registerApplication(appId.name())) + .andReturn(appId).anyTimes(); + replay(coreService); + + provider.cfgService = new ComponentConfigAdapter(); + provider.coreService = coreService; + + provider.providerRegistry = hostRegistry; + provider.topologyService = topoService; + provider.packetService = packetService; + provider.deviceService = deviceService; + provider.hostService = hostService; + + provider.activate(CTX_FOR_NO_REMOVE); + } + + @Test + public void basics() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + } + + @Test + public void events() { + // new host + testProcessor.process(new TestPacketContext(DEV1)); + assertNotNull("new host expected", providerService.added); + assertNull("host motion unexpected", providerService.moved); + + // the host moved to new switch + testProcessor.process(new TestPacketContext(DEV2)); + assertNotNull("host motion expected", providerService.moved); + + // the host was misheard on a spine + testProcessor.process(new TestPacketContext(DEV3)); + assertNull("host misheard on spine switch", providerService.spine); + } + + @Test + public void removeHostByDeviceRemove() { + provider.modified(CTX_FOR_REMOVE); + testProcessor.process(new TestPacketContext(DEV1)); + Device device = new DefaultDevice(ProviderId.NONE, deviceId(DEV1), SWITCH, + "m", "h", "s", "n", new ChassisId(0L)); + deviceService.listener.event(new DeviceEvent(DEVICE_REMOVED, device)); + assertEquals("incorrect remove count", 1, providerService.removeCount); + } + + @Test + public void removeHostByDeviceOffline() { + provider.modified(CTX_FOR_REMOVE); + testProcessor.process(new TestPacketContext(DEV1)); + Device device = new DefaultDevice(ProviderId.NONE, deviceId(DEV1), SWITCH, + "m", "h", "s", "n", new ChassisId(0L)); + deviceService.listener.event(new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device)); + assertEquals("incorrect remove count", 1, providerService.removeCount); + } + + @Test + public void removeHostByDevicePortDown() { + provider.modified(CTX_FOR_REMOVE); + testProcessor.process(new TestPacketContext(DEV1)); + Device device = new DefaultDevice(ProviderId.NONE, deviceId(DEV1), SWITCH, + "m", "h", "s", "n", new ChassisId(0L)); + deviceService.listener.event(new DeviceEvent(PORT_UPDATED, device, + new DefaultPort(device, portNumber(INPORT), + false))); + assertEquals("incorrect remove count", 1, providerService.removeCount); + } + + + @After + public void tearDown() { + provider.deactivate(); + provider.coreService = null; + provider.providerRegistry = null; + } + + private class TestHostRegistry implements HostProviderRegistry { + + @Override + public HostProviderService register(HostProvider provider) { + providerService = new TestHostProviderService(provider); + return providerService; + } + + @Override + public void unregister(HostProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + } + + private class TestHostProviderService + extends AbstractProviderService<HostProvider> + implements HostProviderService { + + DeviceId added = null; + DeviceId moved = null; + DeviceId spine = null; + public int removeCount; + + protected TestHostProviderService(HostProvider provider) { + super(provider); + } + + @Override + public void hostDetected(HostId hostId, HostDescription hostDescription) { + DeviceId descr = hostDescription.location().deviceId(); + if (added == null) { + added = descr; + } else if ((moved == null) && !descr.equals(added)) { + moved = descr; + } else { + spine = descr; + } + } + + @Override + public void hostVanished(HostId hostId) { + removeCount++; + } + + } + + private class TestPacketService extends PacketServiceAdapter { + @Override + public void addProcessor(PacketProcessor processor, int priority) { + testProcessor = processor; + } + } + + + private class TestTopologyService extends TopologyServiceAdapter { + @Override + public boolean isInfrastructure(Topology topology, + ConnectPoint connectPoint) { + //simulate DPID3 as an infrastructure switch + if ((connectPoint.deviceId()).equals(deviceId(DEV3))) { + return true; + } + return false; + } + } + + private class TestPacketContext implements PacketContext { + + private final String deviceId; + + public TestPacketContext(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public long time() { + return 0; + } + + @Override + public InboundPacket inPacket() { + ARP arp = new ARP(); + arp.setSenderProtocolAddress(IP) + .setSenderHardwareAddress(MAC.toBytes()) + .setTargetHardwareAddress(BCMAC.toBytes()) + .setTargetProtocolAddress(IP); + + Ethernet eth = new Ethernet(); + eth.setEtherType(Ethernet.TYPE_ARP) + .setVlanID(VLAN.toShort()) + .setSourceMACAddress(MAC.toBytes()) + .setDestinationMACAddress(BCMAC) + .setPayload(arp); + ConnectPoint receivedFrom = new ConnectPoint(deviceId(deviceId), + portNumber(INPORT)); + return new DefaultInboundPacket(receivedFrom, eth, + ByteBuffer.wrap(eth.serialize())); + } + + @Override + public OutboundPacket outPacket() { + return null; + } + + @Override + public TrafficTreatment.Builder treatmentBuilder() { + return null; + } + + @Override + public void send() { + + } + + @Override + public boolean block() { + return false; + } + + @Override + public boolean isHandled() { + return false; + } + } + + private class TestDeviceService extends DeviceServiceAdapter { + private DeviceListener listener; + + @Override + public void addListener(DeviceListener listener) { + this.listener = listener; + } + + @Override + public Iterable<Device> getDevices() { + return Collections.emptyList(); + } + } + + private class TestHostService extends HostServiceAdapter { + @Override + public Set<Host> getConnectedHosts(ConnectPoint connectPoint) { + return ImmutableSet.of(HOST); + } + + @Override + public Set<Host> getConnectedHosts(DeviceId deviceId) { + return ImmutableSet.of(HOST); + } + + } +} diff --git a/framework/src/onos/providers/lldp/pom.xml b/framework/src/onos/providers/lldp/pom.xml new file mode 100644 index 00000000..bc13793e --- /dev/null +++ b/framework/src/onos/providers/lldp/pom.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-lldp-provider</artifactId> + <packaging>bundle</packaging> + + <description>ONOS LLDP Link Discovery</description> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + + </dependencies> +</project> diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LLDPLinkProvider.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LLDPLinkProvider.java new file mode 100644 index 00000000..e9e2bfad --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LLDPLinkProvider.java @@ -0,0 +1,452 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.lldp.impl; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.Ethernet; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.mastership.MastershipEvent; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Dictionary; +import java.util.EnumSet; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledExecutorService; + +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.onlab.util.Tools.get; +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to detect network + * infrastructure links. + */ +@Component(immediate = true) +public class LLDPLinkProvider extends AbstractProvider implements LinkProvider { + + private static final String PROVIDER_NAME = "org.onosproject.provider.lldp"; + + private static final String PROP_USE_BDDP = "useBDDP"; + private static final String PROP_DISABLE_LD = "disableLinkDiscovery"; + private static final String PROP_LLDP_SUPPRESSION = "lldpSuppression"; + + private static final String DEFAULT_LLDP_SUPPRESSION_CONFIG = "../config/lldp_suppression.json"; + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketService packetService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService masterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private LinkProviderService providerService; + + private ScheduledExecutorService executor; + + @Property(name = PROP_USE_BDDP, boolValue = true, + label = "Use BDDP for link discovery") + private boolean useBDDP = true; + + @Property(name = PROP_DISABLE_LD, boolValue = false, + label = "Permanently disable link discovery") + private boolean disableLinkDiscovery = false; + + private static final long INIT_DELAY = 5; + private static final long DELAY = 5; + + @Property(name = PROP_LLDP_SUPPRESSION, value = DEFAULT_LLDP_SUPPRESSION_CONFIG, + label = "Path to LLDP suppression configuration file") + private String lldpSuppression = DEFAULT_LLDP_SUPPRESSION_CONFIG; + + + private final InternalLinkProvider listener = new InternalLinkProvider(); + + private final InternalRoleListener roleListener = new InternalRoleListener(); + + protected final Map<DeviceId, LinkDiscovery> discoverers = new ConcurrentHashMap<>(); + + private SuppressionRules rules; + private ApplicationId appId; + + /** + * Creates an OpenFlow link provider. + */ + public LLDPLinkProvider() { + super(new ProviderId("lldp", PROVIDER_NAME)); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + appId = coreService.registerApplication(PROVIDER_NAME); + + // to load configuration at startup + modified(context); + if (disableLinkDiscovery) { + log.info("LinkDiscovery has been permanently disabled by configuration"); + return; + } + + providerService = providerRegistry.register(this); + deviceService.addListener(listener); + packetService.addProcessor(listener, PacketProcessor.advisor(0)); + masterService.addListener(roleListener); + + LinkDiscovery ld; + for (Device device : deviceService.getAvailableDevices()) { + if (rules.isSuppressed(device)) { + log.debug("LinkDiscovery from {} disabled by configuration", device.id()); + continue; + } + ld = new LinkDiscovery(device, packetService, masterService, + providerService, useBDDP); + discoverers.put(device.id(), ld); + addPorts(ld, device.id()); + } + + executor = newSingleThreadScheduledExecutor(groupedThreads("onos/device", "sync-%d")); + executor.scheduleAtFixedRate(new SyncDeviceInfoTask(), INIT_DELAY, DELAY, SECONDS); + + requestIntercepts(); + + log.info("Started"); + } + + private void addPorts(LinkDiscovery discoverer, DeviceId deviceId) { + for (Port p : deviceService.getPorts(deviceId)) { + if (rules.isSuppressed(p)) { + continue; + } + if (!p.number().isLogical()) { + discoverer.addPort(p); + } + } + } + + @Deactivate + public void deactivate() { + cfgService.unregisterProperties(getClass(), false); + if (disableLinkDiscovery) { + return; + } + + withdrawIntercepts(); + + providerRegistry.unregister(this); + deviceService.removeListener(listener); + packetService.removeProcessor(listener); + masterService.removeListener(roleListener); + + executor.shutdownNow(); + discoverers.values().forEach(LinkDiscovery::stop); + discoverers.clear(); + providerService = null; + + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + loadSuppressionRules(); + return; + } + @SuppressWarnings("rawtypes") + Dictionary properties = context.getProperties(); + + String s = get(properties, PROP_DISABLE_LD); + if (!Strings.isNullOrEmpty(s)) { + disableLinkDiscovery = Boolean.valueOf(s); + } + s = get(properties, PROP_USE_BDDP); + if (!Strings.isNullOrEmpty(s)) { + useBDDP = Boolean.valueOf(s); + } + s = get(properties, PROP_LLDP_SUPPRESSION); + if (!Strings.isNullOrEmpty(s)) { + lldpSuppression = s; + } + requestIntercepts(); + loadSuppressionRules(); + } + + private void loadSuppressionRules() { + SuppressionRulesStore store = new SuppressionRulesStore(lldpSuppression); + try { + log.info("Reading suppression rules from {}", lldpSuppression); + rules = store.read(); + } catch (IOException e) { + log.info("Failed to load {}, using built-in rules", lldpSuppression); + // default rule to suppress ROADM to maintain compatibility + rules = new SuppressionRules(ImmutableSet.of(), + EnumSet.of(Device.Type.ROADM), + ImmutableMap.of()); + } + + // should refresh discoverers when we need dynamic reconfiguration + } + + /** + * Request packet intercepts. + */ + private void requestIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_LLDP); + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + + selector.matchEthType(Ethernet.TYPE_BSN); + if (useBDDP) { + packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId); + } else { + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + } + + /** + * Withdraw packet intercepts. + */ + private void withdrawIntercepts() { + TrafficSelector.Builder selector = DefaultTrafficSelector.builder(); + selector.matchEthType(Ethernet.TYPE_LLDP); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + selector.matchEthType(Ethernet.TYPE_BSN); + packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId); + } + + private LinkDiscovery createLinkDiscovery(Device device) { + return new LinkDiscovery(device, packetService, masterService, + providerService, useBDDP); + } + + private class InternalRoleListener implements MastershipListener { + + @Override + public void event(MastershipEvent event) { + if (MastershipEvent.Type.BACKUPS_CHANGED.equals(event.type())) { + // only need new master events + return; + } + + DeviceId deviceId = event.subject(); + Device device = deviceService.getDevice(deviceId); + if (device == null) { + log.debug("Device {} doesn't exist, or isn't there yet", deviceId); + return; + } + if (rules.isSuppressed(device)) { + return; + } + synchronized (discoverers) { + if (!discoverers.containsKey(deviceId)) { + // ideally, should never reach here + log.debug("Device mastership changed ({}) {}", event.type(), deviceId); + discoverers.put(deviceId, createLinkDiscovery(device)); + } + } + } + + } + + private class InternalLinkProvider implements PacketProcessor, DeviceListener { + + @Override + public void event(DeviceEvent event) { + LinkDiscovery ld = null; + Device device = event.subject(); + Port port = event.port(); + if (device == null) { + log.error("Device is null."); + return; + } + log.trace("{} {} {}", event.type(), event.subject(), event); + final DeviceId deviceId = device.id(); + switch (event.type()) { + case DEVICE_ADDED: + case DEVICE_UPDATED: + synchronized (discoverers) { + ld = discoverers.get(deviceId); + if (ld == null) { + if (rules.isSuppressed(device)) { + log.debug("LinkDiscovery from {} disabled by configuration", device.id()); + return; + } + log.debug("Device added ({}) {}", event.type(), deviceId); + discoverers.put(deviceId, createLinkDiscovery(device)); + } else { + if (ld.isStopped()) { + log.debug("Device restarted ({}) {}", event.type(), deviceId); + ld.start(); + } + } + } + break; + case PORT_ADDED: + case PORT_UPDATED: + if (port.isEnabled()) { + ld = discoverers.get(deviceId); + if (ld == null) { + return; + } + if (rules.isSuppressed(port)) { + log.debug("LinkDiscovery from {}@{} disabled by configuration", + port.number(), device.id()); + return; + } + if (!port.number().isLogical()) { + log.debug("Port added {}", port); + ld.addPort(port); + } + } else { + log.debug("Port down {}", port); + ConnectPoint point = new ConnectPoint(deviceId, port.number()); + providerService.linksVanished(point); + } + break; + case PORT_REMOVED: + log.debug("Port removed {}", port); + ConnectPoint point = new ConnectPoint(deviceId, port.number()); + providerService.linksVanished(point); + + break; + case DEVICE_REMOVED: + case DEVICE_SUSPENDED: + log.debug("Device removed {}", deviceId); + ld = discoverers.get(deviceId); + if (ld == null) { + return; + } + ld.stop(); + providerService.linksVanished(deviceId); + break; + case DEVICE_AVAILABILITY_CHANGED: + ld = discoverers.get(deviceId); + if (ld == null) { + return; + } + if (deviceService.isAvailable(deviceId)) { + log.debug("Device up {}", deviceId); + ld.start(); + } else { + providerService.linksVanished(deviceId); + log.debug("Device down {}", deviceId); + ld.stop(); + } + break; + case PORT_STATS_UPDATED: + break; + default: + log.debug("Unknown event {}", event); + } + } + + @Override + public void process(PacketContext context) { + if (context == null) { + return; + } + LinkDiscovery ld = discoverers.get(context.inPacket().receivedFrom().deviceId()); + if (ld == null) { + return; + } + + if (ld.handleLLDP(context)) { + context.block(); + } + } + } + + private final class SyncDeviceInfoTask implements Runnable { + + @Override + public void run() { + if (Thread.currentThread().isInterrupted()) { + log.info("Interrupted, quitting"); + return; + } + // check what deviceService sees, to see if we are missing anything + try { + for (Device dev : deviceService.getDevices()) { + if (rules.isSuppressed(dev)) { + continue; + } + DeviceId did = dev.id(); + synchronized (discoverers) { + LinkDiscovery discoverer = discoverers.get(did); + if (discoverer == null) { + discoverer = createLinkDiscovery(dev); + discoverers.put(did, discoverer); + } + + addPorts(discoverer, did); + } + } + } catch (Exception e) { + // catch all Exception to avoid Scheduled task being suppressed. + log.error("Exception thrown during synchronization process", e); + } + } + } + +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java new file mode 100644 index 00000000..a81eeb1d --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/LinkDiscovery.java @@ -0,0 +1,369 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.lldp.impl; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ONOSLLDP; +import org.onlab.util.Timer; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link.Type; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketService; +import org.slf4j.Logger; + +import java.nio.ByteBuffer; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; + +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.flow.DefaultTrafficTreatment.builder; +import static org.slf4j.LoggerFactory.getLogger; + +// TODO: add 'fast discovery' mode: drop LLDPs in destination switch but listen for flow_removed messages +// FIXME: add ability to track links using port pairs or the link inventory + +/** + * Run discovery process from a physical switch. Ports are initially labeled as + * slow ports. When an LLDP is successfully received, label the remote port as + * fast. Every probeRate milliseconds, loop over all fast ports and send an + * LLDP, send an LLDP for a single slow port. Based on FlowVisor topology + * discovery implementation. + */ +public class LinkDiscovery implements TimerTask { + + private final Logger log = getLogger(getClass()); + + private static final short MAX_PROBE_COUNT = 3; // probes to send before link is removed + private static final short DEFAULT_PROBE_RATE = 3000; // millis + private static final String SRC_MAC = "DE:AD:BE:EF:BA:11"; + private static final String SERVICE_NULL = "Service cannot be null"; + + private final Device device; + + // send 1 probe every probeRate milliseconds + private final long probeRate = DEFAULT_PROBE_RATE; + + private final Set<Long> slowPorts = Sets.newConcurrentHashSet(); + // ports, known to have incoming links + private final Set<Long> fastPorts = Sets.newConcurrentHashSet(); + + // number of unacknowledged probes per port + private final Map<Long, AtomicInteger> portProbeCount = Maps.newHashMap(); + + private final ONOSLLDP lldpPacket; + private final Ethernet ethPacket; + private Ethernet bddpEth; + private final boolean useBDDP; + + private Timeout timeout; + private volatile boolean isStopped; + + private final LinkProviderService linkProvider; + private final PacketService pktService; + private final MastershipService mastershipService; + + /** + * Instantiates discovery manager for the given physical switch. Creates a + * generic LLDP packet that will be customized for the port it is sent out on. + * Starts the the timer for the discovery process. + * + * @param device the physical switch + * @param pktService packet service + * @param masterService mastership service + * @param providerService link provider service + * @param useBDDP flag to also use BDDP for discovery + */ + public LinkDiscovery(Device device, PacketService pktService, + MastershipService masterService, + LinkProviderService providerService, Boolean... useBDDP) { + this.device = device; + this.linkProvider = checkNotNull(providerService, SERVICE_NULL); + this.pktService = checkNotNull(pktService, SERVICE_NULL); + this.mastershipService = checkNotNull(masterService, SERVICE_NULL); + + lldpPacket = new ONOSLLDP(); + lldpPacket.setChassisId(device.chassisId()); + lldpPacket.setDevice(device.id().toString()); + + ethPacket = new Ethernet(); + ethPacket.setEtherType(Ethernet.TYPE_LLDP); + ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA); + ethPacket.setPayload(this.lldpPacket); + ethPacket.setPad(true); + + this.useBDDP = useBDDP.length > 0 ? useBDDP[0] : false; + if (this.useBDDP) { + bddpEth = new Ethernet(); + bddpEth.setPayload(lldpPacket); + bddpEth.setEtherType(Ethernet.TYPE_BSN); + bddpEth.setDestinationMACAddress(ONOSLLDP.BDDP_MULTICAST); + bddpEth.setPad(true); + log.info("Using BDDP to discover network"); + } + + isStopped = true; + start(); + log.debug("Started discovery manager for switch {}", device.id()); + + } + + /** + * Add physical port port to discovery process. + * Send out initial LLDP and label it as slow port. + * + * @param port the port + */ + public void addPort(Port port) { + boolean newPort = false; + synchronized (this) { + if (!containsPort(port.number().toLong())) { + newPort = true; + slowPorts.add(port.number().toLong()); + } + } + + boolean isMaster = mastershipService.isLocalMaster(device.id()); + if (newPort && isMaster) { + log.debug("Sending init probe to port {}@{}", port.number().toLong(), device.id()); + sendProbes(port.number().toLong()); + } + } + + /** + * Removes physical port from discovery process. + * + * @param port the port + */ + public void removePort(Port port) { + // Ignore ports that are not on this switch + long portnum = port.number().toLong(); + synchronized (this) { + if (slowPorts.contains(portnum)) { + slowPorts.remove(portnum); + + } else if (fastPorts.contains(portnum)) { + fastPorts.remove(portnum); + portProbeCount.remove(portnum); + // no iterator to update + } else { + log.warn("Tried to dynamically remove non-existing port {}", portnum); + } + } + } + + /** + * Method called by remote port to acknowledge receipt of LLDP sent by + * this port. If slow port, updates label to fast. If fast port, decrements + * number of unacknowledged probes. + * + * @param portNumber the port + */ + public void ackProbe(Long portNumber) { + synchronized (this) { + if (slowPorts.contains(portNumber)) { + log.debug("Setting slow port to fast: {}:{}", device.id(), portNumber); + slowPorts.remove(portNumber); + fastPorts.add(portNumber); + portProbeCount.put(portNumber, new AtomicInteger(0)); + } else if (fastPorts.contains(portNumber)) { + portProbeCount.get(portNumber).set(0); + } else { + log.debug("Got ackProbe for non-existing port: {}", portNumber); + } + } + } + + + /** + * Handles an incoming LLDP packet. Creates link in topology and sends ACK + * to port where LLDP originated. + * + * @param context packet context + * @return true if handled + */ + public boolean handleLLDP(PacketContext context) { + Ethernet eth = context.inPacket().parsed(); + if (eth == null) { + return false; + } + + ONOSLLDP onoslldp = ONOSLLDP.parseONOSLLDP(eth); + if (onoslldp != null) { + PortNumber srcPort = portNumber(onoslldp.getPort()); + PortNumber dstPort = context.inPacket().receivedFrom().port(); + DeviceId srcDeviceId = DeviceId.deviceId(onoslldp.getDeviceString()); + DeviceId dstDeviceId = context.inPacket().receivedFrom().deviceId(); + ackProbe(dstPort.toLong()); + + ConnectPoint src = new ConnectPoint(srcDeviceId, srcPort); + ConnectPoint dst = new ConnectPoint(dstDeviceId, dstPort); + + LinkDescription ld = eth.getEtherType() == Ethernet.TYPE_LLDP ? + new DefaultLinkDescription(src, dst, Type.DIRECT) : + new DefaultLinkDescription(src, dst, Type.INDIRECT); + + try { + linkProvider.linkDetected(ld); + } catch (IllegalStateException e) { + return true; + } + return true; + } + return false; + } + + + /** + * Execute this method every t milliseconds. Loops over all ports + * labeled as fast and sends out an LLDP. Send out an LLDP on a single slow + * port. + * + * @param t timeout + */ + @Override + public void run(Timeout t) { + if (isStopped()) { + return; + } + if (!mastershipService.isLocalMaster(device.id())) { + if (!isStopped()) { + // reschedule timer + timeout = Timer.getTimer().newTimeout(this, probeRate, MILLISECONDS); + } + return; + } + + log.trace("Sending probes from {}", device.id()); + synchronized (this) { + Iterator<Long> fastIterator = fastPorts.iterator(); + while (fastIterator.hasNext()) { + long portNumber = fastIterator.next(); + int probeCount = portProbeCount.get(portNumber).getAndIncrement(); + + if (probeCount < LinkDiscovery.MAX_PROBE_COUNT) { + log.trace("Sending fast probe to port {}", portNumber); + sendProbes(portNumber); + + } else { + // Link down, demote to slowPorts; update fast and slow ports + fastIterator.remove(); + slowPorts.add(portNumber); + portProbeCount.remove(portNumber); + + ConnectPoint cp = new ConnectPoint(device.id(), portNumber(portNumber)); + log.debug("Link down -> {}", cp); + linkProvider.linksVanished(cp); + } + } + + // send a probe for the next slow port + for (long portNumber : slowPorts) { + log.trace("Sending slow probe to port {}", portNumber); + sendProbes(portNumber); + } + } + + if (!isStopped()) { + // reschedule timer + timeout = Timer.getTimer().newTimeout(this, probeRate, MILLISECONDS); + } + } + + public synchronized void stop() { + isStopped = true; + timeout.cancel(); + } + + public synchronized void start() { + if (isStopped) { + isStopped = false; + timeout = Timer.getTimer().newTimeout(this, 0, MILLISECONDS); + } else { + log.warn("LinkDiscovery started multiple times?"); + } + } + + /** + * Creates packet_out LLDP for specified output port. + * + * @param port the port + * @return Packet_out message with LLDP data + */ + private OutboundPacket createOutBoundLLDP(Long port) { + if (port == null) { + return null; + } + lldpPacket.setPortId(port.intValue()); + ethPacket.setSourceMACAddress(SRC_MAC); + return new DefaultOutboundPacket(device.id(), + builder().setOutput(portNumber(port)).build(), + ByteBuffer.wrap(ethPacket.serialize())); + } + + /** + * Creates packet_out BDDP for specified output port. + * + * @param port the port + * @return Packet_out message with LLDP data + */ + private OutboundPacket createOutBoundBDDP(Long port) { + if (port == null) { + return null; + } + lldpPacket.setPortId(port.intValue()); + bddpEth.setSourceMACAddress(SRC_MAC); + return new DefaultOutboundPacket(device.id(), + builder().setOutput(portNumber(port)).build(), + ByteBuffer.wrap(bddpEth.serialize())); + } + + private void sendProbes(Long portNumber) { + log.trace("Sending probes out to {}@{}", portNumber, device.id()); + OutboundPacket pkt = createOutBoundLLDP(portNumber); + pktService.emit(pkt); + if (useBDDP) { + OutboundPacket bpkt = createOutBoundBDDP(portNumber); + pktService.emit(bpkt); + } + } + + public boolean containsPort(Long portNumber) { + return slowPorts.contains(portNumber) || fastPorts.contains(portNumber); + } + + public synchronized boolean isStopped() { + return isStopped || timeout.isCancelled(); + } + +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java new file mode 100644 index 00000000..27c75ebd --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRules.java @@ -0,0 +1,106 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.lldp.impl; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.onosproject.net.Annotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Element; +import org.onosproject.net.Port; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class SuppressionRules { + + public static final String ANY_VALUE = "(any)"; + + private final Set<DeviceId> suppressedDevice; + private final Set<Device.Type> suppressedDeviceType; + private final Map<String, String> suppressedAnnotation; + + public SuppressionRules(Set<DeviceId> suppressedDevice, + Set<Device.Type> suppressedType, + Map<String, String> suppressedAnnotation) { + + this.suppressedDevice = ImmutableSet.copyOf(suppressedDevice); + this.suppressedDeviceType = ImmutableSet.copyOf(suppressedType); + this.suppressedAnnotation = ImmutableMap.copyOf(suppressedAnnotation); + } + + public boolean isSuppressed(Device device) { + if (suppressedDevice.contains(device.id())) { + return true; + } + if (suppressedDeviceType.contains(device.type())) { + return true; + } + final Annotations annotations = device.annotations(); + if (containsSuppressionAnnotation(annotations)) { + return true; + } + return false; + } + + public boolean isSuppressed(Port port) { + Element parent = port.element(); + if (parent instanceof Device) { + if (isSuppressed((Device) parent)) { + return true; + } + } + + final Annotations annotations = port.annotations(); + if (containsSuppressionAnnotation(annotations)) { + return true; + } + return false; + } + + private boolean containsSuppressionAnnotation(final Annotations annotations) { + for (Entry<String, String> entry : suppressedAnnotation.entrySet()) { + final String suppValue = entry.getValue(); + final String suppKey = entry.getKey(); + if (suppValue == ANY_VALUE) { + if (annotations.keys().contains(suppKey)) { + return true; + } + } else { + if (suppValue.equals(annotations.value(suppKey))) { + return true; + } + } + } + return false; + } + + Set<DeviceId> getSuppressedDevice() { + return suppressedDevice; + } + + Set<Device.Type> getSuppressedDeviceType() { + return suppressedDeviceType; + } + + Map<String, String> getSuppressedAnnotation() { + return suppressedAnnotation; + } +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRulesStore.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRulesStore.java new file mode 100644 index 00000000..360bebd2 --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/SuppressionRulesStore.java @@ -0,0 +1,174 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.lldp.impl; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +import com.fasterxml.jackson.core.JsonEncoding; +import com.fasterxml.jackson.core.JsonFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.slf4j.Logger; + +import java.io.File; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/* + * JSON file example + * + +{ + "deviceId" : [ "of:2222000000000000" ], + "deviceType" : [ "ROADM" ], + "annotation" : { "no-lldp" : null, "sendLLDP" : "false" } +} + */ + +/** + * Allows for reading and writing LLDP suppression definition as a JSON file. + */ +public class SuppressionRulesStore { + + private static final String DEVICE_ID = "deviceId"; + private static final String DEVICE_TYPE = "deviceType"; + private static final String ANNOTATION = "annotation"; + + private final Logger log = getLogger(getClass()); + + private final File file; + + /** + * Creates a reader/writer of the LLDP suppression definition file. + * + * @param filePath location of the definition file + */ + public SuppressionRulesStore(String filePath) { + file = new File(filePath); + } + + /** + * Creates a reader/writer of the LLDP suppression definition file. + * + * @param file definition file + */ + public SuppressionRulesStore(File file) { + this.file = checkNotNull(file); + } + + /** + * Returns SuppressionRules. + * + * @return SuppressionRules + * @throws IOException if error occurred while reading the data + */ + public SuppressionRules read() throws IOException { + final Set<DeviceId> suppressedDevice = new HashSet<>(); + final EnumSet<Device.Type> suppressedDeviceType = EnumSet.noneOf(Device.Type.class); + final Map<String, String> suppressedAnnotation = new HashMap<>(); + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = (ObjectNode) mapper.readTree(file); + + for (JsonNode deviceId : root.get(DEVICE_ID)) { + if (deviceId.isTextual()) { + suppressedDevice.add(DeviceId.deviceId(deviceId.asText())); + } else { + log.warn("Encountered unexpected JSONNode {} for deviceId", deviceId); + } + } + + for (JsonNode deviceType : root.get(DEVICE_TYPE)) { + if (deviceType.isTextual()) { + suppressedDeviceType.add(Device.Type.valueOf(deviceType.asText())); + } else { + log.warn("Encountered unexpected JSONNode {} for deviceType", deviceType); + } + } + + JsonNode annotation = root.get(ANNOTATION); + if (annotation.isObject()) { + ObjectNode obj = (ObjectNode) annotation; + Iterator<Entry<String, JsonNode>> it = obj.fields(); + while (it.hasNext()) { + Entry<String, JsonNode> entry = it.next(); + final String key = entry.getKey(); + final JsonNode value = entry.getValue(); + + if (value.isValueNode()) { + if (value.isNull()) { + suppressedAnnotation.put(key, SuppressionRules.ANY_VALUE); + } else { + suppressedAnnotation.put(key, value.asText()); + } + } else { + log.warn("Encountered unexpected JSON field {} for annotation", entry); + } + } + } else { + log.warn("Encountered unexpected JSONNode {} for annotation", annotation); + } + + return new SuppressionRules(suppressedDevice, + suppressedDeviceType, + suppressedAnnotation); + } + + /** + * Writes the given SuppressionRules. + * + * @param rules SuppressionRules + * @throws IOException if error occurred while writing the data + */ + public void write(SuppressionRules rules) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode root = mapper.createObjectNode(); + ArrayNode deviceIds = mapper.createArrayNode(); + ArrayNode deviceTypes = mapper.createArrayNode(); + ObjectNode annotations = mapper.createObjectNode(); + root.set(DEVICE_ID, deviceIds); + root.set(DEVICE_TYPE, deviceTypes); + root.set(ANNOTATION, annotations); + + rules.getSuppressedDevice() + .forEach(deviceId -> deviceIds.add(deviceId.toString())); + + rules.getSuppressedDeviceType() + .forEach(type -> deviceTypes.add(type.toString())); + + rules.getSuppressedAnnotation().forEach((key, value) -> { + if (value == SuppressionRules.ANY_VALUE) { + annotations.putNull(key); + } else { + annotations.put(key, value); + } + }); + mapper.writeTree(new JsonFactory().createGenerator(file, JsonEncoding.UTF8), + root); + } +} diff --git a/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/package-info.java b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/package-info.java new file mode 100644 index 00000000..768a6cd2 --- /dev/null +++ b/framework/src/onos/providers/lldp/src/main/java/org/onosproject/provider/lldp/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses the core as a means of infrastructure link inference. + */ +package org.onosproject.provider.lldp.impl; diff --git a/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java new file mode 100644 index 00000000..1d63a15d --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/LLDPLinkProviderTest.java @@ -0,0 +1,502 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.lldp.impl; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ONOSLLDP; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.cluster.NodeId; +import org.onosproject.cluster.RoleInfo; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.core.DefaultApplicationId; +import org.onosproject.mastership.MastershipListener; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceServiceAdapter; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.packet.DefaultInboundPacket; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketProcessor; +import org.onosproject.net.packet.PacketServiceAdapter; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + +public class LLDPLinkProviderTest { + + private static final DeviceId DID1 = DeviceId.deviceId("of:0000000000000001"); + private static final DeviceId DID2 = DeviceId.deviceId("of:0000000000000002"); + + private static Port pd1; + private static Port pd2; + private static Port pd3; + private static Port pd4; + + private final LLDPLinkProvider provider = new LLDPLinkProvider(); + private final TestLinkRegistry linkService = new TestLinkRegistry(); + private final TestPacketService packetService = new TestPacketService(); + private final TestDeviceService deviceService = new TestDeviceService(); + private final TestMasterShipService masterService = new TestMasterShipService(); + + private CoreService coreService; + private TestLinkProviderService providerService; + + private PacketProcessor testProcessor; + private DeviceListener deviceListener; + + private ApplicationId appId = + new DefaultApplicationId(100, "org.onosproject.provider.lldp"); + + @Before + public void setUp() { + coreService = createMock(CoreService.class); + expect(coreService.registerApplication(appId.name())) + .andReturn(appId).anyTimes(); + replay(coreService); + + provider.cfgService = new ComponentConfigAdapter(); + provider.coreService = coreService; + + provider.deviceService = deviceService; + provider.packetService = packetService; + provider.providerRegistry = linkService; + provider.masterService = masterService; + + + provider.activate(null); + } + + @Test + public void basics() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + } + + @Test + public void switchAdd() { + DeviceEvent de = deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1); + deviceListener.event(de); + + assertFalse("Device not added", provider.discoverers.isEmpty()); + } + + @Test + public void switchRemove() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_REMOVED, DID1)); + + assertTrue("Discoverer is not gone", provider.discoverers.get(DID1).isStopped()); + assertTrue("Device is not gone.", vanishedDpid(DID1)); + } + + @Test + public void portUp() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 3, true))); + + assertTrue("Port not added to discoverer", + provider.discoverers.get(DID1).containsPort(3L)); + } + + @Test + public void portDown() { + + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID1, port(DID1, 1, false))); + + + + assertFalse("Port added to discoverer", + provider.discoverers.get(DID1).containsPort(1L)); + assertTrue("Port is not gone.", vanishedPort(1L)); + } + + @Test + public void portUnknown() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(portEvent(DeviceEvent.Type.PORT_ADDED, DID2, port(DID2, 1, false))); + + + assertNull("DeviceId exists", + provider.discoverers.get(DID2)); + } + + @Test + public void unknownPktCtx() { + + PacketContext pktCtx = new TestPacketContext(deviceService.getDevice(DID2)); + + testProcessor.process(pktCtx); + assertFalse("Context should still be free", pktCtx.isHandled()); + } + + @Test + public void knownPktCtx() { + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID1)); + deviceListener.event(deviceEvent(DeviceEvent.Type.DEVICE_ADDED, DID2)); + PacketContext pktCtx = new TestPacketContext(deviceService.getDevice(DID2)); + + + testProcessor.process(pktCtx); + + assertTrue("Link not detected", detectedLink(DID1, DID2)); + + } + + + @After + public void tearDown() { + provider.deactivate(); + provider.coreService = null; + provider.providerRegistry = null; + provider.deviceService = null; + provider.packetService = null; + } + + private DeviceEvent deviceEvent(DeviceEvent.Type type, DeviceId did) { + return new DeviceEvent(type, deviceService.getDevice(did)); + + } + + @SuppressWarnings(value = { "unused" }) + private DeviceEvent portEvent(DeviceEvent.Type type, DeviceId did, PortNumber port) { + return new DeviceEvent(type, deviceService.getDevice(did), + deviceService.getPort(did, port)); + } + + private DeviceEvent portEvent(DeviceEvent.Type type, DeviceId did, Port port) { + return new DeviceEvent(type, deviceService.getDevice(did), port); + } + + private Port port(DeviceId did, long port, boolean enabled) { + return new DefaultPort(deviceService.getDevice(did), + PortNumber.portNumber(port), enabled); + } + + + private boolean vanishedDpid(DeviceId... dids) { + for (int i = 0; i < dids.length; i++) { + if (!providerService.vanishedDpid.contains(dids[i])) { + return false; + } + } + return true; + } + + private boolean vanishedPort(Long... ports) { + for (int i = 0; i < ports.length; i++) { + if (!providerService.vanishedPort.contains(ports[i])) { + return false; + } + } + return true; + } + + private boolean detectedLink(DeviceId src, DeviceId dst) { + for (DeviceId key : providerService.discoveredLinks.keySet()) { + if (key.equals(src)) { + return providerService.discoveredLinks.get(src).equals(dst); + } + } + return false; + } + + + private class TestLinkRegistry implements LinkProviderRegistry { + + @Override + public LinkProviderService register(LinkProvider provider) { + providerService = new TestLinkProviderService(provider); + return providerService; + } + + @Override + public void unregister(LinkProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + } + + private class TestLinkProviderService + extends AbstractProviderService<LinkProvider> + implements LinkProviderService { + + List<DeviceId> vanishedDpid = Lists.newLinkedList(); + List<Long> vanishedPort = Lists.newLinkedList(); + Map<DeviceId, DeviceId> discoveredLinks = Maps.newHashMap(); + + protected TestLinkProviderService(LinkProvider provider) { + super(provider); + } + + @Override + public void linkDetected(LinkDescription linkDescription) { + DeviceId sDid = linkDescription.src().deviceId(); + DeviceId dDid = linkDescription.dst().deviceId(); + discoveredLinks.put(sDid, dDid); + } + + @Override + public void linkVanished(LinkDescription linkDescription) { + } + + @Override + public void linksVanished(ConnectPoint connectPoint) { + vanishedPort.add(connectPoint.port().toLong()); + + } + + @Override + public void linksVanished(DeviceId deviceId) { + vanishedDpid.add(deviceId); + } + + + } + + + + private class TestPacketContext implements PacketContext { + + protected Device device; + protected boolean blocked = false; + + public TestPacketContext(Device dev) { + device = dev; + } + + @Override + public long time() { + return 0; + } + + @Override + public InboundPacket inPacket() { + ONOSLLDP lldp = new ONOSLLDP(); + lldp.setChassisId(device.chassisId()); + lldp.setPortId((int) pd1.number().toLong()); + lldp.setDevice(deviceService.getDevice(DID1).id().toString()); + + + Ethernet ethPacket = new Ethernet(); + ethPacket.setEtherType(Ethernet.TYPE_LLDP); + ethPacket.setDestinationMACAddress(ONOSLLDP.LLDP_NICIRA); + ethPacket.setPayload(lldp); + ethPacket.setPad(true); + + + + ethPacket.setSourceMACAddress("DE:AD:BE:EF:BA:11"); + + ConnectPoint cp = new ConnectPoint(device.id(), pd3.number()); + + return new DefaultInboundPacket(cp, ethPacket, + ByteBuffer.wrap(ethPacket.serialize())); + + } + + @Override + public OutboundPacket outPacket() { + return null; + } + + @Override + public TrafficTreatment.Builder treatmentBuilder() { + return null; + } + + @Override + public void send() { + + } + + @Override + public boolean block() { + blocked = true; + return blocked; + } + + @Override + public boolean isHandled() { + return blocked; + } + + } + + private class TestPacketService extends PacketServiceAdapter { + @Override + public void addProcessor(PacketProcessor processor, int priority) { + testProcessor = processor; + } + } + + private class TestDeviceService extends DeviceServiceAdapter { + + private Map<DeviceId, Device> devices = new HashMap<>(); + private final ArrayListMultimap<DeviceId, Port> ports = + ArrayListMultimap.create(); + + public TestDeviceService() { + Device d1 = new DefaultDevice(ProviderId.NONE, DID1, Device.Type.SWITCH, + "TESTMF", "TESTHW", "TESTSW", "TESTSN", new ChassisId()); + Device d2 = new DefaultDevice(ProviderId.NONE, DID2, Device.Type.SWITCH, + "TESTMF", "TESTHW", "TESTSW", "TESTSN", new ChassisId()); + devices.put(DID1, d1); + devices.put(DID2, d2); + + pd1 = new DefaultPort(d1, PortNumber.portNumber(1), true); + pd2 = new DefaultPort(d1, PortNumber.portNumber(2), true); + pd3 = new DefaultPort(d2, PortNumber.portNumber(1), true); + pd4 = new DefaultPort(d2, PortNumber.portNumber(2), true); + + ports.putAll(DID1, Lists.newArrayList(pd1, pd2)); + ports.putAll(DID2, Lists.newArrayList(pd3, pd4)); + } + + @Override + public int getDeviceCount() { + return devices.values().size(); + } + + @Override + public Iterable<Device> getDevices() { + return Collections.emptyList(); + } + + @Override + public Device getDevice(DeviceId deviceId) { + return devices.get(deviceId); + } + + @Override + public MastershipRole getRole(DeviceId deviceId) { + return MastershipRole.MASTER; + } + + @Override + public List<Port> getPorts(DeviceId deviceId) { + return ports.get(deviceId); + } + + @Override + public Port getPort(DeviceId deviceId, PortNumber portNumber) { + for (Port p : ports.get(deviceId)) { + if (p.number().equals(portNumber)) { + return p; + } + } + return null; + } + + @Override + public boolean isAvailable(DeviceId deviceId) { + return true; + } + + @Override + public void addListener(DeviceListener listener) { + deviceListener = listener; + + } + + @Override + public void removeListener(DeviceListener listener) { + + } + } + + private final class TestMasterShipService implements MastershipService { + + @Override + public MastershipRole getLocalRole(DeviceId deviceId) { + return MastershipRole.MASTER; + } + + @Override + public CompletableFuture<MastershipRole> requestRoleFor(DeviceId deviceId) { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture<Void> relinquishMastership(DeviceId deviceId) { + return null; + } + + @Override + public NodeId getMasterFor(DeviceId deviceId) { + return null; + } + + @Override + public Set<DeviceId> getDevicesOf(NodeId nodeId) { + return null; + } + + @Override + public void addListener(MastershipListener listener) { + + } + + @Override + public void removeListener(MastershipListener listener) { + + } + + @Override + public RoleInfo getNodesFor(DeviceId deviceId) { + return new RoleInfo(new NodeId("foo"), Collections.<NodeId>emptyList()); + } + } + + +} diff --git a/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesStoreTest.java b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesStoreTest.java new file mode 100644 index 00000000..0ac31123 --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesStoreTest.java @@ -0,0 +1,80 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.lldp.impl; + +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; + +import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.onosproject.net.Device; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.io.Resources; + +public class SuppressionRulesStoreTest { + + @Rule + public TemporaryFolder tempFolder = new TemporaryFolder(); + + // "lldp_suppression.json" + SuppressionRules testData + = new SuppressionRules(ImmutableSet.of(deviceId("of:2222000000000000")), + ImmutableSet.of(Device.Type.ROADM), + ImmutableMap.of("no-lldp", SuppressionRules.ANY_VALUE, + "sendLLDP", "false")); + + private static void assertRulesEqual(SuppressionRules expected, SuppressionRules actual) { + assertEquals(expected.getSuppressedDevice(), + actual.getSuppressedDevice()); + assertEquals(expected.getSuppressedDeviceType(), + actual.getSuppressedDeviceType()); + assertEquals(expected.getSuppressedAnnotation(), + actual.getSuppressedAnnotation()); + } + + @Test + public void testRead() throws URISyntaxException, IOException { + Path path = Paths.get(Resources.getResource("lldp_suppression.json").toURI()); + + SuppressionRulesStore store = new SuppressionRulesStore(path.toString()); + + SuppressionRules rules = store.read(); + + assertRulesEqual(testData, rules); + } + + @Test + public void testWrite() throws IOException { + File newFile = tempFolder.newFile(); + SuppressionRulesStore store = new SuppressionRulesStore(newFile); + store.write(testData); + + SuppressionRulesStore reload = new SuppressionRulesStore(newFile); + SuppressionRules rules = reload.read(); + + assertRulesEqual(testData, rules); + } +} diff --git a/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java new file mode 100644 index 00000000..52f0bb1e --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/java/org/onosproject/provider/lldp/impl/SuppressionRulesTest.java @@ -0,0 +1,147 @@ +package org.onosproject.provider.lldp.impl; + +import static org.junit.Assert.*; +import static org.onosproject.net.DeviceId.deviceId; + +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onosproject.net.Annotations; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.DefaultPort; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class SuppressionRulesTest { + + private static final DeviceId NON_SUPPRESSED_DID = deviceId("of:1111000000000000"); + private static final DeviceId SUPPRESSED_DID = deviceId("of:2222000000000000"); + private static final ProviderId PID = new ProviderId("of", "foo"); + private static final String MFR = "whitebox"; + private static final String HW = "1.1.x"; + private static final String SW1 = "3.8.1"; + private static final String SN = "43311-12345"; + private static final ChassisId CID = new ChassisId(); + + private static final PortNumber P1 = PortNumber.portNumber(1); + + private SuppressionRules rules; + + @Before + public void setUp() throws Exception { + rules = new SuppressionRules(ImmutableSet.of(SUPPRESSED_DID), + ImmutableSet.of(Device.Type.ROADM), + ImmutableMap.of("no-lldp", SuppressionRules.ANY_VALUE, + "sendLLDP", "false")); + } + + @Test + public void testSuppressedDeviceId() { + Device device = new DefaultDevice(PID, + SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedDeviceType() { + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.ROADM, + MFR, HW, SW1, SN, CID); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedDeviceAnnotation() { + Annotations annotation = DefaultAnnotations.builder() + .set("no-lldp", "random") + .build(); + + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID, annotation); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedDeviceAnnotationExact() { + Annotations annotation = DefaultAnnotations.builder() + .set("sendLLDP", "false") + .build(); + + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID, annotation); + assertTrue(rules.isSuppressed(device)); + } + + @Test + public void testNotSuppressedDevice() { + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + assertFalse(rules.isSuppressed(device)); + } + + @Test + public void testSuppressedPortOnSuppressedDevice() { + Device device = new DefaultDevice(PID, + SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true); + + assertTrue(rules.isSuppressed(port)); + } + + @Test + public void testSuppressedPortAnnotation() { + Annotations annotation = DefaultAnnotations.builder() + .set("no-lldp", "random") + .build(); + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true, annotation); + + assertTrue(rules.isSuppressed(port)); + } + + @Test + public void testSuppressedPortAnnotationExact() { + Annotations annotation = DefaultAnnotations.builder() + .set("sendLLDP", "false") + .build(); + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true, annotation); + + assertTrue(rules.isSuppressed(port)); + } + + @Test + public void testNotSuppressedPort() { + Device device = new DefaultDevice(PID, + NON_SUPPRESSED_DID, + Device.Type.SWITCH, + MFR, HW, SW1, SN, CID); + Port port = new DefaultPort(device, P1, true); + + assertFalse(rules.isSuppressed(port)); + } +} diff --git a/framework/src/onos/providers/lldp/src/test/resources/lldp_suppression.json b/framework/src/onos/providers/lldp/src/test/resources/lldp_suppression.json new file mode 100644 index 00000000..062e73aa --- /dev/null +++ b/framework/src/onos/providers/lldp/src/test/resources/lldp_suppression.json @@ -0,0 +1,6 @@ +{ + "deviceId" : [ "of:2222000000000000" ], + "deviceType" : [ "ROADM" ], + "annotation" : { "no-lldp" : null, "sendLLDP" : "false" } +} + diff --git a/framework/src/onos/providers/netconf/app/app.xml b/framework/src/onos/providers/netconf/app/app.xml new file mode 100644 index 00000000..f2d47627 --- /dev/null +++ b/framework/src/onos/providers/netconf/app/app.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<app name="org.onosproject.netconf" origin="ON.Lab" version="${project.version}" + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" + features="${project.artifactId}"> + <description>${project.description}</description> + + <artifact>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</artifact> + <!-- Question: should there be the jnc stuff here? Or is it just for testing --> +</app> diff --git a/framework/src/onos/providers/netconf/app/features.xml b/framework/src/onos/providers/netconf/app/features.xml new file mode 100644 index 00000000..6a3d1a2c --- /dev/null +++ b/framework/src/onos/providers/netconf/app/features.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> + <feature name="${project.artifactId}" version="${project.version}" + description="${project.description}"> + <feature>onos-api</feature> + <bundle>mvn:io.netty/netty/3.9.2.Final</bundle> + <bundle>mvn:${project.groupId}/onos-netconf-provider-device/${project.version}</bundle> + <!-- Question: should there be the jnc stuff here? Or is it just for testing --> + </feature> +</features> + diff --git a/framework/src/onos/providers/netconf/app/pom.xml b/framework/src/onos/providers/netconf/app/pom.xml new file mode 100644 index 00000000..8cf56b3f --- /dev/null +++ b/framework/src/onos/providers/netconf/app/pom.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-netconf</artifactId> + <packaging>pom</packaging> + + <description>NetConf protocol southbound providers</description> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-provider-device</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-provider-flow</artifactId> + <version>${project.version}</version> + </dependency> + <!-- TODO: add other dependencies here as more bundles are added to the app --> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/netconf/device/pom.xml b/framework/src/onos/providers/netconf/device/pom.xml new file mode 100644 index 00000000..be05e3cd --- /dev/null +++ b/framework/src/onos/providers/netconf/device/pom.xml @@ -0,0 +1,165 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-netconf-provider-device</artifactId> + <packaging>bundle</packaging> + + <description>ONOS Netconf protocol device provider</description> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>ch.ethz.ganymed</groupId> + <artifactId>ganymed-ssh2</artifactId> + <version>262</version> + </dependency> + <dependency> + <!-- TODO: change this appropriately when the official TailF JNC is available --> + <groupId>org.onosproject</groupId> + <artifactId>jnc</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.jdom</groupId> + <artifactId>jdom2</artifactId> + <version>2.0.5</version> + </dependency> + <dependency> + <groupId>jaxen</groupId> + <artifactId>jaxen</artifactId> + <version>1.1.4</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <filters> + <filter> + <artifact>com.tailf:JNC</artifact> + <includes> + <include>com/tailf/jnc/**</include> + </includes> + </filter> + <filter> + <artifact>ch.ethz.ganymed:ganymed-ssh2</artifact> + <includes> + <include>ch/ethz/ssh2/**</include> + </includes> + </filter> + <filter> + <artifact>org.jdom:jdom2</artifact> + <includes> + <include>org/jdom2/**</include> + </includes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <configuration> + <instructions> + <Export-Package> + com.tailf.jnc, + ch.ethz.ssh2, + ch.ethz.ssh2.auth, + ch.ethz.ssh2.channel, + ch.ethz.ssh2.crypto, + ch.ethz.ssh2.crypto.cipher, + ch.ethz.ssh2.crypto.dh, + ch.ethz.ssh2.crypto.digest, + ch.ethz.ssh2.log, + ch.ethz.ssh2.packets, + ch.ethz.ssh2.server, + ch.ethz.ssh2.sftp, + ch.ethz.ssh2.signature, + ch.ethz.ssh2.transport, + ch.ethz.ssh2.util, + org.jdom2, + org.jdom2.input, + org.jdom2.output, + org.jdom2.adapters, + org.jdom2.filter, + org.jdom2.internal, + org.jdom2.located, + org.jdom2.transform, + org.jdom2.util, + org.jdom2.xpath, + org.jdom2.input.sax, + org.jdom2.input.stax, + org.jdom2.output.support, + org.jdom2.xpath.jaxen, + org.jdom2.xpath.util + </Export-Package> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.onosproject</groupId> + <artifactId>onos-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java new file mode 100644 index 00000000..b3d26b06 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDevice.java @@ -0,0 +1,304 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.device.impl; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onlab.util.Tools.delay; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.input.SAXBuilder; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.slf4j.Logger; + +import com.tailf.jnc.Capabilities; +import com.tailf.jnc.JNCException; +import com.tailf.jnc.SSHConnection; +import com.tailf.jnc.SSHSession; + +/** + * This is a logical representation of actual NETCONF device, carrying all the + * necessary information to connect and execute NETCONF operations. + */ +public class NetconfDevice { + private final Logger log = getLogger(NetconfDevice.class); + + /** + * The Device State is used to determine whether the device is active or + * inactive. This state infomation will help Device Creator to add or delete + * the device from the core. + */ + public static enum DeviceState { + /* Used to specify Active state of the device */ + ACTIVE, + /* Used to specify inactive state of the device */ + INACTIVE, + /* Used to specify invalid state of the device */ + INVALID + } + + private static final int DEFAULT_SSH_PORT = 22; + private static final int DEFAULT_CON_TIMEOUT = 0; + private static final String XML_CAPABILITY_KEY = "capability"; + private static final int EVENTINTERVAL = 2000; + private static final int CONNECTION_CHECK_INTERVAL = 3; + private static final String INPUT_HELLO_XML_MSG = new StringBuilder( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>") + .append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">") + .append("<capabilities><capability>urn:ietf:params:netconf:base:1.0</capability>") + .append("</capabilities></hello>").toString(); + + private String sshHost; + private int sshPort = DEFAULT_SSH_PORT; + private int connectTimeout = DEFAULT_CON_TIMEOUT; + private String username; + private String password; + private boolean reachable = false; + + private List<String> capabilities = new ArrayList<String>(); + private SSHConnection sshConnection = null; + + private DeviceState deviceState = DeviceState.INVALID; + + protected NetconfDevice(String sshHost, int sshPort, String username, + String password) { + this.username = checkNotNull(username, + "Netconf Username Cannot be null"); + this.sshHost = checkNotNull(sshHost, "Netconf Device IP cannot be null"); + this.sshPort = checkNotNull(sshPort, + "Netconf Device SSH port cannot be null"); + this.password = password; + } + + /** + * This will try to connect to NETCONF device and find all the capabilities. + * + * @throws Exception if unable to connect to the device + */ + // FIXME: this should not be a generic Exception; perhaps wrap in some RuntimeException + public void init() throws Exception { + try { + if (sshConnection == null) { + sshConnection = new SSHConnection(sshHost, sshPort, connectTimeout); + sshConnection.authenticateWithPassword(username, password); + } + // Send hello message to retrieve capabilities. + } catch (IOException e) { + log.error("Fatal Error while creating connection to the device: " + + deviceInfo(), e); + throw e; + } catch (JNCException e) { + log.error("Failed to connect to the device: " + deviceInfo(), e); + throw e; + } + + hello(); + } + + private void hello() { + SSHSession ssh = null; + try { + ssh = new SSHSession(sshConnection); + String helloRequestXML = INPUT_HELLO_XML_MSG.trim(); + + log.debug("++++++++++++++++++++++++++++++++++Sending Hello: " + + sshConnection.getGanymedConnection().getHostname() + + "++++++++++++++++++++++++++++++++++"); + printPrettyXML(helloRequestXML); + ssh.print(helloRequestXML); + // ssh.print(endCharSeq); + ssh.flush(); + String xmlResponse = null; + int i = CONNECTION_CHECK_INTERVAL; + while (!ssh.ready() && i > 0) { + delay(EVENTINTERVAL); + i--; + } + + if (ssh.ready()) { + StringBuffer readOne = ssh.readOne(); + if (readOne == null) { + log.error("The Hello Contains No Capabilites"); + throw new JNCException( + JNCException.SESSION_ERROR, + "server does not support NETCONF base capability: " + + Capabilities.NETCONF_BASE_CAPABILITY); + } else { + xmlResponse = readOne.toString().trim(); + + log.debug("++++++++++++++++++++++++++++++++++Reading Capabilities: " + + sshConnection.getGanymedConnection() + .getHostname() + + "++++++++++++++++++++++++++++++++++"); + + printPrettyXML(xmlResponse); + processCapabilities(xmlResponse); + } + } + reachable = true; + } catch (IOException e) { + log.error("Fatal Error while sending Hello Message to the device: " + + deviceInfo(), e); + } catch (JNCException e) { + log.error("Fatal Error while sending Hello Message to the device: " + + deviceInfo(), e); + } finally { + log.debug("Closing the session after successful execution"); + if (ssh != null) { + ssh.close(); + } + } + } + + private void processCapabilities(String xmlResponse) throws JNCException { + if (xmlResponse.isEmpty()) { + log.error("The capability response cannot be empty"); + throw new JNCException( + JNCException.SESSION_ERROR, + "server does not support NETCONF base capability: " + + Capabilities.NETCONF_BASE_CAPABILITY); + } + try { + Document doc = new SAXBuilder() + .build(new StringReader(xmlResponse)); + Element rootElement = doc.getRootElement(); + processCapabilities(rootElement); + } catch (Exception e) { + log.error("ERROR while parsing the XML " + xmlResponse); + } + } + + private void processCapabilities(Element rootElement) { + List<Element> children = rootElement.getChildren(); + if (children.isEmpty()) { + return; + } + for (Element child : children) { + + if (child.getName().equals(XML_CAPABILITY_KEY)) { + capabilities.add(child.getValue()); + } + if (!child.getChildren().isEmpty()) { + processCapabilities(child); + } + } + } + + private void printPrettyXML(String xmlstring) { + try { + Document doc = new SAXBuilder().build(new StringReader(xmlstring)); + XMLOutputter xmOut = new XMLOutputter(Format.getPrettyFormat()); + String outputString = xmOut.outputString(doc); + log.debug(outputString); + } catch (Exception e) { + log.error("ERROR while parsing the XML " + xmlstring, e); + + } + } + + /** + * This would return host IP and host Port, used by this particular Netconf + * Device. + * @return Device Information. + */ + public String deviceInfo() { + return new StringBuilder("host: ").append(sshHost).append(". port: ") + .append(sshPort).toString(); + } + + /** + * This will terminate the device connection. + */ + public void disconnect() { + sshConnection.close(); + reachable = false; + } + + /** + * This will list down all the capabilities supported on the device. + * @return Capability list. + */ + public List<String> getCapabilities() { + return capabilities; + } + + /** + * This api is intended to know whether the device is connected or not. + * @return true if connected + */ + public boolean isReachable() { + return reachable; + } + + /** + * This will return the IP used connect ssh on the device. + * @return Netconf Device IP + */ + public String getSshHost() { + return sshHost; + } + + /** + * This will return the SSH Port used connect the device. + * @return SSH Port number + */ + public int getSshPort() { + return sshPort; + } + + /** + * The usename used to connect Netconf Device. + * @return Device Username + */ + public String getUsername() { + return username; + } + + /** + * Retrieve current state of the device. + * @return Current Device State + */ + public DeviceState getDeviceState() { + return deviceState; + } + + /** + * This is set the state information for the device. + * @param deviceState Next Device State + */ + public void setDeviceState(DeviceState deviceState) { + this.deviceState = deviceState; + } + + /** + * Check whether the device is in Active state. + * @return true if the device is Active + */ + public boolean isActive() { + return deviceState == DeviceState.ACTIVE ? true : false; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } +} diff --git a/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java new file mode 100644 index 00000000..f9194a7e --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProvider.java @@ -0,0 +1,358 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.device.impl; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onlab.util.Tools.delay; +import static org.onlab.util.Tools.get; +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Dictionary; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.ChassisId; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cluster.ClusterService; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.provider.netconf.device.impl.NetconfDevice.DeviceState; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +/** + * Provider which will try to fetch the details of NETCONF devices from the core + * and run a capability discovery on each of the device. + */ +@Component(immediate = true) +public class NetconfDeviceProvider extends AbstractProvider + implements DeviceProvider { + + private final Logger log = getLogger(NetconfDeviceProvider.class); + + protected Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<DeviceId, NetconfDevice>(); + + private DeviceProviderService providerService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private ExecutorService deviceBuilder = Executors + .newFixedThreadPool(1, groupedThreads("onos/netconf", "device-creator")); + + // Delay between events in ms. + private static final int EVENTINTERVAL = 5; + + private static final String SCHEME = "netconf"; + + @Property(name = "devConfigs", value = "", label = "Instance-specific configurations") + private String devConfigs = null; + + @Property(name = "devPasswords", value = "", label = "Instance-specific password") + private String devPasswords = null; + + /** + * Creates a provider with the supplier identifier. + */ + public NetconfDeviceProvider() { + super(new ProviderId("netconf", "org.onosproject.provider.netconf")); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + providerService = providerRegistry.register(this); + modified(context); + log.info("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + cfgService.unregisterProperties(getClass(), false); + try { + for (Entry<DeviceId, NetconfDevice> deviceEntry : netconfDeviceMap + .entrySet()) { + deviceBuilder.submit(new DeviceCreator(deviceEntry.getValue(), + false)); + } + deviceBuilder.awaitTermination(1000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.error("Device builder did not terminate"); + } + deviceBuilder.shutdownNow(); + netconfDeviceMap.clear(); + providerRegistry.unregister(this); + providerService = null; + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + log.info("No configuration file"); + return; + } + Dictionary<?, ?> properties = context.getProperties(); + String deviceCfgValue = get(properties, "devConfigs"); + log.info("Settings: devConfigs={}", deviceCfgValue); + if (!isNullOrEmpty(deviceCfgValue)) { + addOrRemoveDevicesConfig(deviceCfgValue); + } + } + + private void addOrRemoveDevicesConfig(String deviceConfig) { + for (String deviceEntry : deviceConfig.split(",")) { + NetconfDevice device = processDeviceEntry(deviceEntry); + if (device != null) { + log.info("Device Detail: username: {}, host={}, port={}, state={}", + device.getUsername(), device.getSshHost(), + device.getSshPort(), device.getDeviceState().name()); + if (device.isActive()) { + deviceBuilder.submit(new DeviceCreator(device, true)); + } else { + deviceBuilder.submit(new DeviceCreator(device, false)); + } + } + } + } + + private NetconfDevice processDeviceEntry(String deviceEntry) { + if (deviceEntry == null) { + log.info("No content for Device Entry, so cannot proceed further."); + return null; + } + log.info("Trying to convert Device Entry String: " + deviceEntry + + " to a Netconf Device Object"); + NetconfDevice device = null; + try { + String userInfo = deviceEntry.substring(0, deviceEntry + .lastIndexOf('@')); + String hostInfo = deviceEntry.substring(deviceEntry + .lastIndexOf('@') + 1); + String[] infoSplit = userInfo.split(":"); + String username = infoSplit[0]; + String password = infoSplit[1]; + infoSplit = hostInfo.split(":"); + String hostIp = infoSplit[0]; + Integer hostPort; + try { + hostPort = Integer.parseInt(infoSplit[1]); + } catch (NumberFormatException nfe) { + log.error("Bad Configuration Data: Failed to parse host port number string: " + + infoSplit[1]); + throw nfe; + } + String deviceState = infoSplit[2]; + if (isNullOrEmpty(username) || isNullOrEmpty(password) + || isNullOrEmpty(hostIp) || hostPort == 0) { + log.warn("Bad Configuration Data: both user and device information parts of Configuration " + + deviceEntry + " should be non-nullable"); + } else { + device = new NetconfDevice(hostIp, hostPort, username, password); + if (!isNullOrEmpty(deviceState)) { + if (deviceState.toUpperCase().equals(DeviceState.ACTIVE + .name())) { + device.setDeviceState(DeviceState.ACTIVE); + } else if (deviceState.toUpperCase() + .equals(DeviceState.INACTIVE.name())) { + device.setDeviceState(DeviceState.INACTIVE); + } else { + log.warn("Device State Information can not be empty, so marking the state as INVALID"); + device.setDeviceState(DeviceState.INVALID); + } + } else { + log.warn("The device entry do not specify state information, so marking the state as INVALID"); + device.setDeviceState(DeviceState.INVALID); + } + } + } catch (ArrayIndexOutOfBoundsException aie) { + log.error("Error while reading config infromation from the config file: " + + "The user, host and device state infomation should be " + + "in the order 'userInfo@hostInfo:deviceState'" + + deviceEntry, aie); + } catch (Exception e) { + log.error("Error while parsing config information for the device entry: " + + deviceEntry, e); + } + return device; + } + + @Override + public void triggerProbe(DeviceId deviceId) { + // TODO Auto-generated method stub + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + + } + + @Override + public boolean isReachable(DeviceId deviceId) { + NetconfDevice netconfDevice = netconfDeviceMap.get(deviceId); + if (netconfDevice == null) { + log.warn("BAD REQUEST: the requested device id: " + + deviceId.toString() + + " is not associated to any NETCONF Device"); + return false; + } + return netconfDevice.isReachable(); + } + + /** + * This class is intended to add or remove Configured Netconf Devices. + * Functionality relies on 'createFlag' and 'NetconfDevice' content. The + * functionality runs as a thread and dependening on the 'createFlag' value + * it will create or remove Device entry from the core. + */ + private class DeviceCreator implements Runnable { + + private NetconfDevice device; + private boolean createFlag; + + public DeviceCreator(NetconfDevice device, boolean createFlag) { + this.device = device; + this.createFlag = createFlag; + } + + @Override + public void run() { + if (createFlag) { + log.info("Trying to create Device Info on ONOS core"); + advertiseDevices(); + } else { + log.info("Trying to remove Device Info on ONOS core"); + removeDevices(); + } + } + + /** + * For each Netconf Device, remove the entry from the device store. + */ + private void removeDevices() { + if (device == null) { + log.warn("The Request Netconf Device is null, cannot proceed further"); + return; + } + try { + DeviceId did = getDeviceId(); + if (!netconfDeviceMap.containsKey(did)) { + log.error("BAD Request: 'Currently device is not discovered, " + + "so cannot remove/disconnect the device: " + + device.deviceInfo() + "'"); + return; + } + providerService.deviceDisconnected(did); + device.disconnect(); + netconfDeviceMap.remove(did); + delay(EVENTINTERVAL); + } catch (URISyntaxException uriSyntaxExcpetion) { + log.error("Syntax Error while creating URI for the device: " + + device.deviceInfo() + + " couldn't remove the device from the store", + uriSyntaxExcpetion); + } + } + + /** + * Initialize Netconf Device object, and notify core saying device + * connected. + */ + private void advertiseDevices() { + try { + if (device == null) { + log.warn("The Request Netconf Device is null, cannot proceed further"); + return; + } + device.init(); + DeviceId did = getDeviceId(); + ChassisId cid = new ChassisId(); + DeviceDescription desc = new DefaultDeviceDescription( + did.uri(), + Device.Type.OTHER, + "", "", + "", "", + cid); + log.info("Persisting Device" + did.uri().toString()); + + netconfDeviceMap.put(did, device); + providerService.deviceConnected(did, desc); + log.info("Done with Device Info Creation on ONOS core. Device Info: " + + device.deviceInfo() + " " + did.uri().toString()); + delay(EVENTINTERVAL); + } catch (URISyntaxException e) { + log.error("Syntax Error while creating URI for the device: " + + device.deviceInfo() + + " couldn't persist the device onto the store", e); + } catch (SocketTimeoutException e) { + log.error("Error while setting connection for the device: " + + device.deviceInfo(), e); + } catch (IOException e) { + log.error("Error while setting connection for the device: " + + device.deviceInfo(), e); + } catch (Exception e) { + log.error("Error while initializing session for the device: " + + (device != null ? device.deviceInfo() : null), e); + } + } + + /** + * This will build a device id for the device. + */ + private DeviceId getDeviceId() throws URISyntaxException { + String additionalSSP = new StringBuilder(device.getUsername()) + .append("@").append(device.getSshHost()).append(":") + .append(device.getSshPort()).toString(); + DeviceId did = DeviceId.deviceId(new URI(SCHEME, additionalSSP, + null)); + return did; + } + } +} diff --git a/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/package-info.java b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/package-info.java new file mode 100644 index 00000000..583aeaa2 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/main/java/org/onosproject/provider/netconf/device/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses Netconf capability request as a means of infrastructure device discovery. + */ +package org.onosproject.provider.netconf.device.impl;
\ No newline at end of file diff --git a/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java new file mode 100644 index 00000000..e56c5959 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTest.java @@ -0,0 +1,421 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.device.impl; + +import static org.easymock.EasyMock.expect; +import static org.easymock.EasyMock.replay; +import static org.junit.Assert.assertFalse; +import static org.onlab.util.Tools.delay; +import static org.onosproject.provider.netconf.device.impl.NetconfDeviceProviderTestConstant.*; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.easymock.EasyMock; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.onlab.packet.ChassisId; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.ProviderId; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import com.tailf.jnc.JNCException; + +/** + * Test Case to Validate Netconf Device Provider. + */ +public class NetconfDeviceProviderTest { + TestDeviceCreator create; + + private final Logger log = getLogger(NetconfDeviceProviderTest.class); + + private Map<DeviceId, NetconfDevice> netconfDeviceMap = new ConcurrentHashMap<DeviceId, NetconfDevice>(); + + private DeviceProviderService providerService; + + private static final DeviceId DID1 = DeviceId.deviceId(DEVICE_ID); + + private final NetconfDeviceProvider provider = new NetconfDeviceProvider(); + private final TestDeviceRegistry registry = new TestDeviceRegistry(); + + private ComponentConfigService mockCfgService; + + @Before + public void setUp() { + mockCfgService = EasyMock.createMock(ComponentConfigService.class); + provider.cfgService = mockCfgService; + provider.providerRegistry = registry; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithoutValues(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)).andReturn(NULL); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithDeviceEntryNull(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)).andReturn(NULL_NULL); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockDeviceEntryNumberFomatEx(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_WITH_INVALID_ENTRY_NUMBER) + .andThrow(new NumberFormatException()); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithoutUsernameAndPassword(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)).andReturn(CONFIG_WITH_NULL_ENTRY); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockWithDifferentDeviceState(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_WITH_DIFFERENT_DEVICE_STATE); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockDeviceWithArrayOutOFBoundEx(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_WITH_ARRAY_OUT_OF_BOUNDEX) + .andThrow(new ArrayIndexOutOfBoundsException()); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @SuppressWarnings("unchecked") + private Dictionary<String, String> getDictionaryMockDeviceEntryForDeactivate(ComponentContext componentContext) { + Dictionary<String, String> dictionary = EasyMock + .createMock(Dictionary.class); + expect(dictionary.get(DEV_CONFIG)) + .andReturn(CONFIG_ENTRY_FOR_DEACTIVATE) + .andThrow(new ArrayIndexOutOfBoundsException()); + replay(dictionary); + expect(componentContext.getProperties()).andReturn(dictionary); + return dictionary; + } + + @Ignore + @Test(expected = IOException.class) + public void testSSHAuthentication() throws IOException, JNCException { + TestDeviceCreator objForTestDev = new TestDeviceCreator( + new NetconfDevice( + DEVICE_IP, + DEVICE_PORT, + DEVICE_USERNAME, + DEVICE_PASSWORD), + true); + objForTestDev.run(); + } + + @After + public void tearDown() { + provider.providerRegistry = null; + provider.cfgService = null; + } + + // To check if deviceCfgValue is empty or null + @Test + public void testActiveWithcomponentContextIsNull() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithoutValues(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + // To check deviceEntry and device is null + @Test + public void testActiveWithDeviceEntryIsNull() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithDeviceEntryNull(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithoutUsernameAndPassword() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithoutUsernameAndPassword(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithNumberFomatEx() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockDeviceEntryNumberFomatEx(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithDifferentDeviceState() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockWithDifferentDeviceState(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void testActiveWithDeviceEntryWithArrayOutOFBoundEx() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockDeviceWithArrayOutOFBoundEx(componentContext); + replay(componentContext); + provider.activate(componentContext); + } + + @Test + public void isReachableWithInvalidDeviceId() { + assertFalse("Initially the Device ID Should not be reachable", + provider.isReachable(DID1)); + NetconfDevice device = new NetconfDevice(NULL, ZERO, NULL, NULL); + provider.netconfDeviceMap.put(DID1, device); + assertFalse("Particular Device ID cannot be Reachable", + provider.isReachable(DID1)); + } + + @Test + public void testDeactivate() { + + ComponentContext componentContext = EasyMock + .createMock(ComponentContext.class); + getDictionaryMockDeviceEntryForDeactivate(componentContext); + replay(componentContext); + testActiveWithDeviceEntryWithDifferentDeviceState(); + provider.deactivate(componentContext); + } + + private class TestDeviceCreator { + + private NetconfDevice device; + private boolean createFlag; + + public TestDeviceCreator(NetconfDevice device, boolean createFlag) { + this.device = device; + this.createFlag = createFlag; + } + + public void run() throws JNCException, IOException { + if (createFlag) { + log.info("Trying to create Device Info on ONOS core"); + advertiseDevices(); + } else { + log.info("Trying to remove Device Info on ONOS core"); + removeDevices(); + } + } + + /** + * For each Netconf Device, remove the entry from the device store. + */ + private void removeDevices() { + if (device == null) { + log.warn("The Request Netconf Device is null, cannot proceed further"); + return; + } + try { + DeviceId did = getDeviceId(); + if (!netconfDeviceMap.containsKey(did)) { + log.error("BAD Request: 'Currently device is not discovered, " + + "so cannot remove/disconnect the device: " + + device.deviceInfo() + "'"); + return; + } + providerService.deviceDisconnected(did); + device.disconnect(); + netconfDeviceMap.remove(did); + delay(EVENTINTERVAL); + } catch (URISyntaxException uriSyntaxExcpetion) { + log.error("Syntax Error while creating URI for the device: " + + device.deviceInfo() + + " couldn't remove the device from the store", + uriSyntaxExcpetion); + } + } + + /** + * Initialize Netconf Device object, and notify core saying device + * connected. + */ + private void advertiseDevices() throws JNCException, IOException { + try { + if (device == null) { + log.warn("The Request Netconf Device is null, cannot proceed further"); + return; + } + device.init(); + DeviceId did = getDeviceId(); + ChassisId cid = new ChassisId(); + DeviceDescription desc = new DefaultDeviceDescription( + did.uri(), + Device.Type.OTHER, + NULL, + NULL, + NULL, + NULL, cid); + log.info("Persisting Device" + did.uri().toString()); + + netconfDeviceMap.put(did, device); + providerService.deviceConnected(did, desc); + log.info("Done with Device Info Creation on ONOS core. Device Info: " + + device.deviceInfo() + " " + did.uri().toString()); + delay(EVENTINTERVAL); + } catch (URISyntaxException e) { + log.error("Syntax Error while creating URI for the device: " + + device.deviceInfo() + + " couldn't persist the device onto the store", e); + } catch (JNCException e) { + throw e; + } catch (IOException e) { + throw e; + } catch (Exception e) { + log.error("Error while initializing session for the device: " + + device.deviceInfo(), e); + } + } + + private DeviceId getDeviceId() throws URISyntaxException { + String additionalSSP = new StringBuilder(device.getUsername()) + .append(AT_THE_RATE).append(device.getSshHost()) + .append(COLON).append(device.getSshPort()).toString(); + DeviceId did = DeviceId.deviceId(new URI(SCHEME_NETCONF, + additionalSSP, null)); + return did; + } + } + + private class TestDeviceRegistry implements DeviceProviderRegistry { + + @Override + public DeviceProviderService register(DeviceProvider provider) { + return new TestProviderService(); + } + + @Override + public void unregister(DeviceProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + private class TestProviderService implements DeviceProviderService { + + @Override + public DeviceProvider provider() { + return null; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + + } + + @Override + public void receivedRoleReply(DeviceId deviceId, + MastershipRole requested, + MastershipRole response) { + + } + + @Override + public void updatePortStatistics(DeviceId deviceId, + Collection<PortStatistics> portStatistics) { + + } + } + } +} diff --git a/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTestConstant.java b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTestConstant.java new file mode 100644 index 00000000..1d848e26 --- /dev/null +++ b/framework/src/onos/providers/netconf/device/src/test/java/org/onosproject/provider/netconf/device/impl/NetconfDeviceProviderTestConstant.java @@ -0,0 +1,46 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.device.impl; + +public final class NetconfDeviceProviderTestConstant { + + private NetconfDeviceProviderTestConstant() { + } + + public static final int ZERO = 0; + public static final int EVENTINTERVAL = 5; + public static final String DEV_CONFIG = "devConfigs"; + public static final String CONFIG_WITH_INVALID_ENTRY_NUMBER = "cisco:cisco" + + "@10.18.11.14:cisco:active"; + public static final String CONFIG_WITH_NULL_ENTRY = "null:null@null:0:active"; + public static final String CONFIG_WITH_DIFFERENT_DEVICE_STATE = "cisco:cisco@10.18.11.14:22:active," + + "cisco:cisco@10.18.11.18:22:inactive,cisco:cisco@10.18.11.14:22:invalid," + + "cisco:cisco@10.18.11.14:22:null"; + public static final String CONFIG_WITH_ARRAY_OUT_OF_BOUNDEX = "@10.18.11.14:22:active"; + public static final String CONFIG_ENTRY_FOR_DEACTIVATE = "netconf:cisco" + + "@10.18.11.14:22:active"; + public static final String DEVICE_IP = "10.18.14.19"; + public static final int DEVICE_PORT = 22; + public static final String DEVICE_USERNAME = "cisco"; + public static final String DEVICE_PASSWORD = "cisco"; + public static final String AT_THE_RATE = "@"; + public static final String COLON = ":"; + public static final String NULL = ""; + public static final String NULL_NULL = "null,null"; + public static final String SCHEME_NETCONF = "netconf"; + public static final String DEVICE_ID = "of:0000000000000001"; + +} diff --git a/framework/src/onos/providers/netconf/flow/pom.xml b/framework/src/onos/providers/netconf/flow/pom.xml new file mode 100644 index 00000000..8ee4c4e4 --- /dev/null +++ b/framework/src/onos/providers/netconf/flow/pom.xml @@ -0,0 +1,259 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-netconf-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-netconf-provider-flow</artifactId> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>ch.ethz.ganymed</groupId> + <artifactId>ganymed-ssh2</artifactId> + <version>262</version> + </dependency> + <dependency> + <!-- TODO: change this appropriately when the official TailF JNC is available --> + <groupId>org.onosproject</groupId> + <artifactId>jnc</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.jdom</groupId> + <artifactId>jdom2</artifactId> + <version>2.0.5</version> + </dependency> + <dependency> + <groupId>jaxen</groupId> + <artifactId>jaxen</artifactId> + <version>1.1.4</version> + <optional>true</optional> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-yang-tool</artifactId> + <version>1.3</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>2.3</version> + <configuration> + <filters> + <filter> + <artifact>com.tailf:JNC</artifact> + <includes> + <include>com/tailf/jnc/**</include> + </includes> + </filter> + <filter> + <artifact>ch.ethz.ganymed:ganymed-ssh2</artifact> + <includes> + <include>ch/ethz/ssh2/**</include> + </includes> + </filter> + <filter> + <artifact>org.jdom:jdom2</artifact> + <includes> + <include>org/jdom2/**</include> + </includes> + </filter> + <filter> + <artifact>org.onosproject:onos-yang-tool</artifact> + <includes> + <include>org/opendaylight/yang/gen/**</include> + </includes> + </filter> + </filters> + </configuration> + <executions> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <configuration> + <instructions> + <Export-Package> + com.tailf.jnc, + ch.ethz.ssh2, + ch.ethz.ssh2.auth, + ch.ethz.ssh2.channel, + ch.ethz.ssh2.crypto, + ch.ethz.ssh2.crypto.cipher, + ch.ethz.ssh2.crypto.dh, + ch.ethz.ssh2.crypto.digest, + ch.ethz.ssh2.log, + ch.ethz.ssh2.packets, + ch.ethz.ssh2.server, + ch.ethz.ssh2.sftp, + ch.ethz.ssh2.signature, + ch.ethz.ssh2.transport, + ch.ethz.ssh2.util, + org.jdom2, + org.jdom2.input, + org.jdom2.output, + org.jdom2.adapters, + org.jdom2.filter, + org.jdom2.internal, + org.jdom2.located, + org.jdom2.transform, + org.jdom2.util, + org.jdom2.xpath, + org.jdom2.input.sax, + org.jdom2.input.stax, + org.jdom2.output.support, + org.jdom2.xpath.jaxen, + org.jdom2.xpath.util, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520, + org.opendaylight.yangtools.yang.data.impl.schema.tree, + org.opendaylight.yangtools.yang.data.impl.codec, + org.opendaylight.yangtools.yang.model.parser.api, + org.opendaylight.yangtools.yang.data.impl.schema.nodes, + org.opendaylight.yangtools.yang.binding.util, + org.opendaylight.yangtools.yang.data.impl, + org.opendaylight.yangtools.sal.binding.generator.impl, + org.opendaylight.yangtools.yang.parser.impl.util, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625, + org.opendaylight.yangtools.yang.data.api, + org.opendaylight.yangtools.objcache.spi, + org.opendaylight.yangtools.yang.data.impl.schema.transform.base.parser, + org.opendaylight.yangtools.maven.sal.api.gen.plugin, + org.opendaylight.yangtools.yang.data.impl.schema.builder.impl, + org.opendaylight.yangtools.yang.data.api.schema.tree, + org.opendaylight.yangtools.binding.generator.util, + org.opendaylight.yangtools.sal.binding.generator.spi, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715, + org.opendaylight.yangtools.yang2sources.spi, + org.opendaylight.yangtools.yang.model.repo.api, + org.opendaylight.yangtools.util, + org.opendaylight.yangtools.yang.parser.util, + org.opendaylight.yangtools.yang.data.api.schema.stream, + org.opendaylight.yangtools.yang.data.impl.schema.transform.base.serializer, + org.opendaylight.yangtools.concepts, + org.opendaylight.yangtools.yang.binding, + org.opendaylight.yangtools.yang.model.util.repo, + org.opendaylight.yangtools.yang.wadl.generator.maven, + org.opendaylight.yangtools.yang.data.api.schema, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type, + org.opendaylight.yangtools.concepts.util, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip.ace.ip.version, + org.opendaylight.yangtools.sal.binding.model.api, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip, + org.opendaylight.yangtools.yang.data.impl.schema.builder.api, + org.opendaylight.yangtools.util.concurrent, + org.opendaylight.yangtools.yang.parser.builder.impl, + org.opendaylight.yangtools.yang.data.impl.schema.transform.base, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.actions.packet.handling, + org.opendaylight.yangtools.sal.binding.model.api.type.builder, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields, + org.opendaylight.yangtools.yang2sources.plugin, + org.opendaylight.yangtools.yang.data.impl.codec.xml, + org.opendaylight.yangtools.antlrv4.code.gen, + org.opendaylight.yangtools.yang.parser.builder.util, + org.opendaylight.yangtools.yang.data.impl.schema.transform, + org.opendaylight.yangtools.yang.model.api.type, + org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.serializer, + org.opendaylight.yangtools.yang.data.api.schema.tree.spi, + org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser, + org.opendaylight.yangtools.sal.binding.yang.types, + org.opendaylight.yangtools.yang.data.impl.schema.transform.dom, + org.opendaylight.yangtools.yang.data.impl.util, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.timerange, + org.opendaylight.yangtools.sal.binding.generator.api, + org.opendaylight.yangtools.sal.java.api.generator, + org.opendaylight.yangtools.yang.binding.annotations, + org.opendaylight.yangtools.sal.binding.generator.util, + org.opendaylight.yangtools.yang.model.repo.util, + org.opendaylight.yangtools.yang.model.api, + org.opendaylight.yangtools.yang.common, + org.opendaylight.yangtools.yang.wadl.generator, + org.opendaylight.yangtools.yang.parser.builder.api, + org.opendaylight.yangtools.yang.model.util, + org.opendaylight.yangtools.yang.parser.impl, + org.opendaylight.yangtools.yang.data.impl.schema, + org.opendaylight.yangtools.yang.data.api.codec, + org.opendaylight.yangtools.yang.unified.doc.generator, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list, + org.opendaylight.yangtools.objcache, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.actions, + org.opendaylight.yangtools.yang.data.util, + org.opendaylight.yangtools.yang.unified.doc.generator.maven, + org.opendaylight.yangtools.binding.generator.util.generated.type.builder, + org.opendaylight.yangtools.yang.model.repo.spi, + org.opendaylight.yangtools.yang.parser.repo, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715, + org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches, + org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.valid, + com.romix.scala, + com.romix.scala.collection, + com.romix.scala.collection.concurrent, + org.opendaylight.yangtools.objcache.impl + </Export-Package> + </instructions> + </configuration> + </plugin> + <plugin> + <groupId>org.onosproject</groupId> + <artifactId>onos-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> diff --git a/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/NetconfFlowRuleProvider.java b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/NetconfFlowRuleProvider.java new file mode 100644 index 00000000..b29d687e --- /dev/null +++ b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/NetconfFlowRuleProvider.java @@ -0,0 +1,403 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.flow.impl; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onlab.util.Tools.get; +import static org.slf4j.LoggerFactory.getLogger; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleBatchOperation; +import org.onosproject.net.flow.FlowRuleProvider; +import org.onosproject.net.flow.FlowRuleProviderRegistry; +import org.onosproject.net.flow.FlowRuleProviderService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.AccessList; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.AccessListBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.AccessListEntries; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.AccessListEntriesBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.ActionsBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.Matches; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.MatchesBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.actions.packet.handling.DenyBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.actions.packet.handling.PermitBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.AceIp; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.AceIpBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip.ace.ip.version.AceIpv4; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip.ace.ip.version.AceIpv4Builder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Ipv4Prefix; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields.DestinationPortRange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields.DestinationPortRangeBuilder; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields.SourcePortRange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields.SourcePortRangeBuilder; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +/** + * Netconf provider to accept any flow and report them. + */ +@Component(immediate = true) +public class NetconfFlowRuleProvider extends AbstractProvider + implements FlowRuleProvider { + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleProviderRegistry providerRegistry; + + private ConcurrentMap<DeviceId, Set<FlowEntry>> flowTable = new ConcurrentHashMap<>(); + + private FlowRuleProviderService providerService; + + private XmlBuilder xmlBuilder; + + private AceIp aceIp; + private SourcePortRange srcPortRange; + private DestinationPortRange destPortRange; + private Matches matches; + private HashedWheelTimer timer = Timer.getTimer(); + private Timeout timeout; + private static final String ACL_NAME_KEY = "acl-name"; + private static final String ACL_LIST_ENTRIES_RULE_NAME_KEY = "access-list-entries.rule-name"; + private static final String ACL_LIST_SP_LOWER_KEY = "source-port-range.lower-port"; + private static final String ACL_LIST_SP_UPPER_KEY = "source-port-range.upper-port"; + private static final String ACL_LIST_DP_LOWER_KEY = "destination-port-range.lower-port"; + private static final String ACL_LIST_DP_UPPER_KEY = "destination-port-range.upper-port"; + private static final String ACL_LIST_DEST_IPV4_KEY = "matches.destination-ipv4-address"; + private static final String ACL_LIST_SRC_IPV4_KEY = "matches.source-ipv4-address"; + private static final String ACL_LIST_ACTIONS_KEY = "actions"; + + public NetconfFlowRuleProvider() { + super(new ProviderId("netconf", "org.onosproject.provider.netconf")); + } + + @Activate + public void activate(ComponentContext context) { + providerService = providerRegistry.register(this); + timeout = timer.newTimeout(new StatisticTask(), 5, TimeUnit.SECONDS); + applyRule(); + modified(context); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + providerRegistry.unregister(this); + providerService = null; + timeout.cancel(); + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + if (xmlBuilder == null) { + xmlBuilder = new XmlBuilder(); + } + if (context == null) { + log.info("No configuration file"); + return; + } + Dictionary<?, ?> properties = context.getProperties(); + String deviceEntry = get(properties, "devConfigs"); + log.info("Settings: devConfigs={}", deviceEntry); + Enumeration<?> elements = properties.keys(); + Object nextElement = elements.nextElement(); + while (elements.hasMoreElements()) { + if (nextElement instanceof String) { + log.info("key::" + nextElement + ", value::" + + get(properties, (String) nextElement)); + } + nextElement = elements.nextElement(); + } + if (!isNullOrEmpty(deviceEntry)) { + Map<String, String> deviceMap = processDeviceEntry(deviceEntry); + AccessList accessList = buildAccessList(properties); + String xmlMsg = xmlBuilder.buildAclRequestXml(accessList); + log.info("The resultant xml from the builder\n" + xmlMsg); + NetconfOperation netconfOperation = new NetconfOperation(); + netconfOperation.sendXmlMessage(xmlMsg, deviceMap.get("username"), + deviceMap.get("password"), + deviceMap.get("hostIp"), Integer + .parseInt(deviceMap + .get("hostPort"))); + } + } + + /** + * @param properties + * @return accessList + */ + private AccessList buildAccessList(Dictionary<?, ?> properties) { + /** + * Populating Access List. + */ + AccessListBuilder abuilder = new AccessListBuilder(); + String aclName = get(properties, ACL_NAME_KEY); + if (aclName != null) { + abuilder.setAclName(aclName); + } + AccessList accessList = abuilder.build(); + abuilder.setAccessListEntries(getAccessListEntries(properties, matches)); + srcPortRange = getSourcePortRange(properties); + destPortRange = getDestinationPortRange(properties); + aceIp = getAceIp(properties, srcPortRange, destPortRange); + matches = getMatches(properties); + return accessList; + } + + /** + * @param properties + * @return matches + */ + private Matches getMatches(Dictionary<?, ?> properties) { + /** + * Building Matches for given ACL model. + */ + MatchesBuilder matchesBuilder = new MatchesBuilder(); + if (aceIp != null) { + matchesBuilder.setAceType(aceIp); + } + matches = matchesBuilder.build(); + return matches; + } + + /** + * @param properties + * @return srcPortRange + */ + private SourcePortRange getSourcePortRange(Dictionary<?, ?> properties) { + /** + * Building Source Port Range for given ACL model. + */ + String spRangeLowerStr = get(properties, ACL_LIST_SP_LOWER_KEY); + String spRangeUpperStr = get(properties, ACL_LIST_SP_UPPER_KEY); + SourcePortRangeBuilder srcPortRangeBuilder = new SourcePortRangeBuilder(); + if (spRangeLowerStr != null) { + int spRangeLower = Integer.parseInt(spRangeLowerStr); + srcPortRangeBuilder.setLowerPort(new PortNumber(spRangeLower)); + } + if (spRangeUpperStr != null) { + int spRangeUpper = Integer.parseInt(spRangeUpperStr); + srcPortRangeBuilder.setUpperPort(new PortNumber(spRangeUpper)); + } + srcPortRange = srcPortRangeBuilder.build(); + return srcPortRange; + } + + /** + * @param properties + * @return destPortRange + */ + private DestinationPortRange getDestinationPortRange(Dictionary<?, ?> properties) { + /** + * Building Destination Port Range for given ACL model. + */ + String dpRangeLowerStr = get(properties, ACL_LIST_DP_LOWER_KEY); + String dpRangeUpperStr = get(properties, ACL_LIST_DP_UPPER_KEY); + DestinationPortRangeBuilder destPortRangeBuilder = new DestinationPortRangeBuilder(); + if (dpRangeLowerStr != null) { + int dpRangeLower = Integer.parseInt(dpRangeLowerStr); + destPortRangeBuilder.setLowerPort(new PortNumber(dpRangeLower)); + } + if (dpRangeUpperStr != null) { + int dpRangeUpper = Integer.parseInt(dpRangeUpperStr); + destPortRangeBuilder.setUpperPort(new PortNumber(dpRangeUpper)); + } + destPortRange = destPortRangeBuilder.build(); + return destPortRange; + } + + /** + * @param properties + * @return accessListEntries + */ + private List<AccessListEntries> getAccessListEntries(Dictionary<?, ?> properties, + Matches matches) { + /** + * Build and Populate Access List Entries. + */ + AccessListEntriesBuilder acLListEntriesBuilder = new AccessListEntriesBuilder(); + String aclListEntriesRuleName = get(properties, + ACL_LIST_ENTRIES_RULE_NAME_KEY); + if (aclListEntriesRuleName != null) { + acLListEntriesBuilder.setRuleName(aclListEntriesRuleName); + } + acLListEntriesBuilder.setMatches(matches); + String aclActions = get(properties, ACL_LIST_ACTIONS_KEY); + if (aclActions != null) { + ActionsBuilder actionBuilder = new ActionsBuilder(); + if (aclActions.equalsIgnoreCase("deny")) { + DenyBuilder denyBuilder = new DenyBuilder(); + actionBuilder.setPacketHandling(denyBuilder.build()); + } else if (aclActions.equalsIgnoreCase("permit")) { + PermitBuilder permitBuilder = new PermitBuilder(); + actionBuilder.setPacketHandling(permitBuilder.build()); + } + acLListEntriesBuilder.setActions(actionBuilder.build()); + } + AccessListEntries aclListEntries = acLListEntriesBuilder.build(); + List<AccessListEntries> accessListEntries = new ArrayList<AccessListEntries>(); + accessListEntries.add(aclListEntries); + return accessListEntries; + } + + /** + * @param properties + * @return aceIp + */ + private AceIp getAceIp(Dictionary<?, ?> properties, + SourcePortRange srcPortRange, + DestinationPortRange destPortRange) { + /** + * Building Ace IPV4 Type + */ + String destIpv4 = get(properties, ACL_LIST_DEST_IPV4_KEY); + String srcIpv4 = get(properties, ACL_LIST_SRC_IPV4_KEY); + AceIpv4Builder aceIpv4Builder = new AceIpv4Builder(); + aceIp = null; + if (destIpv4 != null) { + Ipv4Prefix destinationIp = new Ipv4Prefix(destIpv4); + aceIpv4Builder.setDestinationIpv4Address(destinationIp); + } + if (srcIpv4 != null) { + Ipv4Prefix sourceIp = new Ipv4Prefix(srcIpv4); + aceIpv4Builder.setSourceIpv4Address(sourceIp); + } + if (destIpv4 != null || srcIpv4 != null) { + AceIpv4 aceIpv4 = aceIpv4Builder.build(); + AceIpBuilder aceIpBuilder = new AceIpBuilder(); + aceIpBuilder.setAceIpVersion(aceIpv4); + aceIpBuilder.setSourcePortRange(srcPortRange); + aceIpBuilder.setDestinationPortRange(destPortRange); + aceIp = aceIpBuilder.build(); + } + return aceIp; + } + + /** + * @param deviceEntry + * @return deviceMap + */ + private Map<String, String> processDeviceEntry(String deviceEntry) { + if (deviceEntry == null) { + log.info("No content for Device Entry, so cannot proceed further."); + return null; + } + + Map<String, String> deviceMap = new HashMap<String, String>(); + log.info("Trying to convert Device Entry String: " + deviceEntry + + " to a Netconf Device Object"); + try { + URI uri = new URI(deviceEntry); + String path = uri.getPath(); + String userInfo = path.substring(path.lastIndexOf('@')); + String hostInfo = path.substring(path.lastIndexOf('@') + 1); + String[] infoSplit = userInfo.split(":"); + String username = infoSplit[0]; + String password = infoSplit[1]; + infoSplit = hostInfo.split(":"); + String hostIp = infoSplit[0]; + String hostPort = infoSplit[1]; + if (isNullOrEmpty(username) || isNullOrEmpty(password) + || isNullOrEmpty(hostIp) || isNullOrEmpty(hostPort)) { + log.warn("Bad Configuration Data: both user and device" + + " information parts of Configuration " + deviceEntry + + " should be non-nullable"); + } else { + deviceMap.put("hostIp", hostIp); + deviceMap.put("hostPort", hostPort); + deviceMap.put("username", username); + deviceMap.put("password", password); + } + } catch (ArrayIndexOutOfBoundsException aie) { + log.error("Error while reading config infromation from the config file: " + + "The user, host and device state infomation should be " + + "in the order 'userInfo@hostInfo:deviceState'" + + deviceEntry, aie); + } catch (URISyntaxException urie) { + log.error("Error while parsing config information for the device entry: " + + "Illegal character in path " + deviceEntry, + urie); + } catch (Exception e) { + log.error("Error while parsing config information for the device entry: " + + deviceEntry, e); + } + return deviceMap; + } + + @Override + public void applyFlowRule(FlowRule... flowRules) { + } + + @Override + public void removeFlowRule(FlowRule... flowRules) { + } + + private void applyRule() { + // applyFlowRule(flowRules);//currentl + } + + @Override + public void removeRulesById(ApplicationId id, FlowRule... flowRules) { + log.info("removal by app id not supported in null provider"); + } + + @Override + public void executeBatch(FlowRuleBatchOperation batch) { + + } + + private class StatisticTask implements TimerTask { + + @Override + public void run(Timeout to) throws Exception { + for (DeviceId devId : flowTable.keySet()) { + providerService.pushFlowMetrics(devId, flowTable + .getOrDefault(devId, Collections.emptySet())); + } + timeout = timer.newTimeout(to.getTask(), 5, TimeUnit.SECONDS); + + } + } +} diff --git a/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/NetconfOperation.java b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/NetconfOperation.java new file mode 100644 index 00000000..d03e75ac --- /dev/null +++ b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/NetconfOperation.java @@ -0,0 +1,139 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.flow.impl; + +import static org.onlab.util.Tools.delay; +import static org.slf4j.LoggerFactory.getLogger; + +import java.io.IOException; + +import org.slf4j.Logger; + +import com.tailf.jnc.Capabilities; +import com.tailf.jnc.JNCException; +import com.tailf.jnc.SSHConnection; +import com.tailf.jnc.SSHSession; + +/** + * This is to carry necessary information to connect and execute NETCONF + * operations. + */ +public class NetconfOperation { + private final Logger log = getLogger(NetconfOperation.class); + private static final int EVENTINTERVAL = 2000; + private static final int CONNECTION_CHECK_INTERVAL = 3; + private static final String INPUT_HELLO_XML_MSG = new StringBuilder( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>") + .append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">") + .append("<capabilities><capability>urn:ietf:params:netconf:base:1.0</capability>") + .append("</capabilities></hello>").toString(); + + /** + * This will send a Xml message to the device. + * @param xmlMsg XML to send + * @param username user name + * @param password pass word + * @param deviceIp ip address of the device + * @param devicePort port on the device + */ + protected void sendXmlMessage(String xmlMsg, String username, + String password, String deviceIp, + Integer devicePort) { + SSHSession ssh = null; + try { + SSHConnection sshConnection = getConnection(username, password, + deviceIp, devicePort); + ssh = new SSHSession(sshConnection); + executeMessage(ssh, INPUT_HELLO_XML_MSG); + /* + * execute acl message + */ + executeMessage(ssh, xmlMsg); + + } catch (IOException e) { + log.error("Unable to send Hello Message to the device: ", e); + } catch (JNCException e) { + log.error("Authentication fail while sending Hello Message to the device: ", + e); + } catch (Exception e) { + log.error("Unable to send Hello Message to the device: ", e); + } finally { + log.debug("Closing the session after successful execution"); + ssh.close(); + } + } + + private void executeMessage(SSHSession ssh, String xmlMsg) + throws IOException, JNCException { + String helloRequestXML = xmlMsg.trim(); + + log.debug("Sending Hello"); + ssh.print(helloRequestXML); + ssh.flush(); + String xmlResponse = null; + int i = CONNECTION_CHECK_INTERVAL; + while (!ssh.ready() && i > 0) { + delay(EVENTINTERVAL); + i--; + } + + if (ssh.ready()) { + StringBuffer readOne = ssh.readOne(); + if (readOne == null) { + log.error("The Hello Contains No Capabilites"); + throw new JNCException( + JNCException.SESSION_ERROR, + "server does not support NETCONF base capability: " + + Capabilities.NETCONF_BASE_CAPABILITY); + } else { + xmlResponse = readOne.toString().trim(); + + log.debug("Reading Capabilities: " + + ssh.getSSHConnection().getGanymedConnection() + .getHostname()); + } + } + } + + /** + * To establish SSH Connection. + * + * @param username user name + * @param password pass word + * @param sshHost host + * @param sshPort port + * @return new SSH connection + * @throws IOException if connection fails + * @throws JNCException if connection causes an error + */ + public SSHConnection getConnection(String username, String password, + String sshHost, Integer sshPort) + throws IOException, JNCException { + SSHConnection sshConnection; + try { + sshConnection = new SSHConnection(sshHost, sshPort); + sshConnection.authenticateWithPassword(username, password); + } catch (IOException e) { + log.error("Unable to create a connection to the device: "); + throw e; + } catch (JNCException e) { + log.error("Failed to connect to the device: "); + throw e; + } + return sshConnection; + } + +} diff --git a/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/XmlBuilder.java b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/XmlBuilder.java new file mode 100644 index 00000000..389f1669 --- /dev/null +++ b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/XmlBuilder.java @@ -0,0 +1,223 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.netconf.flow.impl; + +import static org.slf4j.LoggerFactory.getLogger; + +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.Namespace; +import org.jdom2.output.Format; +import org.jdom2.output.XMLOutputter; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.AccessList; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.AceType; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.AceEth; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.AceIp; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip.AceIpVersion; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip.ace.ip.version.AceIpv4; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.acl.rev140520.access.list.access.list.entries.matches.ace.type.ace.ip.ace.ip.version.AceIpv6; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields.DestinationPortRange; +import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.packet.fields.rev140625.acl.transport.header.fields.SourcePortRange; +import org.slf4j.Logger; + +/** + * Xml Builder to generate the xml according to given ACL model. + */ +public class XmlBuilder { + private final Logger log = getLogger(XmlBuilder.class); + + public String buildAclRequestXml(AccessList accessList) { + Document doc = new Document(); + Namespace namespaceRpc = Namespace + .getNamespace("urn:ietf:params:xml:ns:netconf:base:1.0"); + Namespace accessNamespaceRpc = Namespace + .getNamespace("urn:ietf:params:xml:ns:yang:ietf-acl"); + doc.setRootElement(new Element("rpc", namespaceRpc) + .setAttribute("message-id", "101")); + + /** + * Access list elements of given ACL model. + */ + Element access = new Element("access-list", accessNamespaceRpc); + access.addContent(new Element("acl-name", accessNamespaceRpc) + .setText(accessList.getAclName())); + // access.addContent(accessEntries); + + if (!accessList.getAccessListEntries().isEmpty() + && accessList.getAccessListEntries() != null) { + for (int accessEntryIntVlu = 0; accessEntryIntVlu < accessList + .getAccessListEntries().size(); accessEntryIntVlu++) { + access.addContent(getAccessEntries(accessEntryIntVlu, + accessList, + accessNamespaceRpc)); + } + } + + /** + * edit-config operation for given ACL model. + */ + Element editConfig = new Element("edit-config", namespaceRpc); + editConfig.addContent(new Element("target", namespaceRpc) + .addContent(new Element("running", namespaceRpc))); + editConfig.addContent(new Element("config", Namespace + .getNamespace("urn:ietf:params:xml:ns:netconf:base:1.0")) + .addContent(access)); + + doc.getRootElement().addContent(editConfig); + XMLOutputter xmlOutputter = new XMLOutputter(Format.getPrettyFormat()); + String outputString = xmlOutputter.outputString(doc); + + return outputString; + } + + /** + * access entries operation for given ACL model. + */ + private Element getAccessEntries(int accessEntryIntVlu, + AccessList accessList, + Namespace accessNamespaceRpc) { + + /** + * Port Number + */ + + int srcPortRangeLower = 0; + int srcPortRangeUpper = 0; + int destPortRangeLower = 0; + int destPortRangeUpper = 0; + + String sourceIpAdd = ""; + String destinationIpAdd = ""; + + /* + * checking accessList is null or not + */ + if (accessList != null) { + /* + * checking list entries are empty or null + */ + if (!accessList.getAccessListEntries().isEmpty() + && accessList.getAccessListEntries() != null) { + AceType aceType = accessList.getAccessListEntries() + .get(accessEntryIntVlu).getMatches().getAceType(); + + if (aceType instanceof AceIp) { + AceIp aceIp = (AceIp) aceType; + SourcePortRange sourcePortRange = aceIp + .getSourcePortRange(); + if (sourcePortRange != null) { + PortNumber lowerPort = sourcePortRange.getLowerPort(); + PortNumber upperPort = sourcePortRange.getUpperPort(); + + if (lowerPort != null) { + srcPortRangeLower = lowerPort.getValue(); + } + if (upperPort != null) { + srcPortRangeUpper = upperPort.getValue(); + } + } + DestinationPortRange destinationPortRange = aceIp + .getDestinationPortRange(); + + if (destinationPortRange != null) { + PortNumber lowerPort = destinationPortRange + .getLowerPort(); + if (lowerPort != null) { + destPortRangeLower = lowerPort.getValue(); + } + + PortNumber upperPort = destinationPortRange + .getUpperPort(); + if (upperPort != null) { + destPortRangeUpper = upperPort.getValue(); + } + + } + + AceIpVersion aceIpVersion = aceIp.getAceIpVersion(); + if (aceIpVersion instanceof AceIpv4) { + AceIpv4 obj = (AceIpv4) aceIpVersion; + destinationIpAdd = obj.getDestinationIpv4Address() + .getValue(); + sourceIpAdd = obj.getSourceIpv4Address().getValue(); + } else if (aceIpVersion instanceof AceIpv6) { + AceIpv6 obj = (AceIpv6) aceIpVersion; + destinationIpAdd = obj.getDestinationIpv6Address() + .getValue(); + sourceIpAdd = obj.getSourceIpv6Address().getValue(); + } + } else if (aceType instanceof AceEth) { + log.debug("Need to add execution loging for Ace Type Ethernet"); + } + } + } + + /** + * Matches elements to define IP address & Port range for given ACL + * model. + */ + Element matchesElement = new Element("matches", accessNamespaceRpc); + if (String.valueOf(srcPortRangeLower) != null + && !String.valueOf(srcPortRangeLower).isEmpty()) { + + matchesElement.addContent(new Element("source-port-range", + accessNamespaceRpc) + .addContent(new Element("lower-port", accessNamespaceRpc) + .setText(String.valueOf(srcPortRangeLower)))); + + matchesElement.addContent(new Element("source-port-range", + accessNamespaceRpc) + .addContent(new Element("upper-port", accessNamespaceRpc) + .setText(String.valueOf(srcPortRangeUpper)))); + + matchesElement.addContent(new Element("destination-port-range", + accessNamespaceRpc) + .addContent(new Element("lower-port", accessNamespaceRpc) + .setText(String.valueOf(destPortRangeLower)))); + + matchesElement.addContent(new Element("destination-port-range", + accessNamespaceRpc) + .addContent(new Element("upper-port", accessNamespaceRpc) + .setText(String.valueOf(destPortRangeUpper)))); + } + + if (destinationIpAdd != null && !destinationIpAdd.isEmpty()) { + matchesElement.addContent(new Element("destination-ipv4-address", + accessNamespaceRpc) + .setText(destinationIpAdd)); + } + if (sourceIpAdd != null && !sourceIpAdd.isEmpty()) { + matchesElement.addContent(new Element("source-ipv4-address", + accessNamespaceRpc) + .setText(sourceIpAdd)); + } + + /** + * Access entries elements for given ACL model. + */ + Element accessEntries = new Element("access-list-entries", + accessNamespaceRpc); + accessEntries.addContent(new Element("rule-name", accessNamespaceRpc) + .setText(accessList.getAccessListEntries() + .get(accessEntryIntVlu).getRuleName())); + accessEntries.addContent(matchesElement); + accessEntries.addContent(new Element("actions", accessNamespaceRpc) + .addContent(new Element("deny", accessNamespaceRpc))); + + return accessEntries; + } +} diff --git a/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/package-info.java b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/package-info.java new file mode 100644 index 00000000..b095fc9a --- /dev/null +++ b/framework/src/onos/providers/netconf/flow/src/main/java/org/onosproject/provider/netconf/flow/impl/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that will accept any flow rules. + */ +package org.onosproject.provider.netconf.flow.impl; + diff --git a/framework/src/onos/providers/netconf/pom.xml b/framework/src/onos/providers/netconf/pom.xml new file mode 100644 index 00000000..ac08dd4a --- /dev/null +++ b/framework/src/onos/providers/netconf/pom.xml @@ -0,0 +1,49 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-netconf-providers</artifactId> + <packaging>pom</packaging> + + <description>ONOS Netconf protocol adapters</description> + + <modules> + <module>device</module> + <module>app</module> + <module>flow</module> + </modules> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/null/pom.xml b/framework/src/onos/providers/null/pom.xml new file mode 100644 index 00000000..ce547588 --- /dev/null +++ b/framework/src/onos/providers/null/pom.xml @@ -0,0 +1,61 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-null-provider</artifactId> + <packaging>bundle</packaging> + + <description>Null southbound providers</description> + + <properties> + <onos.app.name>org.onosproject.null</onos.app.name> + </properties> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.apache.karaf.shell</groupId> + <artifactId>org.apache.karaf.shell.console</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-cli</artifactId> + <version>${project.version}</version> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/AggLinkTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/AggLinkTopologySimulator.java new file mode 100644 index 00000000..2d09ebed --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/AggLinkTopologySimulator.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Simple triangle topology with multiple links between same devices. + */ +public class AggLinkTopologySimulator extends CentipedeTopologySimulator { + + @Override + protected void processTopoShape(String shape) { + super.processTopoShape(shape); + infrastructurePorts = 2 * deviceCount - 1; + } + + @Override + public void setUpTopology() { + checkArgument(deviceCount > 2, "There must be at least 3 devices"); + super.setUpTopology(); + } + + @Override + protected void createLinks() { + int srcPortOffset = deviceCount + 1; + for (int i = 0, n = deviceCount; i < n; i++) { + int dstPortOffset = 1; + for (int j = 0; j <= i; j++) { + createLink(i, (i + 1) % n, srcPortOffset + j, dstPortOffset + j); + } + srcPortOffset = dstPortOffset + i + 1; + } + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/CentipedeTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/CentipedeTopologySimulator.java new file mode 100644 index 00000000..5234d448 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/CentipedeTopologySimulator.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +/** + * Linear topology with hosts on every device. + */ +public class CentipedeTopologySimulator extends LinearTopologySimulator { + + /** + * Creates simulated hosts. + */ + protected void createHosts() { + deviceIds.forEach(id -> createHosts(id, infrastructurePorts)); + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/ConfiguredTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/ConfiguredTopologySimulator.java new file mode 100644 index 00000000..ad57bf3f --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/ConfiguredTopologySimulator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +/** + * Topology simulator which operates on topology configured via the REST API + * config service. + */ +public class ConfiguredTopologySimulator extends TopologySimulator { + + @Override + protected void createDevices() { + deviceService.getDevices() + .forEach(device -> deviceProviderService + .deviceConnected(device.id(), description(device))); + } + + @Override + protected void createLinks() { + linkService.getLinks() + .forEach(link -> linkProviderService + .linkDetected(description(link))); + } + + @Override + protected void createHosts() { + hostService.getHosts() + .forEach(host -> hostProviderService + .hostDetected(host.id(), description(host))); + } +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/LinearTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/LinearTopologySimulator.java new file mode 100644 index 00000000..beb06c33 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/LinearTopologySimulator.java @@ -0,0 +1,58 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Linear topology simulator. + */ +public class LinearTopologySimulator extends TopologySimulator { + + @Override + protected void processTopoShape(String shape) { + super.processTopoShape(shape); + deviceCount = (topoShape.length == 1) ? deviceCount : Integer.parseInt(topoShape[1]); + } + + @Override + public void setUpTopology() { + checkArgument(deviceCount > 1, "There must be at least 2 devices"); + + prepareForDeviceEvents(deviceCount); + createDevices(); + waitForDeviceEvents(); + + createLinks(); + createHosts(); + } + + @Override + protected void createLinks() { + int portOffset = 1; + for (int i = 0, n = deviceCount - 1; i < n; i++) { + createLink(i, i + 1, portOffset, 1); + portOffset = 2; + } + } + + @Override + protected void createHosts() { + createHosts(deviceIds.get(0), infrastructurePorts); + createHosts(deviceIds.get(deviceCount - 1), infrastructurePorts); + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/MeshTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/MeshTopologySimulator.java new file mode 100644 index 00000000..d3f2d6ad --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/MeshTopologySimulator.java @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +/** + * Full mesh topology with hosts at each device. + */ +public class MeshTopologySimulator extends TopologySimulator { + + @Override + protected void processTopoShape(String shape) { + super.processTopoShape(shape); + // FIXME: implement this + } + + @Override + public void setUpTopology() { + // FIXME: implement this + // checkArgument(FIXME, "There must be at least ..."); + super.setUpTopology(); + } + + @Override + protected void createLinks() { + } + + @Override + protected void createHosts() { + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java new file mode 100644 index 00000000..9b01d12a --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullFlowRuleProvider.java @@ -0,0 +1,132 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import com.google.common.collect.Sets; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.CompletedBatchOperation; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleBatchEntry; +import org.onosproject.net.flow.FlowRuleBatchOperation; +import org.onosproject.net.flow.FlowRuleProvider; +import org.onosproject.net.flow.FlowRuleProviderService; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Null provider to accept any flow and report them. + */ +class NullFlowRuleProvider extends NullProviders.AbstractNullProvider + implements FlowRuleProvider { + + private final Logger log = getLogger(getClass()); + + private ConcurrentMap<DeviceId, Set<FlowEntry>> flowTable = new ConcurrentHashMap<>(); + + private FlowRuleProviderService providerService; + + private HashedWheelTimer timer = Timer.getTimer(); + private Timeout timeout; + + /** + * Starts the flow rule provider simulation. + * + * @param providerService flow rule provider service + */ + void start(FlowRuleProviderService providerService) { + this.providerService = providerService; + timeout = timer.newTimeout(new StatisticTask(), 5, TimeUnit.SECONDS); + } + + /** + * Stops the flow rule provider simulation. + */ + void stop() { + timeout.cancel(); + } + + @Override + public void applyFlowRule(FlowRule... flowRules) { + // FIXME: invoke executeBatch + } + + @Override + public void removeFlowRule(FlowRule... flowRules) { + // FIXME: invoke executeBatch + } + + @Override + public void removeRulesById(ApplicationId id, FlowRule... flowRules) { + throw new UnsupportedOperationException("Cannot remove by appId from null provider"); + } + + @Override + public void executeBatch(FlowRuleBatchOperation batch) { + // TODO: consider checking mastership + Set<FlowEntry> entries = + flowTable.getOrDefault(batch.deviceId(), + Sets.newConcurrentHashSet()); + for (FlowRuleBatchEntry fbe : batch.getOperations()) { + switch (fbe.operator()) { + case ADD: + entries.add(new DefaultFlowEntry(fbe.target())); + break; + case REMOVE: + entries.remove(new DefaultFlowEntry(fbe.target())); + break; + case MODIFY: + FlowEntry entry = new DefaultFlowEntry(fbe.target()); + entries.remove(entry); + entries.add(entry); + break; + default: + log.error("Unknown flow operation: {}", fbe); + } + } + flowTable.put(batch.deviceId(), entries); + CompletedBatchOperation op = + new CompletedBatchOperation(true, Collections.emptySet(), + batch.deviceId()); + providerService.batchOperationCompleted(batch.id(), op); + } + + // Periodically reports flow rule statistics. + private class StatisticTask implements TimerTask { + @Override + public void run(Timeout to) throws Exception { + for (DeviceId devId : flowTable.keySet()) { + Set<FlowEntry> entries = + flowTable.getOrDefault(devId, Collections.emptySet()); + providerService.pushFlowMetrics(devId, entries); + } + timeout = timer.newTimeout(to.getTask(), 5, TimeUnit.SECONDS); + } + } +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullPacketProvider.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullPacketProvider.java new file mode 100644 index 00000000..07a137e9 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullPacketProvider.java @@ -0,0 +1,169 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.packet.Ethernet; +import org.onlab.packet.ICMP; +import org.onlab.util.Timer; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.host.HostService; +import org.onosproject.net.packet.DefaultInboundPacket; +import org.onosproject.net.packet.DefaultPacketContext; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketProvider; +import org.onosproject.net.packet.PacketProviderService; +import org.slf4j.Logger; + +import java.nio.ByteBuffer; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.google.common.collect.ImmutableList.copyOf; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.onosproject.net.MastershipRole.MASTER; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which generates simulated packets and acts as a sink for outbound + * packets. To be used for benchmarking only. + */ +class NullPacketProvider extends NullProviders.AbstractNullProvider + implements PacketProvider { + + private static final int INITIAL_DELAY = 5; + private final Logger log = getLogger(getClass()); + + // Arbitrary host src/dst + private static final int SRC_HOST = 2; + private static final int DST_HOST = 5; + + // Time between event firing, in milliseconds + private int delay; + + // TODO: use host service to pick legitimate hosts connected to devices + private HostService hostService; + private PacketProviderService providerService; + + private List<Device> devices; + private int currentDevice = 0; + + private HashedWheelTimer timer = Timer.getTimer(); + private Timeout timeout; + + /** + * Starts the packet generation process. + * + * @param packetRate packets per second + * @param hostService host service + * @param deviceService device service + * @param providerService packet provider service + */ + void start(int packetRate, HostService hostService, + DeviceAdminService deviceService, + PacketProviderService providerService) { + this.hostService = hostService; + this.providerService = providerService; + + this.devices = copyOf(deviceService.getDevices()).stream() + .filter(d -> deviceService.getRole(d.id()) == MASTER) + .collect(Collectors.toList()); + + adjustRate(packetRate); + timeout = timer.newTimeout(new PacketDriverTask(), INITIAL_DELAY, SECONDS); + } + + /** + * Adjusts packet rate. + * + * @param packetRate new packet rate + */ + void adjustRate(int packetRate) { + delay = 1000 / packetRate; + log.info("Settings: packetRate={}, delay={}", packetRate, delay); + } + + /** + * Stops the packet generation process. + */ + void stop() { + if (timeout != null) { + timeout.cancel(); + } + } + + @Override + public void emit(OutboundPacket packet) { + // We don't have a network to emit to. Keep a counter here, maybe? + } + + /** + * Generates packet events at a given rate. + */ + private class PacketDriverTask implements TimerTask { + + // Filler echo request + ICMP icmp; + Ethernet eth; + + public PacketDriverTask() { + icmp = new ICMP(); + icmp.setIcmpType((byte) 8).setIcmpCode((byte) 0).setChecksum((short) 0); + eth = new Ethernet(); + eth.setEtherType(Ethernet.TYPE_IPV4); + eth.setPayload(icmp); + } + + @Override + public void run(Timeout to) { + if (!devices.isEmpty() && !to.isCancelled()) { + sendEvent(devices.get(Math.min(currentDevice, devices.size() - 1))); + currentDevice = (currentDevice + 1) % devices.size(); + timeout = timer.newTimeout(to.getTask(), delay, TimeUnit.MILLISECONDS); + } + } + + private void sendEvent(Device device) { + // Make it look like things came from ports attached to hosts + eth.setSourceMACAddress("00:00:10:00:00:0" + SRC_HOST) + .setDestinationMACAddress("00:00:10:00:00:0" + DST_HOST); + InboundPacket inPkt = new DefaultInboundPacket( + new ConnectPoint(device.id(), PortNumber.portNumber(SRC_HOST)), + eth, ByteBuffer.wrap(eth.serialize())); + providerService.processPacket(new NullPacketContext(inPkt, null)); + } + } + + // Minimal PacketContext to make core and applications happy. + private final class NullPacketContext extends DefaultPacketContext { + private NullPacketContext(InboundPacket inPkt, OutboundPacket outPkt) { + super(System.currentTimeMillis(), inPkt, outPkt, false); + } + + @Override + public void send() { + // We don't send anything out. + } + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java new file mode 100644 index 00000000..c5688419 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/NullProviders.java @@ -0,0 +1,420 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.osgi.DefaultServiceDirectory; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.NodeId; +import org.onosproject.mastership.MastershipAdminService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.flow.FlowRuleProviderRegistry; +import org.onosproject.net.flow.FlowRuleProviderService; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.HostProviderRegistry; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.host.HostService; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.packet.PacketProviderRegistry; +import org.onosproject.net.packet.PacketProviderService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.util.Dictionary; +import java.util.Properties; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onlab.util.Tools.delay; +import static org.onlab.util.Tools.get; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.MastershipRole.MASTER; +import static org.onosproject.net.MastershipRole.NONE; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider of a fake network environment, i.e. devices, links, hosts, etc. + * To be used for benchmarking only. + */ +@Component(immediate = true) +@Service(value = NullProviders.class) +public class NullProviders { + + private static final Logger log = getLogger(NullProviders.class); + + static final String SCHEME = "null"; + static final String PROVIDER_ID = "org.onosproject.provider.nil"; + + private static final String FORMAT = + "Settings: enabled={}, topoShape={}, deviceCount={}, " + + "hostCount={}, packetRate={}, mutationRate={}"; + + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipAdminService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceAdminService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceProviderRegistry deviceProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostProviderRegistry hostProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkProviderRegistry linkProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleProviderRegistry flowRuleProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketProviderRegistry packetProviderRegistry; + + + private final NullDeviceProvider deviceProvider = new NullDeviceProvider(); + private final NullLinkProvider linkProvider = new NullLinkProvider(); + private final NullHostProvider hostProvider = new NullHostProvider(); + private final NullFlowRuleProvider flowRuleProvider = new NullFlowRuleProvider(); + private final NullPacketProvider packetProvider = new NullPacketProvider(); + private final TopologyMutationDriver topologyMutationDriver = new TopologyMutationDriver(); + + private DeviceProviderService deviceProviderService; + private HostProviderService hostProviderService; + private LinkProviderService linkProviderService; + private FlowRuleProviderService flowRuleProviderService; + private PacketProviderService packetProviderService; + + private TopologySimulator simulator; + + @Property(name = "enabled", boolValue = false, + label = "Enables or disables the provider") + private boolean enabled = false; + + private static final String DEFAULT_TOPO_SHAPE = "configured"; + @Property(name = "topoShape", value = DEFAULT_TOPO_SHAPE, + label = "Topology shape: configured, linear, reroute, tree, spineleaf, mesh") + private String topoShape = DEFAULT_TOPO_SHAPE; + + private static final int DEFAULT_DEVICE_COUNT = 10; + @Property(name = "deviceCount", intValue = DEFAULT_DEVICE_COUNT, + label = "Number of devices to generate") + private int deviceCount = DEFAULT_DEVICE_COUNT; + + private static final int DEFAULT_HOST_COUNT = 5; + @Property(name = "hostCount", intValue = DEFAULT_HOST_COUNT, + label = "Number of host to generate per device") + private int hostCount = DEFAULT_HOST_COUNT; + + private static final int DEFAULT_PACKET_RATE = 5; + @Property(name = "packetRate", intValue = DEFAULT_PACKET_RATE, + label = "Packet-in/s rate; 0 for no packets") + private int packetRate = DEFAULT_PACKET_RATE; + + private static final double DEFAULT_MUTATION_RATE = 0; + @Property(name = "mutationRate", doubleValue = DEFAULT_MUTATION_RATE, + label = "Link event/s topology mutation rate; 0 for no mutations") + private double mutationRate = DEFAULT_MUTATION_RATE; + + private static final String DEFAULT_MASTERSHIP = "random"; + @Property(name = "mastership", value = DEFAULT_MASTERSHIP, + label = "Mastership given as 'random' or 'node1=dpid,dpid/node2=dpid,...'") + private String mastership = DEFAULT_MASTERSHIP; + + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + + deviceProviderService = deviceProviderRegistry.register(deviceProvider); + hostProviderService = hostProviderRegistry.register(hostProvider); + linkProviderService = linkProviderRegistry.register(linkProvider); + flowRuleProviderService = flowRuleProviderRegistry.register(flowRuleProvider); + packetProviderService = packetProviderRegistry.register(packetProvider); + log.info("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + cfgService.unregisterProperties(getClass(), false); + tearDown(); + + deviceProviderRegistry.unregister(deviceProvider); + hostProviderRegistry.unregister(hostProvider); + linkProviderRegistry.unregister(linkProvider); + flowRuleProviderRegistry.unregister(flowRuleProvider); + packetProviderRegistry.unregister(packetProvider); + + deviceProviderService = null; + hostProviderService = null; + linkProviderService = null; + flowRuleProviderService = null; + packetProviderService = null; + + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties(); + + boolean newEnabled; + int newDeviceCount, newHostCount, newPacketRate; + double newMutationRate; + String newTopoShape, newMastership; + try { + String s = get(properties, "enabled"); + newEnabled = isNullOrEmpty(s) ? enabled : Boolean.parseBoolean(s.trim()); + + newTopoShape = get(properties, "topoShape"); + newMastership = get(properties, "mastership"); + + s = get(properties, "deviceCount"); + newDeviceCount = isNullOrEmpty(s) ? deviceCount : Integer.parseInt(s.trim()); + + s = get(properties, "hostCount"); + newHostCount = isNullOrEmpty(s) ? hostCount : Integer.parseInt(s.trim()); + + s = get(properties, "packetRate"); + newPacketRate = isNullOrEmpty(s) ? packetRate : Integer.parseInt(s.trim()); + + s = get(properties, "mutationRate"); + newMutationRate = isNullOrEmpty(s) ? mutationRate : Double.parseDouble(s.trim()); + + } catch (NumberFormatException e) { + log.warn(e.getMessage()); + newEnabled = enabled; + newTopoShape = topoShape; + newDeviceCount = deviceCount; + newHostCount = hostCount; + newPacketRate = packetRate; + newMutationRate = mutationRate; + newMastership = mastership; + } + + // Any change in the following parameters implies hard restart + if (newEnabled != enabled || !newTopoShape.equals(topoShape) || + newDeviceCount != deviceCount || newHostCount != hostCount) { + enabled = newEnabled; + topoShape = newTopoShape; + deviceCount = newDeviceCount; + hostCount = newHostCount; + packetRate = newPacketRate; + mutationRate = newMutationRate; + restartSimulation(); + } + + // Any change in the following parameters implies just a rate change + if (newPacketRate != packetRate || newMutationRate != mutationRate) { + packetRate = newPacketRate; + mutationRate = newMutationRate; + adjustRates(); + } + + // Any change in mastership implies just reassignments. + if (!newMastership.equals(mastership)) { + mastership = newMastership; + reassignMastership(); + } + + log.info(FORMAT, enabled, topoShape, deviceCount, hostCount, + packetRate, mutationRate); + } + + /** + * Severs the link between the specified end-points in both directions. + * + * @param one link endpoint + * @param two link endpoint + */ + public void severLink(ConnectPoint one, ConnectPoint two) { + if (enabled) { + topologyMutationDriver.severLink(one, two); + } + } + + /** + * Severs the link between the specified end-points in both directions. + * + * @param one link endpoint + * @param two link endpoint + */ + public void repairLink(ConnectPoint one, ConnectPoint two) { + if (enabled) { + topologyMutationDriver.repairLink(one, two); + } + } + + // Resets simulation based on the current configuration parameters. + private void restartSimulation() { + tearDown(); + if (enabled) { + setUp(); + } + } + + // Sets up the topology simulation and all providers. + private void setUp() { + simulator = selectSimulator(topoShape); + simulator.init(topoShape, deviceCount, hostCount, + new DefaultServiceDirectory(), + deviceProviderService, hostProviderService, + linkProviderService); + simulator.setUpTopology(); + flowRuleProvider.start(flowRuleProviderService); + packetProvider.start(packetRate, hostService, deviceService, + packetProviderService); + topologyMutationDriver.start(mutationRate, linkService, deviceService, + linkProviderService); + } + + // Selects the simulator based on the specified name. + private TopologySimulator selectSimulator(String topoShape) { + if (topoShape.matches("linear([,].*|$)")) { + return new LinearTopologySimulator(); + } else if (topoShape.matches("centipede([,].*|$)")) { + return new CentipedeTopologySimulator(); + } else if (topoShape.matches("reroute([,].*|$)")) { + return new RerouteTopologySimulator(); + } else if (topoShape.matches("tree([,].*|$)")) { + return new TreeTopologySimulator(); + } else if (topoShape.matches("agglink([,].*|$)")) { + return new AggLinkTopologySimulator(); + } else if (topoShape.matches("spineleaf([,].*|$)")) { + return new SpineLeafTopologySimulator(); + } else if (topoShape.matches("mesh([,].*|$)")) { + return new MeshTopologySimulator(); + } else { + return new ConfiguredTopologySimulator(); + } + } + + // Shuts down the topology simulator and all providers. + private void tearDown() { + if (simulator != null) { + topologyMutationDriver.stop(); + packetProvider.stop(); + flowRuleProvider.stop(); + delay(500); + simulator.tearDownTopology(); + simulator = null; + } + } + + // Changes packet and mutation rates. + private void adjustRates() { + packetProvider.adjustRate(packetRate); + topologyMutationDriver.adjustRate(mutationRate); + } + + // Re-assigns mastership roles. + private void reassignMastership() { + if (mastership.equals(DEFAULT_MASTERSHIP)) { + mastershipService.balanceRoles(); + } else { + NodeId localNode = clusterService.getLocalNode().id(); + rejectMastership(); + String[] nodeSpecs = mastership.split("/"); + for (int i = 0; i < nodeSpecs.length; i++) { + String[] specs = nodeSpecs[i].split("="); + if (specs[0].equals(localNode.toString())) { + String[] ids = specs[1].split(","); + for (String id : ids) { + mastershipService.setRole(localNode, deviceId(id), MASTER); + } + break; + } + } + } + } + + // Rejects mastership of all devices. + private void rejectMastership() { + NodeId localNode = clusterService.getLocalNode().id(); + deviceService.getDevices() + .forEach(device -> mastershipService.setRole(localNode, device.id(), + NONE)); + } + + // Null provider base class. + abstract static class AbstractNullProvider extends AbstractProvider { + protected AbstractNullProvider() { + super(new ProviderId(SCHEME, PROVIDER_ID)); + } + } + + // Device provider facade. + private class NullDeviceProvider extends AbstractNullProvider implements DeviceProvider { + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + deviceProviderService.receivedRoleReply(deviceId, newRole, newRole); + } + + @Override + public boolean isReachable(DeviceId deviceId) { + return topoShape.equals("configured") || + (simulator != null && simulator.contains(deviceId)); + } + + @Override + public void triggerProbe(DeviceId deviceId) { + } + } + + // Host provider facade. + private class NullHostProvider extends AbstractNullProvider implements HostProvider { + @Override + public void triggerProbe(Host host) { + } + } + + // Host provider facade. + private class NullLinkProvider extends AbstractNullProvider implements LinkProvider { + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/RerouteTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/RerouteTopologySimulator.java new file mode 100644 index 00000000..d17466b9 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/RerouteTopologySimulator.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Re-routable linear topology simulator with an alternate path in the middle. + */ +public class RerouteTopologySimulator extends LinearTopologySimulator { + + @Override + protected void processTopoShape(String shape) { + super.processTopoShape(shape); + infrastructurePorts = 5; + deviceCount = (topoShape.length == 1) ? deviceCount : Integer.parseInt(topoShape[1]); + } + + @Override + public void setUpTopology() { + checkArgument(deviceCount > 2, "There must be at least 3 devices"); + super.setUpTopology(); + } + + @Override + protected void createLinks() { + int portOffset = 1; + for (int i = 0, n = deviceCount - 2; i < n; i++) { + createLink(i, i + 1, portOffset, 1); + portOffset = 2; + } + int middle = (deviceCount - 1) / 2; + int alternate = deviceCount - 1; + createLink(middle - 1, alternate, 3, 1); + createLink(middle, alternate, 3, 2); + } + + @Override + protected void createHosts() { + createHosts(deviceIds.get(0), infrastructurePorts); + createHosts(deviceIds.get(deviceCount - 2), infrastructurePorts); + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/SpineLeafTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/SpineLeafTopologySimulator.java new file mode 100644 index 00000000..876fc5dc --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/SpineLeafTopologySimulator.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +/** + * Spine-leaf topology with hosts at the leaf devices. + */ +public class SpineLeafTopologySimulator extends TopologySimulator { + + @Override + protected void processTopoShape(String shape) { + super.processTopoShape(shape); + // FIXME: implement this + } + + @Override + public void setUpTopology() { + // checkArgument(FIXME, "There must be at least one spine tier"); + super.setUpTopology(); + } + + @Override + protected void createLinks() { + } + + @Override + protected void createHosts() { + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TopologyMutationDriver.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TopologyMutationDriver.java new file mode 100644 index 00000000..ccf7e08a --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TopologyMutationDriver.java @@ -0,0 +1,223 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import com.google.common.collect.Lists; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.link.LinkService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.Random; +import java.util.concurrent.ExecutorService; +import java.util.stream.Collectors; + +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; +import static org.onlab.util.Tools.delay; +import static org.onlab.util.Tools.groupedThreads; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.MastershipRole.MASTER; +import static org.onosproject.provider.nil.TopologySimulator.description; + +/** + * Drives topology mutations at a specified rate of events per second. + */ +class TopologyMutationDriver implements Runnable { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + private static final int WAIT_DELAY = 2_000; + private static final int MAX_DOWN_LINKS = 5; + + private final Random random = new Random(); + + private volatile boolean stopped = true; + + private double mutationRate; + private int millis, nanos; + + private LinkService linkService; + private DeviceService deviceService; + private LinkProviderService linkProviderService; + + private List<LinkDescription> activeLinks; + private List<LinkDescription> inactiveLinks; + + private final ExecutorService executor = + newSingleThreadScheduledExecutor(groupedThreads("onos/null", "topo-mutator")); + + /** + * Starts the mutation process. + * + * @param mutationRate link events per second + * @param linkService link service + * @param deviceService device service + * @param linkProviderService link provider service + */ + void start(double mutationRate, + LinkService linkService, DeviceService deviceService, + LinkProviderService linkProviderService) { + stopped = false; + this.linkService = linkService; + this.deviceService = deviceService; + this.linkProviderService = linkProviderService; + activeLinks = reduceLinks(); + inactiveLinks = Lists.newArrayList(); + adjustRate(mutationRate); + executor.submit(this); + } + + /** + * Adjusts the topology mutation rate. + * + * @param mutationRate new topology mutation rate + */ + void adjustRate(double mutationRate) { + this.mutationRate = mutationRate; + if (mutationRate > 0) { + this.millis = (int) (1_000 / mutationRate / 2); + this.nanos = (int) (1_000_000 / mutationRate / 2) % 1_000_000; + } else { + this.millis = 0; + this.nanos = 0; + } + log.info("Settings: millis={}, nanos={}", millis, nanos); + } + + /** + * Stops the mutation process. + */ + void stop() { + stopped = true; + } + + /** + * Severs the link between the specified end-points in both directions. + * + * @param one link endpoint + * @param two link endpoint + */ + void severLink(ConnectPoint one, ConnectPoint two) { + LinkDescription link = new DefaultLinkDescription(one, two, DIRECT); + linkProviderService.linkVanished(link); + linkProviderService.linkVanished(reverse(link)); + + } + + /** + * Repairs the link between the specified end-points in both directions. + * + * @param one link endpoint + * @param two link endpoint + */ + void repairLink(ConnectPoint one, ConnectPoint two) { + LinkDescription link = new DefaultLinkDescription(one, two, DIRECT); + linkProviderService.linkDetected(link); + linkProviderService.linkDetected(reverse(link)); + } + + @Override + public void run() { + delay(WAIT_DELAY); + + while (!stopped) { + if (mutationRate > 0 && inactiveLinks.isEmpty()) { + primeInactiveLinks(); + } else if (mutationRate <= 0 && !inactiveLinks.isEmpty()) { + repairInactiveLinks(); + } else if (inactiveLinks.isEmpty()) { + delay(WAIT_DELAY); + + } else { + activeLinks.add(repairLink()); + pause(); + inactiveLinks.add(severLink()); + pause(); + } + } + } + + // Primes the inactive links with a few random links. + private void primeInactiveLinks() { + for (int i = 0, n = Math.min(MAX_DOWN_LINKS, activeLinks.size()); i < n; i++) { + inactiveLinks.add(severLink()); + } + } + + // Repairs all inactive links. + private void repairInactiveLinks() { + while (!inactiveLinks.isEmpty()) { + repairLink(); + } + } + + // Picks a random active link and severs it. + private LinkDescription severLink() { + LinkDescription link = getRandomLink(activeLinks); + linkProviderService.linkVanished(link); + linkProviderService.linkVanished(reverse(link)); + return link; + } + + // Picks a random inactive link and repairs it. + private LinkDescription repairLink() { + LinkDescription link = getRandomLink(inactiveLinks); + linkProviderService.linkDetected(link); + linkProviderService.linkDetected(reverse(link)); + return link; + } + + // Produces a reverse of the specified link. + private LinkDescription reverse(LinkDescription link) { + return new DefaultLinkDescription(link.dst(), link.src(), link.type()); + } + + // Returns a random link from the specified list of links. + private LinkDescription getRandomLink(List<LinkDescription> links) { + return links.remove(random.nextInt(links.size())); + } + + // Reduces the given list of links to just a single link in each original pair. + private List<LinkDescription> reduceLinks() { + List<LinkDescription> links = Lists.newArrayList(); + linkService.getLinks().forEach(link -> links.add(description(link))); + return links.stream() + .filter(this::isOurLink) + .filter(this::isRightDirection) + .collect(Collectors.toList()); + } + + // Returns true if the specified link is ours. + private boolean isOurLink(LinkDescription linkDescription) { + return deviceService.getRole(linkDescription.src().deviceId()) == MASTER; + } + + // Returns true if the link source is greater than the link destination. + private boolean isRightDirection(LinkDescription link) { + return link.src().deviceId().toString().compareTo(link.dst().deviceId().toString()) > 0; + } + + // Pauses the current thread for the pre-computed time of millis & nanos. + private void pause() { + delay(millis, nanos); + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java new file mode 100644 index 00000000..9f2320ed --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TopologySimulator.java @@ -0,0 +1,382 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import com.google.common.collect.Lists; +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.ChassisId; +import org.onlab.packet.IpAddress; +import org.onlab.packet.MacAddress; +import org.onlab.packet.VlanId; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.NodeId; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.HostLocation; +import org.onosproject.net.Link; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DeviceAdminService; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceEvent; +import org.onosproject.net.device.DeviceListener; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.host.DefaultHostDescription; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.host.HostService; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.link.LinkService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.onlab.util.Tools.toHex; +import static org.onosproject.net.HostId.hostId; +import static org.onosproject.net.Link.Type.DIRECT; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.net.device.DeviceEvent.Type.*; +import static org.onosproject.provider.nil.NullProviders.SCHEME; + +/** + * Abstraction of a provider capable to simulate some network topology. + */ +public abstract class TopologySimulator { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + protected String[] topoShape; + protected int deviceCount; + protected int hostCount; + + protected ServiceDirectory directory; + protected NodeId localNode; + + protected ClusterService clusterService; + protected MastershipService mastershipService; + + protected DeviceAdminService deviceService; + protected HostService hostService; + protected LinkService linkService; + + protected DeviceProviderService deviceProviderService; + protected HostProviderService hostProviderService; + protected LinkProviderService linkProviderService; + + protected int maxWaitSeconds = 1; + protected int infrastructurePorts = 2; + protected CountDownLatch deviceLatch; + + protected final List<DeviceId> deviceIds = Lists.newArrayList(); + + private DeviceListener deviceEventCounter = new DeviceEventCounter(); + + /** + * Initializes a new topology simulator with access to the specified service + * directory and various provider services. + * + * @param topoShape topology shape specifier + * @param deviceCount number of devices in the topology + * @param hostCount number of hosts per device + * @param directory service directory + * @param deviceProviderService device provider service + * @param hostProviderService host provider service + * @param linkProviderService link provider service + */ + protected void init(String topoShape, int deviceCount, int hostCount, + ServiceDirectory directory, + DeviceProviderService deviceProviderService, + HostProviderService hostProviderService, + LinkProviderService linkProviderService) { + this.deviceCount = deviceCount; + this.hostCount = hostCount; + this.directory = directory; + + this.clusterService = directory.get(ClusterService.class); + this.mastershipService = directory.get(MastershipService.class); + + this.deviceService = directory.get(DeviceAdminService.class); + this.hostService = directory.get(HostService.class); + this.linkService = directory.get(LinkService.class); + this.deviceProviderService = deviceProviderService; + this.hostProviderService = hostProviderService; + this.linkProviderService = linkProviderService; + + localNode = clusterService.getLocalNode().id(); + + processTopoShape(topoShape); + } + + /** + * Processes the topology shape specifier. + * + * @param shape topology shape specifier + */ + protected void processTopoShape(String shape) { + this.topoShape = shape.split(","); + } + + /** + * Sets up network topology simulation. + */ + public void setUpTopology() { + prepareForDeviceEvents(deviceCount); + createDevices(); + waitForDeviceEvents(); + + createLinks(); + createHosts(); + } + + /** + * Creates simulated devices. + */ + protected void createDevices() { + for (int i = 0; i < deviceCount; i++) { + createDevice(i + 1); + } + } + + /** + * Creates simulated links. + */ + protected abstract void createLinks(); + + /** + * Creates simulated hosts. + */ + protected abstract void createHosts(); + + /** + * Creates simulated device. + * + * @param i index of the device id in the list. + */ + protected void createDevice(int i) { + DeviceId id = DeviceId.deviceId(SCHEME + ":" + toHex(i)); + DeviceDescription desc = + new DefaultDeviceDescription(id.uri(), Device.Type.SWITCH, + "ON.Lab", "0.1", "0.1", "1234", + new ChassisId(i)); + deviceIds.add(id); + deviceProviderService.deviceConnected(id, desc); + deviceProviderService.updatePorts(id, buildPorts(hostCount + infrastructurePorts)); + } + +// /** +// * Creates simulated link between two devices on port 1 and port 2. +// * +// * @param i index of one simulated device +// * @param j index of another simulated device +// */ +// protected void createLink(int i, int j) { +// createLink(i, j, 1, 2); +// } + + /** + * Creates simulated link between two devices. + * + * @param i index of one simulated device + * @param j index of another simulated device + * @param pi port number of i-th device + * @param pj port number of j-th device + */ + protected void createLink(int i, int j, int pi, int pj) { + ConnectPoint one = new ConnectPoint(deviceIds.get(i), PortNumber.portNumber(pi)); + ConnectPoint two = new ConnectPoint(deviceIds.get(j), PortNumber.portNumber(pj)); + linkProviderService.linkDetected(new DefaultLinkDescription(one, two, DIRECT)); + linkProviderService.linkDetected(new DefaultLinkDescription(two, one, DIRECT)); + } + + /** + * Creates simularted hosts for the specified device. + * + * @param deviceId device identifier + * @param portOffset port offset where to start attaching hosts + */ + protected void createHosts(DeviceId deviceId, int portOffset) { + String s = deviceId.toString(); + byte dByte = Byte.parseByte(s.substring(s.length() - 1), 16); + // TODO: this limits the simulation to 256 devices & 256 hosts/device. + byte[] macBytes = new byte[]{0, 0, 0, 0, dByte, 0}; + byte[] ipBytes = new byte[]{(byte) 192, (byte) 168, dByte, 0}; + + for (int i = 0; i < hostCount; i++) { + int port = portOffset + i + 1; + macBytes[5] = (byte) (i + 1); + ipBytes[3] = (byte) (i + 1); + HostId id = hostId(MacAddress.valueOf(macBytes), VlanId.NONE); + IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET, ipBytes); + hostProviderService.hostDetected(id, description(id, ip, deviceId, port)); + } + } + + /** + * Prepares to count device added/available/removed events. + * + * @param count number of events to count + */ + protected void prepareForDeviceEvents(int count) { + deviceLatch = new CountDownLatch(count); + deviceService.addListener(deviceEventCounter); + } + + /** + * Waits for all expected device added/available/removed events. + */ + protected void waitForDeviceEvents() { + try { + deviceLatch.await(maxWaitSeconds, TimeUnit.SECONDS); + } catch (InterruptedException e) { + log.warn("Device events did not arrive in time"); + } + deviceService.removeListener(deviceEventCounter); + } + + + /** + * Tears down network topology simulation. + */ + public void tearDownTopology() { + removeHosts(); + removeLinks(); + removeDevices(); + } + + /** + * Removes any hosts previously advertised by this provider. + */ + protected void removeHosts() { + hostService.getHosts() + .forEach(host -> hostProviderService.hostVanished(host.id())); + } + + /** + * Removes any links previously advertised by this provider. + */ + protected void removeLinks() { + linkService.getLinks() + .forEach(link -> linkProviderService.linkVanished(description(link))); + } + + /** + * Removes any devices previously advertised by this provider. + */ + protected void removeDevices() { + prepareForDeviceEvents(deviceIds.size()); + deviceIds.forEach(deviceProviderService::deviceDisconnected); + waitForDeviceEvents(); + deviceIds.clear(); + } + + + /** + * Produces a device description from the given device. + * + * @param device device to copy + * @return device description + */ + static DeviceDescription description(Device device) { + return new DefaultDeviceDescription(device.id().uri(), device.type(), + device.manufacturer(), + device.hwVersion(), device.swVersion(), + device.serialNumber(), device.chassisId()); + } + + /** + * Produces a link description from the given link. + * + * @param link link to copy + * @return link description + */ + static DefaultLinkDescription description(Link link) { + return new DefaultLinkDescription(link.src(), link.dst(), link.type()); + } + + /** + * Produces a host description from the given host. + * + * @param host host to copy + * @return host description + */ + static DefaultHostDescription description(Host host) { + return new DefaultHostDescription(host.mac(), host.vlan(), host.location(), + host.ipAddresses()); + } + + /** + * Generates a host description from the given id and location information. + * + * @param hostId host identifier + * @param ip host IP + * @param deviceId edge device + * @param port edge port + * @return host description + */ + static HostDescription description(HostId hostId, IpAddress ip, + DeviceId deviceId, int port) { + HostLocation location = new HostLocation(deviceId, portNumber(port), 0L); + return new DefaultHostDescription(hostId.mac(), hostId.vlanId(), location, ip); + } + + /** + * Generates a list of a configured number of ports. + * + * @param portCount number of ports + * @return list of ports + */ + protected List<PortDescription> buildPorts(int portCount) { + List<PortDescription> ports = Lists.newArrayList(); + for (int i = 0; i < portCount; i++) { + ports.add(new DefaultPortDescription(PortNumber.portNumber(i), true, + Port.Type.COPPER, 0)); + } + return ports; + } + + /** + * Indicates whether or not the simulation knows of this device. + * + * @param deviceId device identifier + * @return true if device is known + */ + public boolean contains(DeviceId deviceId) { + return deviceIds.contains(deviceId); + } + + // Counts down number of device added/available/removed events. + private class DeviceEventCounter implements DeviceListener { + @Override + public void event(DeviceEvent event) { + DeviceEvent.Type type = event.type(); + if (type == DEVICE_ADDED || type == DEVICE_REMOVED || + type == DEVICE_AVAILABILITY_CHANGED) { + deviceLatch.countDown(); + } + } + } +}
\ No newline at end of file diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TreeTopologySimulator.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TreeTopologySimulator.java new file mode 100644 index 00000000..2c049333 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/TreeTopologySimulator.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * Tree topology with hosts at the leaf devices. + */ +public class TreeTopologySimulator extends TopologySimulator { + + private int[] tierMultiplier; + private int[] tierDeviceCount; + private int[] tierOffset; + + @Override + protected void processTopoShape(String shape) { + super.processTopoShape(shape); + tierOffset = new int[topoShape.length]; + tierMultiplier = new int[topoShape.length]; + tierDeviceCount = new int[topoShape.length]; + + deviceCount = 1; + + tierOffset[0] = 0; + tierMultiplier[0] = 1; + tierDeviceCount[0] = deviceCount; + + for (int i = 1; i < topoShape.length; i++) { + tierOffset[i] = deviceCount; + tierMultiplier[i] = Integer.parseInt(topoShape[i]); + tierDeviceCount[i] = tierDeviceCount[i - 1] * tierMultiplier[i]; + deviceCount = deviceCount + tierDeviceCount[i]; + } + } + + @Override + public void setUpTopology() { + checkArgument(tierDeviceCount.length > 0, "There must be at least one tree tier"); + super.setUpTopology(); + } + + @Override + protected void createLinks() { + int portOffset = 1; + for (int t = 1; t < tierOffset.length; t++) { + int child = tierOffset[t]; + for (int parent = tierOffset[t - 1]; parent < tierOffset[t]; parent++) { + for (int i = 0; i < tierMultiplier[t]; i++) { + createLink(parent, child, i + portOffset, 1); + child++; + } + } + portOffset = 2; // beyond first tier, allow for up-links + } + } + + @Override + protected void createHosts() { + for (int i = tierOffset[tierOffset.length - 1]; i < deviceCount; i++) { + createHosts(deviceIds.get(i), hostCount); + } + } +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullControlCommand.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullControlCommand.java new file mode 100644 index 00000000..261f46de --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullControlCommand.java @@ -0,0 +1,52 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.provider.nil.NullProviders; + +import static org.onosproject.cli.StartStopCompleter.START; + +/** + * Starts or stops topology simulation. + */ +@Command(scope = "onos", name = "null-simulation", + description = "Starts or stops topology simulation") +public class NullControlCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "cmd", description = "Control command: start/stop", + required = true, multiValued = false) + String cmd = null; + + @Argument(index = 1, name = "topoShape", + description = "Topology shape: e.g. configured, linear, reroute, centipede, tree, spineleaf, mesh", + required = false, multiValued = false) + String topoShape = null; + + @Override + protected void execute() { + ComponentConfigService service = get(ComponentConfigService.class); + if (topoShape != null) { + service.setProperty(NullProviders.class.getName(), "topoShape", topoShape); + } + service.setProperty(NullProviders.class.getName(), "enabled", + cmd.equals(START) ? "true" : "false"); + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java new file mode 100644 index 00000000..a76da3b8 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/NullLinkCommand.java @@ -0,0 +1,68 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.net.ConnectPoint; +import org.onosproject.provider.nil.NullProviders; + +import static org.onosproject.cli.UpDownCompleter.DOWN; +import static org.onosproject.cli.UpDownCompleter.UP; + +/** + * Servers or repairs a simulated link. + */ +@Command(scope = "onos", name = "null-link", + description = "Severs or repairs a simulated link") +public class NullLinkCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "one", description = "One link end-point as device/port", + required = true, multiValued = false) + String one = null; + + @Argument(index = 1, name = "two", description = "Another link end-point as device/port", + required = true, multiValued = false) + String two = null; + + @Argument(index = 2, name = "cmd", description = "up/down", + required = true, multiValued = false) + String cmd = null; + + + @Override + protected void execute() { + NullProviders service = get(NullProviders.class); + + try { + ConnectPoint onePoint = ConnectPoint.deviceConnectPoint(one); + + ConnectPoint twoPoint = ConnectPoint.deviceConnectPoint(two); + + if (cmd.equals(UP)) { + service.repairLink(onePoint, twoPoint); + } else if (cmd.equals(DOWN)) { + service.severLink(onePoint, twoPoint); + } else { + error("Illegal command %s; must be up or down", cmd); + } + } catch (NumberFormatException e) { + error("Invalid port number specified", e); + } + } + +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/TopologyShapeCompleter.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/TopologyShapeCompleter.java new file mode 100644 index 00000000..fad29b6e --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/TopologyShapeCompleter.java @@ -0,0 +1,32 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.nil.cli; + +import com.google.common.collect.ImmutableList; +import org.onosproject.cli.AbstractChoicesCompleter; + +import java.util.List; + +/** + * Topology shape completer. + */ +public class TopologyShapeCompleter extends AbstractChoicesCompleter { + @Override + public List<String> choices() { + return ImmutableList.of("configured", "linear", "reroute", "centipede", + "tree", "spineleaf", "mesh"); + } +} diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/package-info.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/package-info.java new file mode 100644 index 00000000..f876ced3 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/cli/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Null provider CLI commands and completers. + */ +package org.onosproject.provider.nil.cli; diff --git a/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/package-info.java b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/package-info.java new file mode 100644 index 00000000..e8d309f7 --- /dev/null +++ b/framework/src/onos/providers/null/src/main/java/org/onosproject/provider/nil/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Set of null south-bound providers which permit simulating a network + * topology using fake devices, links, hosts, etc. + */ +package org.onosproject.provider.nil; diff --git a/framework/src/onos/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml new file mode 100644 index 00000000..f96f10cc --- /dev/null +++ b/framework/src/onos/providers/null/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -0,0 +1,44 @@ +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"> + + <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0"> + <command> + <action class="org.onosproject.provider.nil.cli.NullControlCommand"/> + <completers> + <ref component-id="startStopCompleter"/> + <ref component-id="topoShapeCompleter"/> + <null/> + </completers> + </command> + <command> + <action class="org.onosproject.provider.nil.cli.NullLinkCommand"/> + <completers> + <ref component-id="linkSrcCompleter"/> + <ref component-id="linkDstCompleter"/> + <ref component-id="upDownCompleter"/> + <null/> + </completers> + </command> + </command-bundle> + + <bean id="startStopCompleter" class="org.onosproject.cli.StartStopCompleter"/> + <bean id="upDownCompleter" class="org.onosproject.cli.UpDownCompleter"/> + <bean id="topoShapeCompleter" class="org.onosproject.provider.nil.cli.TopologyShapeCompleter"/> + <bean id="linkSrcCompleter" class="org.onosproject.cli.net.LinkSrcCompleter"/> + <bean id="linkDstCompleter" class="org.onosproject.cli.net.LinkDstCompleter"/> + +</blueprint> diff --git a/framework/src/onos/providers/openflow/app/app.xml b/framework/src/onos/providers/openflow/app/app.xml new file mode 100644 index 00000000..e54d1a8a --- /dev/null +++ b/framework/src/onos/providers/openflow/app/app.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<app name="org.onosproject.openflow" origin="ON.Lab" version="${project.version}" + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" + features="${project.artifactId}"> + <description>${project.description}</description> + + <artifact>mvn:${project.groupId}/onos-of-api/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-of-ctl/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-drivers/${project.version}</artifact> + + <artifact>mvn:${project.groupId}/onos-lldp-provider/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-host-provider/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-of-provider-device/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-of-provider-packet/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-of-provider-flow/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-of-provider-group/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-of-provider-meter/${project.version}</artifact> +</app> diff --git a/framework/src/onos/providers/openflow/app/features.xml b/framework/src/onos/providers/openflow/app/features.xml new file mode 100644 index 00000000..7c410172 --- /dev/null +++ b/framework/src/onos/providers/openflow/app/features.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> + <feature name="${project.artifactId}" version="${project.version}" + description="${project.description}"> + <feature>onos-api</feature> + <bundle>mvn:io.netty/netty/3.9.2.Final</bundle> + <bundle>mvn:${project.groupId}/onos-of-api/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-ctl/${project.version}</bundle> + + <bundle>mvn:${project.groupId}/onos-lldp-provider/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-host-provider/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-provider-device/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-provider-packet/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-provider-flow/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-provider-group/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-of-provider-meter/${project.version}</bundle> + </feature> +</features> diff --git a/framework/src/onos/providers/openflow/app/pom.xml b/framework/src/onos/providers/openflow/app/pom.xml new file mode 100644 index 00000000..62e5eb87 --- /dev/null +++ b/framework/src/onos/providers/openflow/app/pom.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-openflow</artifactId> + <packaging>pom</packaging> + + <description>OpenFlow protocol southbound providers</description> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-api</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-ctl</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-drivers</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-provider-device</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-provider-packet</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-provider-flow</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-provider-group</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-lldp-provider</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-host-provider</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/openflow/device/pom.xml b/framework/src/onos/providers/openflow/device/pom.xml new file mode 100644 index 00000000..5f27d42b --- /dev/null +++ b/framework/src/onos/providers/openflow/device/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-provider-device</artifactId> + <packaging>bundle</packaging> + + <description>ONOS OpenFlow protocol device provider</description> + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + </dependencies> +</project> diff --git a/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java new file mode 100644 index 00000000..cb19dc52 --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProvider.java @@ -0,0 +1,569 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.device.impl; + +import com.google.common.base.Strings; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.ChassisId; +import org.onlab.util.Frequency; +import org.onosproject.cfg.ComponentConfigService; +import org.onlab.util.Spectrum; +import org.onosproject.net.AnnotationKeys; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DeviceId; +import org.onosproject.net.GridType; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.OchSignal; +import org.onosproject.net.OduSignalType; +import org.onosproject.net.Port; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DefaultPortStatistics; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.OchPortDescription; +import org.onosproject.net.device.OmsPortDescription; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.PortDescPropertyType; +import org.onosproject.openflow.controller.RoleState; +import org.osgi.service.component.ComponentContext; +import org.projectfloodlight.openflow.protocol.OFCalientPortDescStatsEntry; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortConfig; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescPropOpticalTransport; +import org.projectfloodlight.openflow.protocol.OFPortFeatures; +import org.projectfloodlight.openflow.protocol.OFPortOptical; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortState; +import org.projectfloodlight.openflow.protocol.OFPortStatsEntry; +import org.projectfloodlight.openflow.protocol.OFPortStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.types.PortSpeed; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onlab.util.Tools.get; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.Port.Type.COPPER; +import static org.onosproject.net.Port.Type.FIBER; +import static org.onosproject.openflow.controller.Dpid.dpid; +import static org.onosproject.openflow.controller.Dpid.uri; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to detect network + * infrastructure devices. + */ +@Component(immediate = true) +public class OpenFlowDeviceProvider extends AbstractProvider implements DeviceProvider { + + private static final Logger LOG = getLogger(OpenFlowDeviceProvider.class); + private static final long MBPS = 1_000 * 1_000; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenFlowController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private DeviceProviderService providerService; + + private final InternalDeviceProvider listener = new InternalDeviceProvider(); + + // TODO: We need to make the poll interval configurable. + static final int POLL_INTERVAL = 5; + @Property(name = "PortStatsPollFrequency", intValue = POLL_INTERVAL, + label = "Frequency (in seconds) for polling switch Port statistics") + private int portStatsPollFrequency = POLL_INTERVAL; + + private HashMap<Dpid, PortStatsCollector> collectors = Maps.newHashMap(); + + /** + * Creates an OpenFlow device provider. + */ + public OpenFlowDeviceProvider() { + super(new ProviderId("of", "org.onosproject.provider.openflow")); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + providerService = providerRegistry.register(this); + controller.addListener(listener); + controller.addEventListener(listener); + for (OpenFlowSwitch sw : controller.getSwitches()) { + try { + listener.switchAdded(new Dpid(sw.getId())); + } catch (Exception e) { + LOG.warn("Failed initially adding {} : {}", sw.getStringId(), e.getMessage()); + LOG.debug("Error details:", e); + // disconnect to trigger switch-add later + sw.disconnectSwitch(); + } + PortStatsCollector psc = new PortStatsCollector(sw, portStatsPollFrequency); + psc.start(); + collectors.put(new Dpid(sw.getId()), psc); + } + LOG.info("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + cfgService.unregisterProperties(getClass(), false); + providerRegistry.unregister(this); + controller.removeListener(listener); + collectors.values().forEach(PortStatsCollector::stop); + providerService = null; + LOG.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + int newPortStatsPollFrequency; + try { + String s = get(properties, "PortStatsPollFrequency"); + newPortStatsPollFrequency = isNullOrEmpty(s) ? portStatsPollFrequency : Integer.parseInt(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + newPortStatsPollFrequency = portStatsPollFrequency; + } + + if (newPortStatsPollFrequency != portStatsPollFrequency) { + portStatsPollFrequency = newPortStatsPollFrequency; + collectors.values().forEach(psc -> psc.adjustPollInterval(portStatsPollFrequency)); + } + + LOG.info("Settings: portStatsPollFrequency={}", portStatsPollFrequency); + } + + @Override + public boolean isReachable(DeviceId deviceId) { + OpenFlowSwitch sw = controller.getSwitch(dpid(deviceId.uri())); + if (sw == null || !sw.isConnected()) { + return false; + } + return true; + } + + @Override + public void triggerProbe(DeviceId deviceId) { + LOG.debug("Triggering probe on device {}", deviceId); + + final Dpid dpid = dpid(deviceId.uri()); + OpenFlowSwitch sw = controller.getSwitch(dpid); + if (sw == null || !sw.isConnected()) { + LOG.error("Failed to probe device {} on sw={}", deviceId, sw); + providerService.deviceDisconnected(deviceId); + return; + } else { + LOG.trace("Confirmed device {} connection", deviceId); + } + + // Prompt an update of port information. We can use any XID for this. + OFFactory fact = sw.factory(); + switch (fact.getVersion()) { + case OF_10: + sw.sendMsg(fact.buildFeaturesRequest().setXid(0).build()); + break; + case OF_13: + sw.sendMsg(fact.buildPortDescStatsRequest().setXid(0).build()); + break; + default: + LOG.warn("Unhandled protocol version"); + } + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + switch (newRole) { + case MASTER: + controller.setRole(dpid(deviceId.uri()), RoleState.MASTER); + break; + case STANDBY: + controller.setRole(dpid(deviceId.uri()), RoleState.EQUAL); + break; + case NONE: + controller.setRole(dpid(deviceId.uri()), RoleState.SLAVE); + break; + default: + LOG.error("Unknown Mastership state : {}", newRole); + + } + LOG.debug("Accepting mastership role change for device {}", deviceId); + } + + private void pushPortMetrics(Dpid dpid, List<OFPortStatsEntry> portStatsEntries) { + DeviceId deviceId = DeviceId.deviceId(dpid.uri(dpid)); + Collection<PortStatistics> stats = buildPortStatistics(deviceId, portStatsEntries); + providerService.updatePortStatistics(deviceId, stats); + } + + private Collection<PortStatistics> buildPortStatistics(DeviceId deviceId, + List<OFPortStatsEntry> entries) { + HashSet<PortStatistics> stats = Sets.newHashSet(); + + for (OFPortStatsEntry entry : entries) { + try { + if (entry == null || entry.getPortNo() == null || entry.getPortNo().getPortNumber() < 0) { + continue; + } + DefaultPortStatistics.Builder builder = DefaultPortStatistics.builder(); + DefaultPortStatistics stat = builder.setDeviceId(deviceId) + .setPort(entry.getPortNo().getPortNumber()) + .setPacketsReceived(entry.getRxPackets().getValue()) + .setPacketsSent(entry.getTxPackets().getValue()) + .setBytesReceived(entry.getRxBytes().getValue()) + .setBytesSent(entry.getTxBytes().getValue()) + .setPacketsRxDropped(entry.getRxDropped().getValue()) + .setPacketsTxDropped(entry.getTxDropped().getValue()) + .setPacketsRxErrors(entry.getRxErrors().getValue()) + .setPacketsTxErrors(entry.getTxErrors().getValue()) + .setDurationSec(entry.getVersion() == OFVersion.OF_10 ? 0 : entry.getDurationSec()) + .setDurationNano(entry.getVersion() == OFVersion.OF_10 ? 0 : entry.getDurationNsec()) + .build(); + + stats.add(stat); + } catch (Exception e) { + LOG.warn("Unable to process port stats", e); + } + } + + return Collections.unmodifiableSet(stats); + + } + + private class InternalDeviceProvider implements OpenFlowSwitchListener, OpenFlowEventListener { + + private HashMap<Dpid, List<OFPortStatsEntry>> portStatsReplies = new HashMap<>(); + + @Override + public void switchAdded(Dpid dpid) { + if (providerService == null) { + return; + } + DeviceId did = deviceId(uri(dpid)); + OpenFlowSwitch sw = controller.getSwitch(dpid); + + ChassisId cId = new ChassisId(dpid.value()); + + SparseAnnotations annotations = DefaultAnnotations.builder() + .set("protocol", sw.factory().getVersion().toString()) + .set("channelId", sw.channelId()) + .build(); + + DeviceDescription description = + new DefaultDeviceDescription(did.uri(), sw.deviceType(), + sw.manufacturerDescription(), + sw.hardwareDescription(), + sw.softwareDescription(), + sw.serialNumber(), + cId, annotations); + providerService.deviceConnected(did, description); + providerService.updatePorts(did, buildPortDescriptions(sw)); + + PortStatsCollector psc = + new PortStatsCollector(controller.getSwitch(dpid), portStatsPollFrequency); + psc.start(); + collectors.put(dpid, psc); + } + + @Override + public void switchRemoved(Dpid dpid) { + if (providerService == null) { + return; + } + providerService.deviceDisconnected(deviceId(uri(dpid))); + + PortStatsCollector collector = collectors.remove(dpid); + if (collector != null) { + collector.stop(); + } + } + + @Override + public void switchChanged(Dpid dpid) { + if (providerService == null) { + return; + } + DeviceId did = deviceId(uri(dpid)); + OpenFlowSwitch sw = controller.getSwitch(dpid); + providerService.updatePorts(did, buildPortDescriptions(sw)); + } + + @Override + public void portChanged(Dpid dpid, OFPortStatus status) { + PortDescription portDescription = buildPortDescription(status); + providerService.portStatusChanged(deviceId(uri(dpid)), portDescription); + } + + @Override + public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) { + MastershipRole request = roleOf(requested); + MastershipRole reply = roleOf(response); + + providerService.receivedRoleReply(deviceId(uri(dpid)), request, reply); + } + + /** + * Translates a RoleState to the corresponding MastershipRole. + * + * @param response role state + * @return a MastershipRole + */ + private MastershipRole roleOf(RoleState response) { + switch (response) { + case MASTER: + return MastershipRole.MASTER; + case EQUAL: + return MastershipRole.STANDBY; + case SLAVE: + return MastershipRole.NONE; + default: + LOG.warn("unknown role {}", response); + return null; + } + } + + /** + * Builds a list of port descriptions for a given list of ports. + * + * @return list of portdescriptions + */ + private List<PortDescription> buildPortDescriptions(OpenFlowSwitch sw) { + final List<PortDescription> portDescs = new ArrayList<>(sw.getPorts().size()); + sw.getPorts().forEach(port -> portDescs.add(buildPortDescription(port))); + + OpenFlowOpticalSwitch opsw; + switch (sw.deviceType()) { + case ROADM: + opsw = (OpenFlowOpticalSwitch) sw; + opsw.getPortTypes().forEach(type -> { + opsw.getPortsOf(type).forEach( + op -> { + portDescs.add(buildPortDescription(type, (OFPortOptical) op)); + } + ); + }); + break; + case FIBER_SWITCH: + opsw = (OpenFlowOpticalSwitch) sw; + opsw.getPortTypes().forEach(type -> { + opsw.getPortsOf(type).forEach( + op -> { + portDescs.add(buildPortDescription((OFCalientPortDescStatsEntry) op)); + } + ); + }); + break; + default: + break; + } + + return portDescs; + } + + /** + * Creates an annotation for the port name if one is available. + * + * @param port description of the port + * @return annotation containing the port name if one is found, + * null otherwise + */ + private SparseAnnotations makePortNameAnnotation(String port) { + SparseAnnotations annotations = null; + String portName = Strings.emptyToNull(port); + if (portName != null) { + annotations = DefaultAnnotations.builder() + .set(AnnotationKeys.PORT_NAME, portName).build(); + } + return annotations; + } + + /** + * Build a portDescription from a given Ethernet port description. + * + * @param port the port to build from. + * @return portDescription for the port. + */ + private PortDescription buildPortDescription(OFPortDesc port) { + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + boolean enabled = + !port.getState().contains(OFPortState.LINK_DOWN) && + !port.getConfig().contains(OFPortConfig.PORT_DOWN); + Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER; + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + return new DefaultPortDescription(portNo, enabled, type, + portSpeed(port), annotations); + } + + /** + * Build a portDescription from a given a port description describing some + * Optical port. + * + * @param port description property type. + * @param port the port to build from. + * @return portDescription for the port. + */ + private PortDescription buildPortDescription(PortDescPropertyType ptype, OFPortOptical port) { + checkArgument(port.getDesc().size() >= 1); + + // Minimally functional fixture. This needs to be fixed as we add better support. + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + + boolean enabled = !port.getState().contains(OFPortState.LINK_DOWN) + && !port.getConfig().contains(OFPortConfig.PORT_DOWN); + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + + if (port.getVersion() == OFVersion.OF_13 + && ptype == PortDescPropertyType.OPTICAL_TRANSPORT) { + // At this point, not much is carried in the optical port message. + LOG.debug("Optical transport port message {}", port.toString()); + } else { + // removable once 1.4+ support complete. + LOG.debug("Unsupported optical port properties"); + } + + OFPortDescPropOpticalTransport desc = port.getDesc().get(0); + switch (desc.getPortSignalType()) { + // FIXME: use constants once loxi has full optical extensions + case 2: // OMS port + // Assume complete optical spectrum and 50 GHz grid + // LINC-OE is only supported optical OF device for now + return new OmsPortDescription(portNo, enabled, + Spectrum.U_BAND_MIN, Spectrum.O_BAND_MAX, Frequency.ofGHz(50), annotations); + case 5: // OCH port + OchSignal signal = new OchSignal(GridType.DWDM, ChannelSpacing.CHL_50GHZ, 0, 4); + return new OchPortDescription(portNo, enabled, OduSignalType.ODU4, + true, signal, annotations); + default: + break; + } + + return new DefaultPortDescription(portNo, enabled, FIBER, 0, annotations); + } + + /** + * Build a portDescription from a given port description describing a fiber switch optical port. + * + * @param port description property type. + * @param port the port to build from. + * @return portDescription for the port. + */ + private PortDescription buildPortDescription(OFCalientPortDescStatsEntry port) { + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + + // FIXME when Calient OF agent reports port status + boolean enabled = true; + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + + // S160 data sheet + // Wavelength range: 1260 - 1630 nm, grid is irrelevant for this type of switch + return new OmsPortDescription(portNo, enabled, + Spectrum.U_BAND_MIN, Spectrum.O_BAND_MAX, Frequency.ofGHz(100), annotations); + } + + private PortDescription buildPortDescription(OFPortStatus status) { + OFPortDesc port = status.getDesc(); + if (status.getReason() != OFPortReason.DELETE) { + return buildPortDescription(port); + } else { + PortNumber portNo = PortNumber.portNumber(port.getPortNo().getPortNumber()); + Port.Type type = port.getCurr().contains(OFPortFeatures.PF_FIBER) ? FIBER : COPPER; + SparseAnnotations annotations = makePortNameAnnotation(port.getName()); + return new DefaultPortDescription(portNo, false, type, + portSpeed(port), annotations); + } + } + + private long portSpeed(OFPortDesc port) { + if (port.getVersion() == OFVersion.OF_13) { + return port.getCurrSpeed() / MBPS; + } + + PortSpeed portSpeed = PortSpeed.SPEED_NONE; + for (OFPortFeatures feat : port.getCurr()) { + portSpeed = PortSpeed.max(portSpeed, feat.getPortSpeed()); + } + return portSpeed.getSpeedBps() / MBPS; + } + + @Override + public void handleMessage(Dpid dpid, OFMessage msg) { + switch (msg.getType()) { + case STATS_REPLY: + if (((OFStatsReply) msg).getStatsType() == OFStatsType.PORT) { + OFPortStatsReply portStatsReply = (OFPortStatsReply) msg; + List<OFPortStatsEntry> portStatsReplyList = portStatsReplies.get(dpid); + if (portStatsReplyList == null) { + portStatsReplyList = Lists.newArrayList(); + } + portStatsReplyList.addAll(portStatsReply.getEntries()); + portStatsReplies.put(dpid, portStatsReplyList); + if (!portStatsReply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { + pushPortMetrics(dpid, portStatsReplies.get(dpid)); + portStatsReplies.get(dpid).clear(); + } + } + break; + default: + break; + } + } + } + +} diff --git a/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/PortStatsCollector.java b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/PortStatsCollector.java new file mode 100644 index 00000000..8383fa3f --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/PortStatsCollector.java @@ -0,0 +1,113 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.of.device.impl; + +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFPortStatsRequest; +import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +import static org.slf4j.LoggerFactory.getLogger; + +/* + * Sends Group Stats Request and collect the group statistics with a time interval. + */ +public class PortStatsCollector implements TimerTask { + + // TODO: Refactoring is required using ScheduledExecutorService + + private final HashedWheelTimer timer = Timer.getTimer(); + private final OpenFlowSwitch sw; + private final Logger log = getLogger(getClass()); + private int refreshInterval; + private final AtomicLong xidAtomic = new AtomicLong(1); + + private Timeout timeout; + private volatile boolean stopped; + + /** + * Creates a GroupStatsCollector object. + * + * @param sw Open Flow switch + * @param interval time interval for collecting group statistic + */ + public PortStatsCollector(OpenFlowSwitch sw, int interval) { + this.sw = sw; + this.refreshInterval = interval; + } + + @Override + public void run(Timeout to) throws Exception { + if (stopped || timeout.isCancelled()) { + return; + } + log.trace("Collecting stats for {}", sw.getStringId()); + + sendPortStatistic(); + + if (!stopped && !timeout.isCancelled()) { + log.trace("Scheduling stats collection in {} seconds for {}", + this.refreshInterval, this.sw.getStringId()); + timeout.getTimer().newTimeout(this, refreshInterval, TimeUnit.SECONDS); + } + } + + synchronized void adjustPollInterval(int pollInterval) { + this.refreshInterval = pollInterval; + // task.cancel(); + // task = new InternalTimerTask(); + // timer.scheduleAtFixedRate(task, pollInterval * SECONDS, pollInterval * 1000); + } + + private void sendPortStatistic() { + if (sw.getRole() != RoleState.MASTER) { + return; + } + Long statsXid = xidAtomic.getAndIncrement(); + OFPortStatsRequest statsRequest = sw.factory().buildPortStatsRequest() + .setPortNo(OFPort.ANY) + .setXid(statsXid) + .build(); + sw.sendMsg(statsRequest); + } + + /** + * Starts the collector. + */ + public synchronized void start() { + log.info("Starting Port Stats collection thread for {}", sw.getStringId()); + stopped = false; + timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS); + } + + /** + * Stops the collector. + */ + public synchronized void stop() { + log.info("Stopping Port Stats collection thread for {}", sw.getStringId()); + stopped = true; + timeout.cancel(); + } +} diff --git a/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/package-info.java b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/package-info.java new file mode 100644 index 00000000..9376b47d --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/main/java/org/onosproject/provider/of/device/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses OpenFlow controller as a means of infrastructure device discovery. + */ +package org.onosproject.provider.of.device.impl; diff --git a/framework/src/onos/providers/openflow/device/src/test/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProviderTest.java b/framework/src/onos/providers/openflow/device/src/test/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProviderTest.java new file mode 100644 index 00000000..7b4d7922 --- /dev/null +++ b/framework/src/onos/providers/openflow/device/src/test/java/org/onosproject/provider/of/device/impl/OpenFlowDeviceProviderTest.java @@ -0,0 +1,402 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.device.impl; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Multimap; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.net.DefaultDevice; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.PacketListener; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortReason; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.ver10.OFFactoryVer10; +import org.projectfloodlight.openflow.types.OFPort; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.junit.Assert.*; +import static org.onosproject.net.Device.Type.SWITCH; +import static org.onosproject.net.MastershipRole.*; + +public class OpenFlowDeviceProviderTest { + + private static final ProviderId PID = new ProviderId("of", "test"); + private static final DeviceId DID1 = DeviceId.deviceId("of:0000000000000001"); + private static final Dpid DPID1 = Dpid.dpid(DID1.uri()); + + private static final OFPortDesc PD1 = portDesc(1); + private static final OFPortDesc PD2 = portDesc(2); + private static final OFPortDesc PD3 = portDesc(3); + + private static final List<OFPortDesc> PLIST = Lists.newArrayList(PD1, PD2); + + private static final Device DEV1 = + new DefaultDevice(PID, DID1, SWITCH, "", "", "", "", null); + + private static final TestOpenFlowSwitch SW1 = new TestOpenFlowSwitch(); + + private final OpenFlowDeviceProvider provider = new OpenFlowDeviceProvider(); + private final TestDeviceRegistry registry = new TestDeviceRegistry(); + private final TestController controller = new TestController(); + + @Before + public void startUp() { + provider.providerRegistry = registry; + provider.controller = controller; + provider.cfgService = new ComponentConfigAdapter(); + controller.switchMap.put(DPID1, SW1); + provider.activate(null); + assertNotNull("provider should be registered", registry.provider); + assertNotNull("listener should be registered", controller.listener); + assertEquals("devices not added", 1, registry.connected.size()); + assertEquals("ports not added", 2, registry.ports.get(DID1).size()); + } + + @After + public void tearDown() { + provider.deactivate(null); + assertNull("listener should be removed", controller.listener); + provider.controller = null; + provider.providerRegistry = null; + } + + @Test + public void roleChanged() { + provider.roleChanged(DID1, MASTER); + assertEquals("Should be MASTER", RoleState.MASTER, controller.roleMap.get(DPID1)); + provider.roleChanged(DID1, STANDBY); + assertEquals("Should be EQUAL", RoleState.EQUAL, controller.roleMap.get(DPID1)); + provider.roleChanged(DID1, NONE); + assertEquals("Should be SLAVE", RoleState.SLAVE, controller.roleMap.get(DPID1)); + } + + @Test + public void triggerProbe() { + + } + + @Test + public void switchRemoved() { + controller.listener.switchRemoved(DPID1); + assertTrue("device not removed", registry.connected.isEmpty()); + } + + @Test + public void portChanged() { + OFPortStatus stat = SW1.factory().buildPortStatus() + .setReason(OFPortReason.ADD) + .setDesc(PD3) + .build(); + controller.listener.portChanged(DPID1, stat); + assertNotNull("never went throught the provider service", registry.descr); + assertEquals("port status unhandled", 3, registry.ports.get(DID1).size()); + } + + @Test + public void receivedRoleReply() { + // check translation capabilities + controller.listener.receivedRoleReply(DPID1, RoleState.MASTER, RoleState.MASTER); + assertEquals("wrong role reported", DPID1, registry.roles.get(MASTER)); + controller.listener.receivedRoleReply(DPID1, RoleState.EQUAL, RoleState.MASTER); + assertEquals("wrong role reported", DPID1, registry.roles.get(STANDBY)); + controller.listener.receivedRoleReply(DPID1, RoleState.SLAVE, RoleState.MASTER); + assertEquals("wrong role reported", DPID1, registry.roles.get(NONE)); + } + + private static OFPortDesc portDesc(int port) { + OFPortDesc.Builder builder = OFFactoryVer10.INSTANCE.buildPortDesc(); + builder.setPortNo(OFPort.of(port)); + + return builder.build(); + } + + private class TestDeviceRegistry implements DeviceProviderRegistry { + DeviceProvider provider; + + Set<DeviceId> connected = new HashSet<>(); + Multimap<DeviceId, PortDescription> ports = HashMultimap.create(); + PortDescription descr = null; + Map<MastershipRole, Dpid> roles = new HashMap<>(); + + @Override + public DeviceProviderService register(DeviceProvider provider) { + this.provider = provider; + return new TestProviderService(); + } + + @Override + public void unregister(DeviceProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + private class TestProviderService implements DeviceProviderService { + + @Override + public DeviceProvider provider() { + return null; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + connected.add(deviceId); + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + connected.remove(deviceId); + ports.removeAll(deviceId); + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + for (PortDescription p : portDescriptions) { + ports.put(deviceId, p); + } + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + ports.put(deviceId, portDescription); + descr = portDescription; + } + + @Override + public void receivedRoleReply(DeviceId deviceId, + MastershipRole requested, MastershipRole response) { + roles.put(requested, Dpid.dpid(deviceId.uri())); + } + + @Override + public void updatePortStatistics(DeviceId deviceId, Collection<PortStatistics> portStatistics) { + + } + + } + } + + private class TestController implements OpenFlowController { + OpenFlowSwitchListener listener = null; + Map<Dpid, RoleState> roleMap = new HashMap<Dpid, RoleState>(); + Map<Dpid, OpenFlowSwitch> switchMap = new HashMap<Dpid, OpenFlowSwitch>(); + + @Override + public Iterable<OpenFlowSwitch> getSwitches() { + return switchMap.values(); + } + + @Override + public Iterable<OpenFlowSwitch> getMasterSwitches() { + return null; + } + + @Override + public Iterable<OpenFlowSwitch> getEqualSwitches() { + return null; + } + + @Override + public OpenFlowSwitch getSwitch(Dpid dpid) { + return switchMap.get(dpid); + } + + @Override + public OpenFlowSwitch getMasterSwitch(Dpid dpid) { + return null; + } + + @Override + public OpenFlowSwitch getEqualSwitch(Dpid dpid) { + + return null; + } + + @Override + public void addListener(OpenFlowSwitchListener listener) { + this.listener = listener; + } + + @Override + public void removeListener(OpenFlowSwitchListener listener) { + this.listener = null; + } + + @Override + public void addPacketListener(int priority, PacketListener listener) { + } + + @Override + public void removePacketListener(PacketListener listener) { + } + + @Override + public void addEventListener(OpenFlowEventListener listener) { + } + + @Override + public void removeEventListener(OpenFlowEventListener listener) { + } + + @Override + public void write(Dpid dpid, OFMessage msg) { + } + + @Override + public void processPacket(Dpid dpid, OFMessage msg) { + } + + @Override + public void setRole(Dpid dpid, RoleState role) { + roleMap.put(dpid, role); + } + } + + private static class TestOpenFlowSwitch implements OpenFlowSwitch { + + RoleState state; + List<OFMessage> sent = new ArrayList<OFMessage>(); + OFFactory factory = OFFactoryVer10.INSTANCE; + + @Override + public void sendMsg(OFMessage msg) { + sent.add(msg); + } + + @Override + public void sendMsg(List<OFMessage> msgs) { + } + + @Override + public void handleMessage(OFMessage fromSwitch) { + } + + @Override + public void setRole(RoleState role) { + state = role; + } + + @Override + public RoleState getRole() { + return state; + } + + @Override + public List<OFPortDesc> getPorts() { + return PLIST; + } + + @Override + public OFFactory factory() { + return factory; + } + + @Override + public String getStringId() { + return null; + } + + @Override + public long getId() { + return DPID1.value(); + } + + @Override + public String manufacturerDescription() { + return null; + } + + @Override + public String datapathDescription() { + return null; + } + + @Override + public String hardwareDescription() { + return null; + } + + @Override + public String softwareDescription() { + return null; + } + + @Override + public String serialNumber() { + return null; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public void disconnectSwitch() { + } + + @Override + public Device.Type deviceType() { + return Device.Type.SWITCH; + } + + @Override + public void returnRoleReply(RoleState requested, RoleState response) { + } + + @Override + public String channelId() { + return "1.2.3.4:1"; + } + + } + +} diff --git a/framework/src/onos/providers/openflow/flow/pom.xml b/framework/src/onos/providers/openflow/flow/pom.xml new file mode 100644 index 00000000..24c430e3 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/pom.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-provider-flow</artifactId> + <packaging>bundle</packaging> + + <description>ONOS OpenFlow protocol flow provider</description> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + </dependencies> +</project> diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowEntryBuilder.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowEntryBuilder.java new file mode 100644 index 00000000..f238bdb1 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowEntryBuilder.java @@ -0,0 +1,707 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import static org.onosproject.net.flow.criteria.Criteria.matchLambda; +import static org.onosproject.net.flow.criteria.Criteria.matchOchSignalType; +import static org.onosproject.provider.of.flow.impl.OpenFlowValueMapper.lookupChannelSpacing; +import static org.onosproject.provider.of.flow.impl.OpenFlowValueMapper.lookupGridType; +import static org.onosproject.provider.of.flow.impl.OpenFlowValueMapper.lookupOchSignalType; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.List; + +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.Ip6Address; +import org.onlab.packet.Ip6Prefix; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.TpPort; +import org.onlab.packet.VlanId; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Lambda; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultFlowEntry; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowEntry.FlowEntryState; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.openflow.controller.Dpid; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionCircuit; +import org.projectfloodlight.openflow.protocol.action.OFActionExperimenter; +import org.projectfloodlight.openflow.protocol.action.OFActionGroup; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.action.OFActionPopMpls; +import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetDlSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetField; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanPcp; +import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanVid; +import org.projectfloodlight.openflow.protocol.instruction.OFInstruction; +import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions; +import org.projectfloodlight.openflow.protocol.instruction.OFInstructionGotoTable; +import org.projectfloodlight.openflow.protocol.instruction.OFInstructionWriteActions; +import org.projectfloodlight.openflow.protocol.instruction.OFInstructionWriteMetadata; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.protocol.oxm.OFOxm; +import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigidBasic; +import org.projectfloodlight.openflow.protocol.ver13.OFFactoryVer13; +import org.projectfloodlight.openflow.types.CircuitSignalID; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IPv6Address; +import org.projectfloodlight.openflow.types.Masked; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.U64; +import org.projectfloodlight.openflow.types.U8; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.slf4j.Logger; + +import com.google.common.collect.Lists; + +public class FlowEntryBuilder { + private final Logger log = getLogger(getClass()); + + private final OFFlowStatsEntry stat; + private final OFFlowRemoved removed; + private final OFFlowMod flowMod; + + private final Match match; + + // All actions are contained in an OFInstruction. For OF1.0 + // the instruction type is apply instruction (immediate set in ONOS speak) + private final List<OFInstruction> instructions; + + private final Dpid dpid; + + public enum FlowType { STAT, REMOVED, MOD } + + private final FlowType type; + + public FlowEntryBuilder(Dpid dpid, OFFlowStatsEntry entry) { + this.stat = entry; + this.match = entry.getMatch(); + this.instructions = getInstructions(entry); + this.dpid = dpid; + this.removed = null; + this.flowMod = null; + this.type = FlowType.STAT; + } + + public FlowEntryBuilder(Dpid dpid, OFFlowRemoved removed) { + this.match = removed.getMatch(); + this.removed = removed; + + this.dpid = dpid; + this.instructions = null; + this.stat = null; + this.flowMod = null; + this.type = FlowType.REMOVED; + + } + + public FlowEntryBuilder(Dpid dpid, OFFlowMod fm) { + this.match = fm.getMatch(); + this.dpid = dpid; + this.instructions = getInstructions(fm); + this.type = FlowType.MOD; + this.flowMod = fm; + this.stat = null; + this.removed = null; + } + + public FlowEntry build(FlowEntryState... state) { + FlowRule rule; + switch (this.type) { + case STAT: + rule = DefaultFlowRule.builder() + .forDevice(DeviceId.deviceId(Dpid.uri(dpid))) + .withSelector(buildSelector()) + .withTreatment(buildTreatment()) + .withPriority(stat.getPriority()) + .makeTemporary(stat.getIdleTimeout()) + .withCookie(stat.getCookie().getValue()) + .forTable(stat.getTableId().getValue()) + .build(); + + return new DefaultFlowEntry(rule, FlowEntryState.ADDED, + stat.getDurationSec(), stat.getPacketCount().getValue(), + stat.getByteCount().getValue()); + case REMOVED: + rule = DefaultFlowRule.builder() + .forDevice(DeviceId.deviceId(Dpid.uri(dpid))) + .withSelector(buildSelector()) + .withPriority(removed.getPriority()) + .makeTemporary(removed.getIdleTimeout()) + .withCookie(removed.getCookie().getValue()) + .forTable(removed.getTableId().getValue()) + .build(); + + return new DefaultFlowEntry(rule, FlowEntryState.REMOVED, removed.getDurationSec(), + removed.getPacketCount().getValue(), removed.getByteCount().getValue()); + case MOD: + FlowEntryState flowState = state.length > 0 ? state[0] : FlowEntryState.FAILED; + rule = DefaultFlowRule.builder() + .forDevice(DeviceId.deviceId(Dpid.uri(dpid))) + .withSelector(buildSelector()) + .withTreatment(buildTreatment()) + .withPriority(flowMod.getPriority()) + .makeTemporary(flowMod.getIdleTimeout()) + .withCookie(flowMod.getCookie().getValue()) + .forTable(flowMod.getTableId().getValue()) + .build(); + + return new DefaultFlowEntry(rule, flowState, 0, 0, 0); + default: + log.error("Unknown flow type : {}", this.type); + return null; + } + + } + + private List<OFInstruction> getInstructions(OFFlowMod entry) { + switch (entry.getVersion()) { + case OF_10: + return Lists.newArrayList(OFFactoryVer13.INSTANCE.instructions() + .applyActions( + entry.getActions())); + case OF_11: + case OF_12: + case OF_13: + return entry.getInstructions(); + default: + log.warn("Unknown OF version {}", entry.getVersion()); + } + return Lists.newLinkedList(); + } + + private List<OFInstruction> getInstructions(OFFlowStatsEntry entry) { + switch (entry.getVersion()) { + case OF_10: + return Lists.newArrayList( + OFFactoryVer13.INSTANCE.instructions().applyActions(entry.getActions())); + case OF_11: + case OF_12: + case OF_13: + return entry.getInstructions(); + default: + log.warn("Unknown OF version {}", entry.getVersion()); + } + return Lists.newLinkedList(); + } + + private TrafficTreatment buildTreatment() { + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + // If this is a drop rule + if (instructions.size() == 0) { + builder.drop(); + return builder.build(); + } + for (OFInstruction in : instructions) { + switch (in.getType()) { + case GOTO_TABLE: + builder.transition(((int) ((OFInstructionGotoTable) in) + .getTableId().getValue())); + break; + case WRITE_METADATA: + OFInstructionWriteMetadata m = (OFInstructionWriteMetadata) in; + builder.writeMetadata(m.getMetadata().getValue(), + m.getMetadataMask().getValue()); + break; + case WRITE_ACTIONS: + builder.deferred(); + buildActions(((OFInstructionWriteActions) in).getActions(), + builder); + break; + case APPLY_ACTIONS: + builder.immediate(); + buildActions(((OFInstructionApplyActions) in).getActions(), + builder); + break; + case CLEAR_ACTIONS: + builder.wipeDeferred(); + break; + case EXPERIMENTER: + break; + case METER: + break; + default: + log.warn("Unknown instructions type {}", in.getType()); + } + } + + return builder.build(); + } + + private TrafficTreatment.Builder buildActions(List<OFAction> actions, + TrafficTreatment.Builder builder) { + for (OFAction act : actions) { + switch (act.getType()) { + case OUTPUT: + OFActionOutput out = (OFActionOutput) act; + builder.setOutput( + PortNumber.portNumber(out.getPort().getPortNumber())); + break; + case SET_VLAN_VID: + OFActionSetVlanVid vlan = (OFActionSetVlanVid) act; + builder.setVlanId(VlanId.vlanId(vlan.getVlanVid().getVlan())); + break; + case SET_VLAN_PCP: + OFActionSetVlanPcp pcp = (OFActionSetVlanPcp) act; + builder.setVlanPcp(pcp.getVlanPcp().getValue()); + break; + case SET_DL_DST: + OFActionSetDlDst dldst = (OFActionSetDlDst) act; + builder.setEthDst( + MacAddress.valueOf(dldst.getDlAddr().getLong())); + break; + case SET_DL_SRC: + OFActionSetDlSrc dlsrc = (OFActionSetDlSrc) act; + builder.setEthSrc( + MacAddress.valueOf(dlsrc.getDlAddr().getLong())); + + break; + case SET_NW_DST: + OFActionSetNwDst nwdst = (OFActionSetNwDst) act; + IPv4Address di = nwdst.getNwAddr(); + builder.setIpDst(Ip4Address.valueOf(di.getInt())); + break; + case SET_NW_SRC: + OFActionSetNwSrc nwsrc = (OFActionSetNwSrc) act; + IPv4Address si = nwsrc.getNwAddr(); + builder.setIpSrc(Ip4Address.valueOf(si.getInt())); + break; + case EXPERIMENTER: + OFActionExperimenter exp = (OFActionExperimenter) act; + if (exp.getExperimenter() == 0x80005A06 || + exp.getExperimenter() == 0x748771) { + OFActionCircuit ct = (OFActionCircuit) exp; + short lambda = ((OFOxmOchSigidBasic) ct.getField()).getValue().getChannelNumber(); + builder.add(Instructions.modL0Lambda(Lambda.indexedLambda(lambda))); + } else { + log.warn("Unsupported OFActionExperimenter {}", exp.getExperimenter()); + } + break; + case SET_FIELD: + OFActionSetField setField = (OFActionSetField) act; + handleSetField(builder, setField.getField()); + break; + case POP_MPLS: + OFActionPopMpls popMpls = (OFActionPopMpls) act; + builder.popMpls((short) popMpls.getEthertype().getValue()); + break; + case PUSH_MPLS: + builder.pushMpls(); + break; + case COPY_TTL_IN: + builder.copyTtlIn(); + break; + case COPY_TTL_OUT: + builder.copyTtlOut(); + break; + case DEC_MPLS_TTL: + builder.decMplsTtl(); + break; + case DEC_NW_TTL: + builder.decNwTtl(); + break; + case GROUP: + OFActionGroup group = (OFActionGroup) act; + builder.group(new DefaultGroupId(group.getGroup().getGroupNumber())); + break; + case STRIP_VLAN: + case POP_VLAN: + builder.popVlan(); + break; + case PUSH_VLAN: + builder.pushVlan(); + break; + case SET_TP_DST: + case SET_TP_SRC: + case POP_PBB: + case PUSH_PBB: + case SET_MPLS_LABEL: + case SET_MPLS_TC: + case SET_MPLS_TTL: + case SET_NW_ECN: + case SET_NW_TOS: + case SET_NW_TTL: + case SET_QUEUE: + + case ENQUEUE: + default: + log.warn("Action type {} not yet implemented.", act.getType()); + } + } + return builder; + } + + + private void handleSetField(TrafficTreatment.Builder builder, OFOxm<?> oxm) { + switch (oxm.getMatchField().id) { + case VLAN_PCP: + @SuppressWarnings("unchecked") + OFOxm<VlanPcp> vlanpcp = (OFOxm<VlanPcp>) oxm; + builder.setVlanPcp(vlanpcp.getValue().getValue()); + break; + case VLAN_VID: + @SuppressWarnings("unchecked") + OFOxm<OFVlanVidMatch> vlanvid = (OFOxm<OFVlanVidMatch>) oxm; + builder.setVlanId(VlanId.vlanId(vlanvid.getValue().getVlan())); + break; + case ETH_DST: + @SuppressWarnings("unchecked") + OFOxm<org.projectfloodlight.openflow.types.MacAddress> ethdst = + (OFOxm<org.projectfloodlight.openflow.types.MacAddress>) oxm; + builder.setEthDst(MacAddress.valueOf(ethdst.getValue().getLong())); + break; + case ETH_SRC: + @SuppressWarnings("unchecked") + OFOxm<org.projectfloodlight.openflow.types.MacAddress> ethsrc = + (OFOxm<org.projectfloodlight.openflow.types.MacAddress>) oxm; + builder.setEthSrc(MacAddress.valueOf(ethsrc.getValue().getLong())); + break; + case IPV4_DST: + @SuppressWarnings("unchecked") + OFOxm<IPv4Address> ip4dst = (OFOxm<IPv4Address>) oxm; + builder.setIpDst(Ip4Address.valueOf(ip4dst.getValue().getInt())); + break; + case IPV4_SRC: + @SuppressWarnings("unchecked") + OFOxm<IPv4Address> ip4src = (OFOxm<IPv4Address>) oxm; + builder.setIpSrc(Ip4Address.valueOf(ip4src.getValue().getInt())); + break; + case MPLS_LABEL: + @SuppressWarnings("unchecked") + OFOxm<U32> labelId = (OFOxm<U32>) oxm; + builder.setMpls(MplsLabel.mplsLabel((int) labelId.getValue().getValue())); + break; + case MPLS_BOS: + @SuppressWarnings("unchecked") + OFOxm<U8> mplsBos = (OFOxm<U8>) oxm; + builder.setMplsBos(mplsBos.getValue() == U8.ZERO ? false : true); + break; + case TUNNEL_ID: + @SuppressWarnings("unchecked") + OFOxm<U64> tunnelId = (OFOxm<U64>) oxm; + builder.setTunnelId(tunnelId.getValue().getValue()); + break; + case TCP_DST: + @SuppressWarnings("unchecked") + OFOxm<TransportPort> tcpdst = (OFOxm<TransportPort>) oxm; + builder.setTcpDst(TpPort.tpPort(tcpdst.getValue().getPort())); + break; + case TCP_SRC: + @SuppressWarnings("unchecked") + OFOxm<TransportPort> tcpsrc = (OFOxm<TransportPort>) oxm; + builder.setTcpSrc(TpPort.tpPort(tcpsrc.getValue().getPort())); + break; + case UDP_DST: + @SuppressWarnings("unchecked") + OFOxm<TransportPort> udpdst = (OFOxm<TransportPort>) oxm; + builder.setUdpDst(TpPort.tpPort(udpdst.getValue().getPort())); + break; + case UDP_SRC: + @SuppressWarnings("unchecked") + OFOxm<TransportPort> udpsrc = (OFOxm<TransportPort>) oxm; + builder.setUdpSrc(TpPort.tpPort(udpsrc.getValue().getPort())); + break; + case ARP_OP: + case ARP_SHA: + case ARP_SPA: + case ARP_THA: + case ARP_TPA: + case BSN_EGR_PORT_GROUP_ID: + case BSN_GLOBAL_VRF_ALLOWED: + case BSN_IN_PORTS_128: + case BSN_L3_DST_CLASS_ID: + case BSN_L3_INTERFACE_CLASS_ID: + case BSN_L3_SRC_CLASS_ID: + case BSN_LAG_ID: + case BSN_TCP_FLAGS: + case BSN_UDF0: + case BSN_UDF1: + case BSN_UDF2: + case BSN_UDF3: + case BSN_UDF4: + case BSN_UDF5: + case BSN_UDF6: + case BSN_UDF7: + case BSN_VLAN_XLATE_PORT_GROUP_ID: + case BSN_VRF: + case ETH_TYPE: + case ICMPV4_CODE: + case ICMPV4_TYPE: + case ICMPV6_CODE: + case ICMPV6_TYPE: + case IN_PHY_PORT: + case IN_PORT: + case IPV6_DST: + case IPV6_FLABEL: + case IPV6_ND_SLL: + case IPV6_ND_TARGET: + case IPV6_ND_TLL: + case IPV6_SRC: + case IP_DSCP: + case IP_ECN: + case IP_PROTO: + case METADATA: + case MPLS_TC: + case OCH_SIGID: + case OCH_SIGID_BASIC: + case OCH_SIGTYPE: + case OCH_SIGTYPE_BASIC: + case SCTP_DST: + case SCTP_SRC: + default: + log.warn("Set field type {} not yet implemented.", oxm.getMatchField().id); + break; + } + } + + // CHECKSTYLE IGNORE MethodLength FOR NEXT 1 LINES + private TrafficSelector buildSelector() { + MacAddress mac; + Ip4Prefix ip4Prefix; + Ip6Address ip6Address; + Ip6Prefix ip6Prefix; + + TrafficSelector.Builder builder = DefaultTrafficSelector.builder(); + for (MatchField<?> field : match.getMatchFields()) { + switch (field.id) { + case IN_PORT: + builder.matchInPort(PortNumber + .portNumber(match.get(MatchField.IN_PORT).getPortNumber())); + break; + case IN_PHY_PORT: + builder.matchInPhyPort(PortNumber + .portNumber(match.get(MatchField.IN_PHY_PORT).getPortNumber())); + break; + case METADATA: + long metadata = + match.get(MatchField.METADATA).getValue().getValue(); + builder.matchMetadata(metadata); + break; + case ETH_DST: + mac = MacAddress.valueOf(match.get(MatchField.ETH_DST).getLong()); + builder.matchEthDst(mac); + break; + case ETH_SRC: + mac = MacAddress.valueOf(match.get(MatchField.ETH_SRC).getLong()); + builder.matchEthSrc(mac); + break; + case ETH_TYPE: + int ethType = match.get(MatchField.ETH_TYPE).getValue(); + if (ethType == EthType.VLAN_FRAME.getValue()) { + builder.matchVlanId(VlanId.ANY); + } else { + builder.matchEthType((short) ethType); + } + break; + case VLAN_VID: + VlanId vlanId = null; + if (match.isPartiallyMasked(MatchField.VLAN_VID)) { + Masked<OFVlanVidMatch> masked = match.getMasked(MatchField.VLAN_VID); + if (masked.getValue().equals(OFVlanVidMatch.PRESENT) + && masked.getMask().equals(OFVlanVidMatch.PRESENT)) { + vlanId = VlanId.ANY; + } + } else { + if (!match.get(MatchField.VLAN_VID).isPresentBitSet()) { + vlanId = VlanId.NONE; + } else { + vlanId = VlanId.vlanId(match.get(MatchField.VLAN_VID).getVlan()); + } + } + if (vlanId != null) { + builder.matchVlanId(vlanId); + } + break; + case VLAN_PCP: + byte vlanPcp = match.get(MatchField.VLAN_PCP).getValue(); + builder.matchVlanPcp(vlanPcp); + break; + case IP_DSCP: + byte ipDscp = match.get(MatchField.IP_DSCP).getDscpValue(); + builder.matchIPDscp(ipDscp); + break; + case IP_ECN: + byte ipEcn = match.get(MatchField.IP_ECN).getEcnValue(); + builder.matchIPEcn(ipEcn); + break; + case IP_PROTO: + short proto = match.get(MatchField.IP_PROTO).getIpProtocolNumber(); + builder.matchIPProtocol((byte) proto); + break; + case IPV4_SRC: + if (match.isPartiallyMasked(MatchField.IPV4_SRC)) { + Masked<IPv4Address> maskedIp = match.getMasked(MatchField.IPV4_SRC); + ip4Prefix = Ip4Prefix.valueOf( + maskedIp.getValue().getInt(), + maskedIp.getMask().asCidrMaskLength()); + } else { + ip4Prefix = Ip4Prefix.valueOf( + match.get(MatchField.IPV4_SRC).getInt(), + Ip4Prefix.MAX_MASK_LENGTH); + } + builder.matchIPSrc(ip4Prefix); + break; + case IPV4_DST: + if (match.isPartiallyMasked(MatchField.IPV4_DST)) { + Masked<IPv4Address> maskedIp = match.getMasked(MatchField.IPV4_DST); + ip4Prefix = Ip4Prefix.valueOf( + maskedIp.getValue().getInt(), + maskedIp.getMask().asCidrMaskLength()); + } else { + ip4Prefix = Ip4Prefix.valueOf( + match.get(MatchField.IPV4_DST).getInt(), + Ip4Prefix.MAX_MASK_LENGTH); + } + builder.matchIPDst(ip4Prefix); + break; + case TCP_SRC: + builder.matchTcpSrc(TpPort.tpPort(match.get(MatchField.TCP_SRC).getPort())); + break; + case TCP_DST: + builder.matchTcpDst(TpPort.tpPort(match.get(MatchField.TCP_DST).getPort())); + break; + case UDP_SRC: + builder.matchUdpSrc(TpPort.tpPort(match.get(MatchField.UDP_SRC).getPort())); + break; + case UDP_DST: + builder.matchUdpDst(TpPort.tpPort(match.get(MatchField.UDP_DST).getPort())); + break; + case MPLS_LABEL: + builder.matchMplsLabel(MplsLabel.mplsLabel((int) match.get(MatchField.MPLS_LABEL) + .getValue())); + break; + case MPLS_BOS: + builder.matchMplsBos(match.get(MatchField.MPLS_BOS).getValue()); + break; + case SCTP_SRC: + builder.matchSctpSrc(TpPort.tpPort(match.get(MatchField.SCTP_SRC).getPort())); + break; + case SCTP_DST: + builder.matchSctpDst(TpPort.tpPort(match.get(MatchField.SCTP_DST).getPort())); + break; + case ICMPV4_TYPE: + byte icmpType = (byte) match.get(MatchField.ICMPV4_TYPE).getType(); + builder.matchIcmpType(icmpType); + break; + case ICMPV4_CODE: + byte icmpCode = (byte) match.get(MatchField.ICMPV4_CODE).getCode(); + builder.matchIcmpCode(icmpCode); + break; + case IPV6_SRC: + if (match.isPartiallyMasked(MatchField.IPV6_SRC)) { + Masked<IPv6Address> maskedIp = match.getMasked(MatchField.IPV6_SRC); + ip6Prefix = Ip6Prefix.valueOf( + maskedIp.getValue().getBytes(), + maskedIp.getMask().asCidrMaskLength()); + } else { + ip6Prefix = Ip6Prefix.valueOf( + match.get(MatchField.IPV6_SRC).getBytes(), + Ip6Prefix.MAX_MASK_LENGTH); + } + builder.matchIPv6Src(ip6Prefix); + break; + case IPV6_DST: + if (match.isPartiallyMasked(MatchField.IPV6_DST)) { + Masked<IPv6Address> maskedIp = match.getMasked(MatchField.IPV6_DST); + ip6Prefix = Ip6Prefix.valueOf( + maskedIp.getValue().getBytes(), + maskedIp.getMask().asCidrMaskLength()); + } else { + ip6Prefix = Ip6Prefix.valueOf( + match.get(MatchField.IPV6_DST).getBytes(), + Ip6Prefix.MAX_MASK_LENGTH); + } + builder.matchIPv6Dst(ip6Prefix); + break; + case IPV6_FLABEL: + int flowLabel = + match.get(MatchField.IPV6_FLABEL).getIPv6FlowLabelValue(); + builder.matchIPv6FlowLabel(flowLabel); + break; + case ICMPV6_TYPE: + byte icmpv6type = (byte) match.get(MatchField.ICMPV6_TYPE).getValue(); + builder.matchIcmpv6Type(icmpv6type); + break; + case ICMPV6_CODE: + byte icmpv6code = (byte) match.get(MatchField.ICMPV6_CODE).getValue(); + builder.matchIcmpv6Code(icmpv6code); + break; + case IPV6_ND_TARGET: + ip6Address = + Ip6Address.valueOf(match.get(MatchField.IPV6_ND_TARGET).getBytes()); + builder.matchIPv6NDTargetAddress(ip6Address); + break; + case IPV6_ND_SLL: + mac = MacAddress.valueOf(match.get(MatchField.IPV6_ND_SLL).getLong()); + builder.matchIPv6NDSourceLinkLayerAddress(mac); + break; + case IPV6_ND_TLL: + mac = MacAddress.valueOf(match.get(MatchField.IPV6_ND_TLL).getLong()); + builder.matchIPv6NDTargetLinkLayerAddress(mac); + break; + case IPV6_EXTHDR: + builder.matchIPv6ExthdrFlags((short) match.get(MatchField.IPV6_EXTHDR) + .getValue()); + break; + case OCH_SIGID: + CircuitSignalID sigId = match.get(MatchField.OCH_SIGID); + builder.add(matchLambda(Lambda.ochSignal( + lookupGridType(sigId.getGridType()), lookupChannelSpacing(sigId.getChannelSpacing()), + sigId.getChannelNumber(), sigId.getSpectralWidth()) + )); + break; + case OCH_SIGTYPE: + U8 sigType = match.get(MatchField.OCH_SIGTYPE); + builder.add(matchOchSignalType(lookupOchSignalType((byte) sigType.getValue()))); + break; + case TUNNEL_ID: + long tunnelId = match.get(MatchField.TUNNEL_ID).getValue(); + builder.matchTunnelId(tunnelId); + break; + case ARP_OP: + case ARP_SHA: + case ARP_SPA: + case ARP_THA: + case ARP_TPA: + case MPLS_TC: + default: + log.warn("Match type {} not yet implemented.", field.id); + } + } + return builder.build(); + } +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilder.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilder.java new file mode 100644 index 00000000..e050524a --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilder.java @@ -0,0 +1,444 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Optional; + +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip4Prefix; +import org.onlab.packet.Ip6Address; +import org.onlab.packet.Ip6Prefix; +import org.onlab.packet.VlanId; +import org.onosproject.net.OchSignal; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthCriterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.IPDscpCriterion; +import org.onosproject.net.flow.criteria.IPEcnCriterion; +import org.onosproject.net.flow.criteria.IPProtocolCriterion; +import org.onosproject.net.flow.criteria.IPv6ExthdrFlagsCriterion; +import org.onosproject.net.flow.criteria.IPv6FlowLabelCriterion; +import org.onosproject.net.flow.criteria.IPv6NDLinkLayerAddressCriterion; +import org.onosproject.net.flow.criteria.IPv6NDTargetAddressCriterion; +import org.onosproject.net.flow.criteria.IcmpCodeCriterion; +import org.onosproject.net.flow.criteria.IcmpTypeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6CodeCriterion; +import org.onosproject.net.flow.criteria.Icmpv6TypeCriterion; +import org.onosproject.net.flow.criteria.MetadataCriterion; +import org.onosproject.net.flow.criteria.MplsBosCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; +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.criteria.SctpPortCriterion; +import org.onosproject.net.flow.criteria.TcpPortCriterion; +import org.onosproject.net.flow.criteria.TunnelIdCriterion; +import org.onosproject.net.flow.criteria.UdpPortCriterion; +import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.criteria.VlanPcpCriterion; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowDelete; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.CircuitSignalID; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.ICMPv4Code; +import org.projectfloodlight.openflow.types.ICMPv4Type; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IPv6Address; +import org.projectfloodlight.openflow.types.IPv6FlowLabel; +import org.projectfloodlight.openflow.types.IpDscp; +import org.projectfloodlight.openflow.types.IpEcn; +import org.projectfloodlight.openflow.types.IpProtocol; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.Masked; +import org.projectfloodlight.openflow.types.OFBooleanValue; +import org.projectfloodlight.openflow.types.OFMetadata; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U16; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.U64; +import org.projectfloodlight.openflow.types.U8; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.projectfloodlight.openflow.types.VlanVid; +import org.slf4j.Logger; + +/** + * Builder for OpenFlow flow mods based on FlowRules. + */ +public abstract class FlowModBuilder { + + private final Logger log = getLogger(getClass()); + + private final OFFactory factory; + private final FlowRule flowRule; + private final TrafficSelector selector; + protected final Long xid; + + /** + * Creates a new flow mod builder. + * + * @param flowRule the flow rule to transform into a flow mod + * @param factory the OpenFlow factory to use to build the flow mod + * @param xid the transaction ID + * @return the new flow mod builder + */ + public static FlowModBuilder builder(FlowRule flowRule, + OFFactory factory, + Optional<Long> xid) { + switch (factory.getVersion()) { + case OF_10: + return new FlowModBuilderVer10(flowRule, factory, xid); + case OF_13: + return new FlowModBuilderVer13(flowRule, factory, xid); + default: + throw new UnsupportedOperationException( + "No flow mod builder for protocol version " + factory.getVersion()); + } + } + + /** + * Constructs a flow mod builder. + * + * @param flowRule the flow rule to transform into a flow mod + * @param factory the OpenFlow factory to use to build the flow mod + * @param xid the transaction ID + */ + protected FlowModBuilder(FlowRule flowRule, OFFactory factory, Optional<Long> xid) { + this.factory = factory; + this.flowRule = flowRule; + this.selector = flowRule.selector(); + this.xid = xid.orElse(0L); + + } + + /** + * Builds an ADD flow mod. + * + * @return the flow mod + */ + public abstract OFFlowAdd buildFlowAdd(); + + /** + * Builds a MODIFY flow mod. + * + * @return the flow mod + */ + public abstract OFFlowMod buildFlowMod(); + + /** + * Builds a DELETE flow mod. + * + * @return the flow mod + */ + public abstract OFFlowDelete buildFlowDel(); + + /** + * Builds the match for the flow mod. + * + * @return the match + */ + // CHECKSTYLE IGNORE MethodLength FOR NEXT 300 LINES + protected Match buildMatch() { + Match.Builder mBuilder = factory.buildMatch(); + Ip6Address ip6Address; + Ip4Prefix ip4Prefix; + Ip6Prefix ip6Prefix; + EthCriterion ethCriterion; + IPCriterion ipCriterion; + TcpPortCriterion tcpPortCriterion; + UdpPortCriterion udpPortCriterion; + SctpPortCriterion sctpPortCriterion; + IPv6NDLinkLayerAddressCriterion llAddressCriterion; + + for (Criterion c : selector.criteria()) { + switch (c.type()) { + case IN_PORT: + PortCriterion inPort = (PortCriterion) c; + mBuilder.setExact(MatchField.IN_PORT, + OFPort.of((int) inPort.port().toLong())); + break; + case IN_PHY_PORT: + PortCriterion inPhyPort = (PortCriterion) c; + mBuilder.setExact(MatchField.IN_PORT, + OFPort.of((int) inPhyPort.port().toLong())); + break; + case METADATA: + MetadataCriterion metadata = (MetadataCriterion) c; + mBuilder.setExact(MatchField.METADATA, + OFMetadata.ofRaw(metadata.metadata())); + break; + case ETH_DST: + ethCriterion = (EthCriterion) c; + mBuilder.setExact(MatchField.ETH_DST, + MacAddress.of(ethCriterion.mac().toLong())); + break; + case ETH_SRC: + ethCriterion = (EthCriterion) c; + mBuilder.setExact(MatchField.ETH_SRC, + MacAddress.of(ethCriterion.mac().toLong())); + break; + case ETH_TYPE: + EthTypeCriterion ethType = (EthTypeCriterion) c; + mBuilder.setExact(MatchField.ETH_TYPE, EthType.of(ethType.ethType().toShort())); + break; + case VLAN_VID: + VlanIdCriterion vid = (VlanIdCriterion) c; + + if (vid.vlanId().equals(VlanId.ANY)) { + mBuilder.setMasked(MatchField.VLAN_VID, OFVlanVidMatch.PRESENT, + OFVlanVidMatch.PRESENT); + } else if (vid.vlanId().equals(VlanId.NONE)) { + mBuilder.setExact(MatchField.VLAN_VID, OFVlanVidMatch.NONE); + } else { + mBuilder.setExact(MatchField.VLAN_VID, + OFVlanVidMatch.ofVlanVid(VlanVid.ofVlan(vid.vlanId().toShort()))); + } + break; + case VLAN_PCP: + VlanPcpCriterion vpcp = (VlanPcpCriterion) c; + mBuilder.setExact(MatchField.VLAN_PCP, VlanPcp.of(vpcp.priority())); + break; + case IP_DSCP: + IPDscpCriterion ipDscpCriterion = (IPDscpCriterion) c; + mBuilder.setExact(MatchField.IP_DSCP, + IpDscp.of(ipDscpCriterion.ipDscp())); + break; + case IP_ECN: + IPEcnCriterion ipEcnCriterion = (IPEcnCriterion) c; + mBuilder.setExact(MatchField.IP_ECN, + IpEcn.of(ipEcnCriterion.ipEcn())); + break; + case IP_PROTO: + IPProtocolCriterion p = (IPProtocolCriterion) c; + mBuilder.setExact(MatchField.IP_PROTO, IpProtocol.of(p.protocol())); + break; + case IPV4_SRC: + ipCriterion = (IPCriterion) c; + ip4Prefix = ipCriterion.ip().getIp4Prefix(); + if (ip4Prefix.prefixLength() != Ip4Prefix.MAX_MASK_LENGTH) { + Ip4Address maskAddr = + Ip4Address.makeMaskPrefix(ip4Prefix.prefixLength()); + Masked<IPv4Address> maskedIp = + Masked.of(IPv4Address.of(ip4Prefix.address().toInt()), + IPv4Address.of(maskAddr.toInt())); + mBuilder.setMasked(MatchField.IPV4_SRC, maskedIp); + } else { + mBuilder.setExact(MatchField.IPV4_SRC, + IPv4Address.of(ip4Prefix.address().toInt())); + } + break; + case IPV4_DST: + ipCriterion = (IPCriterion) c; + ip4Prefix = ipCriterion.ip().getIp4Prefix(); + if (ip4Prefix.prefixLength() != Ip4Prefix.MAX_MASK_LENGTH) { + Ip4Address maskAddr = + Ip4Address.makeMaskPrefix(ip4Prefix.prefixLength()); + Masked<IPv4Address> maskedIp = + Masked.of(IPv4Address.of(ip4Prefix.address().toInt()), + IPv4Address.of(maskAddr.toInt())); + mBuilder.setMasked(MatchField.IPV4_DST, maskedIp); + } else { + mBuilder.setExact(MatchField.IPV4_DST, + IPv4Address.of(ip4Prefix.address().toInt())); + } + break; + case TCP_SRC: + tcpPortCriterion = (TcpPortCriterion) c; + mBuilder.setExact(MatchField.TCP_SRC, + TransportPort.of(tcpPortCriterion.tcpPort().toInt())); + break; + case TCP_DST: + tcpPortCriterion = (TcpPortCriterion) c; + mBuilder.setExact(MatchField.TCP_DST, + TransportPort.of(tcpPortCriterion.tcpPort().toInt())); + break; + case UDP_SRC: + udpPortCriterion = (UdpPortCriterion) c; + mBuilder.setExact(MatchField.UDP_SRC, + TransportPort.of(udpPortCriterion.udpPort().toInt())); + break; + case UDP_DST: + udpPortCriterion = (UdpPortCriterion) c; + mBuilder.setExact(MatchField.UDP_DST, + TransportPort.of(udpPortCriterion.udpPort().toInt())); + break; + case SCTP_SRC: + sctpPortCriterion = (SctpPortCriterion) c; + mBuilder.setExact(MatchField.SCTP_SRC, + TransportPort.of(sctpPortCriterion.sctpPort().toInt())); + break; + case SCTP_DST: + sctpPortCriterion = (SctpPortCriterion) c; + mBuilder.setExact(MatchField.SCTP_DST, + TransportPort.of(sctpPortCriterion.sctpPort().toInt())); + break; + case ICMPV4_TYPE: + IcmpTypeCriterion icmpType = (IcmpTypeCriterion) c; + mBuilder.setExact(MatchField.ICMPV4_TYPE, + ICMPv4Type.of(icmpType.icmpType())); + break; + case ICMPV4_CODE: + IcmpCodeCriterion icmpCode = (IcmpCodeCriterion) c; + mBuilder.setExact(MatchField.ICMPV4_CODE, + ICMPv4Code.of(icmpCode.icmpCode())); + break; + case IPV6_SRC: + ipCriterion = (IPCriterion) c; + ip6Prefix = ipCriterion.ip().getIp6Prefix(); + if (ip6Prefix.prefixLength() != Ip6Prefix.MAX_MASK_LENGTH) { + Ip6Address maskAddr = + Ip6Address.makeMaskPrefix(ip6Prefix.prefixLength()); + Masked<IPv6Address> maskedIp = + Masked.of(IPv6Address.of(ip6Prefix.address().toString()), + IPv6Address.of(maskAddr.toString())); + mBuilder.setMasked(MatchField.IPV6_SRC, maskedIp); + } else { + mBuilder.setExact(MatchField.IPV6_SRC, + IPv6Address.of(ip6Prefix.address().toString())); + } + break; + case IPV6_DST: + ipCriterion = (IPCriterion) c; + ip6Prefix = ipCriterion.ip().getIp6Prefix(); + if (ip6Prefix.prefixLength() != Ip6Prefix.MAX_MASK_LENGTH) { + Ip6Address maskAddr = + Ip6Address.makeMaskPrefix(ip6Prefix.prefixLength()); + Masked<IPv6Address> maskedIp = + Masked.of(IPv6Address.of(ip6Prefix.address().toString()), + IPv6Address.of(maskAddr.toString())); + mBuilder.setMasked(MatchField.IPV6_DST, maskedIp); + } else { + mBuilder.setExact(MatchField.IPV6_DST, + IPv6Address.of(ip6Prefix.address().toString())); + } + break; + case IPV6_FLABEL: + IPv6FlowLabelCriterion flowLabelCriterion = + (IPv6FlowLabelCriterion) c; + mBuilder.setExact(MatchField.IPV6_FLABEL, + IPv6FlowLabel.of(flowLabelCriterion.flowLabel())); + break; + case ICMPV6_TYPE: + Icmpv6TypeCriterion icmpv6Type = (Icmpv6TypeCriterion) c; + mBuilder.setExact(MatchField.ICMPV6_TYPE, + U8.of(icmpv6Type.icmpv6Type())); + break; + case ICMPV6_CODE: + Icmpv6CodeCriterion icmpv6Code = (Icmpv6CodeCriterion) c; + mBuilder.setExact(MatchField.ICMPV6_CODE, + U8.of(icmpv6Code.icmpv6Code())); + break; + case IPV6_ND_TARGET: + IPv6NDTargetAddressCriterion targetAddressCriterion = + (IPv6NDTargetAddressCriterion) c; + ip6Address = targetAddressCriterion.targetAddress(); + mBuilder.setExact(MatchField.IPV6_ND_TARGET, + IPv6Address.of(ip6Address.toOctets())); + break; + case IPV6_ND_SLL: + llAddressCriterion = + (IPv6NDLinkLayerAddressCriterion) c; + mBuilder.setExact(MatchField.IPV6_ND_SLL, + MacAddress.of(llAddressCriterion.mac().toLong())); + break; + case IPV6_ND_TLL: + llAddressCriterion = + (IPv6NDLinkLayerAddressCriterion) c; + mBuilder.setExact(MatchField.IPV6_ND_TLL, + MacAddress.of(llAddressCriterion.mac().toLong())); + break; + case MPLS_LABEL: + MplsCriterion mp = (MplsCriterion) c; + mBuilder.setExact(MatchField.MPLS_LABEL, U32.of(mp.label().toInt())); + break; + case IPV6_EXTHDR: + IPv6ExthdrFlagsCriterion exthdrFlagsCriterion = + (IPv6ExthdrFlagsCriterion) c; + mBuilder.setExact(MatchField.IPV6_EXTHDR, + U16.of(exthdrFlagsCriterion.exthdrFlags())); + break; + case OCH_SIGID: + try { + OchSignalCriterion ochSignalCriterion = (OchSignalCriterion) c; + OchSignal signal = ochSignalCriterion.lambda(); + byte gridType = OpenFlowValueMapper.lookupGridType(signal.gridType()); + byte channelSpacing = OpenFlowValueMapper.lookupChannelSpacing(signal.channelSpacing()); + mBuilder.setExact(MatchField.OCH_SIGID, + new CircuitSignalID(gridType, channelSpacing, + (short) signal.spacingMultiplier(), (short) signal.slotGranularity())); + } catch (NoMappingFoundException e) { + log.warn(e.getMessage()); + } + break; + case OCH_SIGTYPE: + OchSignalTypeCriterion sc = (OchSignalTypeCriterion) c; + byte signalType = OpenFlowValueMapper.lookupOchSignalType(sc.signalType()); + mBuilder.setExact(MatchField.OCH_SIGTYPE, U8.of(signalType)); + break; + case TUNNEL_ID: + TunnelIdCriterion tunnelId = (TunnelIdCriterion) c; + mBuilder.setExact(MatchField.TUNNEL_ID, + U64.of(tunnelId.tunnelId())); + break; + case MPLS_BOS: + MplsBosCriterion mplsBos = (MplsBosCriterion) c; + mBuilder.setExact(MatchField.MPLS_BOS, + mplsBos.mplsBos() ? OFBooleanValue.TRUE + : OFBooleanValue.FALSE); + break; + case ARP_OP: + case ARP_SHA: + case ARP_SPA: + case ARP_THA: + case ARP_TPA: + case MPLS_TC: + case PBB_ISID: + default: + log.warn("Match type {} not yet implemented.", c.type()); + } + } + return mBuilder.build(); + } + + /** + * Returns the flow rule for this builder. + * + * @return the flow rule + */ + protected FlowRule flowRule() { + return flowRule; + } + + /** + * Returns the factory used for building OpenFlow constructs. + * + * @return the factory + */ + protected OFFactory factory() { + return factory; + } + +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilderVer10.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilderVer10.java new file mode 100644 index 00000000..c9de4500 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilderVer10.java @@ -0,0 +1,230 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import org.onlab.packet.Ip4Address; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPInstruction; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowDelete; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowModFlags; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.projectfloodlight.openflow.types.VlanVid; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +/** + * Flow mod builder for OpenFlow 1.0. + */ +public class FlowModBuilderVer10 extends FlowModBuilder { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private static final int OFPCML_NO_BUFFER = 0xffff; + + private final TrafficTreatment treatment; + + /** + * Constructor for a flow mod builder for OpenFlow 1.0. + * + * @param flowRule the flow rule to transform into a flow mod + * @param factory the OpenFlow factory to use to build the flow mod + * @param xid the transaction ID + */ + protected FlowModBuilderVer10(FlowRule flowRule, + OFFactory factory, Optional<Long> xid) { + super(flowRule, factory, xid); + + this.treatment = flowRule.treatment(); + } + + @Override + public OFFlowAdd buildFlowAdd() { + Match match = buildMatch(); + List<OFAction> actions = buildActions(); + + long cookie = flowRule().id().value(); + + + OFFlowAdd fm = factory().buildFlowAdd() + .setXid(xid) + .setCookie(U64.of(cookie)) + .setBufferId(OFBufferId.NO_BUFFER) + .setActions(actions) + .setMatch(match) + .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) + .setPriority(flowRule().priority()) + .build(); + + return fm; + } + + @Override + public OFFlowMod buildFlowMod() { + Match match = buildMatch(); + List<OFAction> actions = buildActions(); + + long cookie = flowRule().id().value(); + + OFFlowMod fm = factory().buildFlowModify() + .setXid(xid) + .setCookie(U64.of(cookie)) + .setBufferId(OFBufferId.NO_BUFFER) + .setActions(actions) + .setMatch(match) + .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) + .setPriority(flowRule().priority()) + .build(); + + return fm; + } + + @Override + public OFFlowDelete buildFlowDel() { + Match match = buildMatch(); + + long cookie = flowRule().id().value(); + + OFFlowDelete fm = factory().buildFlowDelete() + .setXid(xid) + .setCookie(U64.of(cookie)) + .setBufferId(OFBufferId.NO_BUFFER) + .setMatch(match) + .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) + .setPriority(flowRule().priority()) + .build(); + + return fm; + } + + private List<OFAction> buildActions() { + List<OFAction> acts = new LinkedList<>(); + OFAction act; + if (treatment == null) { + return acts; + } + for (Instruction i : treatment.immediate()) { + switch (i.type()) { + case DROP: + log.warn("Saw drop action; assigning drop action"); + return Collections.emptyList(); + case L2MODIFICATION: + act = buildL2Modification(i); + if (act != null) { + acts.add(buildL2Modification(i)); + } + break; + case L3MODIFICATION: + act = buildL3Modification(i); + if (act != null) { + acts.add(buildL3Modification(i)); + } + break; + case OUTPUT: + OutputInstruction out = (OutputInstruction) i; + OFActionOutput.Builder action = factory().actions().buildOutput() + .setPort(OFPort.of((int) out.port().toLong())); + if (out.port().equals(PortNumber.CONTROLLER)) { + action.setMaxLen(OFPCML_NO_BUFFER); + } + acts.add(action.build()); + break; + case L0MODIFICATION: + case GROUP: + case TABLE: + case METADATA: + log.warn("Instruction type {} not supported with protocol version {}", + i.type(), factory().getVersion()); + break; + default: + log.warn("Instruction type {} not yet implemented.", i.type()); + } + } + + return acts; + } + + private OFAction buildL3Modification(Instruction i) { + L3ModificationInstruction l3m = (L3ModificationInstruction) i; + ModIPInstruction ip; + Ip4Address ip4; + switch (l3m.subtype()) { + case IPV4_SRC: + ip = (ModIPInstruction) i; + ip4 = ip.ip().getIp4Address(); + return factory().actions().setNwSrc(IPv4Address.of(ip4.toInt())); + case IPV4_DST: + ip = (ModIPInstruction) i; + ip4 = ip.ip().getIp4Address(); + return factory().actions().setNwDst(IPv4Address.of(ip4.toInt())); + default: + log.warn("Unimplemented action type {}.", l3m.subtype()); + break; + } + return null; + } + + private OFAction buildL2Modification(Instruction i) { + L2ModificationInstruction l2m = (L2ModificationInstruction) i; + ModEtherInstruction eth; + switch (l2m.subtype()) { + case ETH_DST: + eth = (ModEtherInstruction) l2m; + return factory().actions().setDlDst(MacAddress.of(eth.mac().toLong())); + case ETH_SRC: + eth = (ModEtherInstruction) l2m; + return factory().actions().setDlSrc(MacAddress.of(eth.mac().toLong())); + case VLAN_ID: + ModVlanIdInstruction vlanId = (ModVlanIdInstruction) l2m; + return factory().actions().setVlanVid(VlanVid.ofVlan(vlanId.vlanId().toShort())); + case VLAN_PCP: + ModVlanPcpInstruction vlanPcp = (ModVlanPcpInstruction) l2m; + return factory().actions().setVlanPcp(VlanPcp.of(vlanPcp.vlanPcp())); + case VLAN_POP: + return factory().actions().stripVlan(); + case VLAN_PUSH: + return null; + default: + log.warn("Unimplemented action type {}.", l2m.subtype()); + break; + } + return null; + } + +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilderVer13.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilderVer13.java new file mode 100644 index 00000000..8918d337 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowModBuilderVer13.java @@ -0,0 +1,458 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import com.google.common.collect.Lists; +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip6Address; +import org.onosproject.net.OchSignal; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.Instructions.GroupInstruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L0ModificationInstruction.ModLambdaInstruction; +import org.onosproject.net.flow.instructions.L0ModificationInstruction.ModOchSignalInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsBosInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanPcpInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.PushHeaderInstructions; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModTunnelIdInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction.ModIPv6FlowLabelInstruction; +import org.onosproject.net.flow.instructions.L4ModificationInstruction; +import org.onosproject.net.flow.instructions.L4ModificationInstruction.ModTransportPortInstruction; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowDelete; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowModFlags; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionGroup; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.instruction.OFInstruction; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.oxm.OFOxm; +import org.projectfloodlight.openflow.types.CircuitSignalID; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IPv6Address; +import org.projectfloodlight.openflow.types.IPv6FlowLabel; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBooleanValue; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFGroup; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TableId; +import org.projectfloodlight.openflow.types.TransportPort; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.U64; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +/** + * Flow mod builder for OpenFlow 1.3+. + */ +public class FlowModBuilderVer13 extends FlowModBuilder { + + private final Logger log = LoggerFactory.getLogger(getClass()); + private static final int OFPCML_NO_BUFFER = 0xffff; + + private final TrafficTreatment treatment; + + /** + * Constructor for a flow mod builder for OpenFlow 1.3. + * + * @param flowRule the flow rule to transform into a flow mod + * @param factory the OpenFlow factory to use to build the flow mod + * @param xid the transaction ID + */ + protected FlowModBuilderVer13(FlowRule flowRule, OFFactory factory, Optional<Long> xid) { + super(flowRule, factory, xid); + + this.treatment = flowRule.treatment(); + } + + @Override + public OFFlowAdd buildFlowAdd() { + Match match = buildMatch(); + List<OFAction> deferredActions = buildActions(treatment.deferred()); + List<OFAction> immediateActions = buildActions(treatment.immediate()); + List<OFInstruction> instructions = Lists.newLinkedList(); + + + if (treatment.clearedDeferred()) { + instructions.add(factory().instructions().clearActions()); + } + if (immediateActions.size() > 0) { + instructions.add(factory().instructions().applyActions(immediateActions)); + } + if (deferredActions.size() > 0) { + instructions.add(factory().instructions().writeActions(deferredActions)); + } + if (treatment.tableTransition() != null) { + instructions.add(buildTableGoto(treatment.tableTransition())); + } + if (treatment.writeMetadata() != null) { + instructions.add(buildMetadata(treatment.writeMetadata())); + } + + long cookie = flowRule().id().value(); + + OFFlowAdd fm = factory().buildFlowAdd() + .setXid(xid) + .setCookie(U64.of(cookie)) + .setBufferId(OFBufferId.NO_BUFFER) + .setInstructions(instructions) + .setMatch(match) + .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) + .setPriority(flowRule().priority()) + .setTableId(TableId.of(flowRule().tableId())) + .build(); + + return fm; + } + + @Override + public OFFlowMod buildFlowMod() { + Match match = buildMatch(); + List<OFAction> deferredActions = buildActions(treatment.deferred()); + List<OFAction> immediateActions = buildActions(treatment.immediate()); + List<OFInstruction> instructions = Lists.newLinkedList(); + + + if (immediateActions.size() > 0) { + instructions.add(factory().instructions().applyActions(immediateActions)); + } + if (treatment.clearedDeferred()) { + instructions.add(factory().instructions().clearActions()); + } + if (deferredActions.size() > 0) { + instructions.add(factory().instructions().writeActions(deferredActions)); + } + if (treatment.tableTransition() != null) { + instructions.add(buildTableGoto(treatment.tableTransition())); + } + if (treatment.writeMetadata() != null) { + instructions.add(buildMetadata(treatment.writeMetadata())); + } + if (treatment.metered() != null) { + instructions.add(buildMeter(treatment.metered())); + } + + long cookie = flowRule().id().value(); + + OFFlowMod fm = factory().buildFlowModify() + .setXid(xid) + .setCookie(U64.of(cookie)) + .setBufferId(OFBufferId.NO_BUFFER) + .setInstructions(instructions) + .setMatch(match) + .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) + .setPriority(flowRule().priority()) + .setTableId(TableId.of(flowRule().tableId())) + .build(); + + return fm; + } + + @Override + public OFFlowDelete buildFlowDel() { + Match match = buildMatch(); + + long cookie = flowRule().id().value(); + + OFFlowDelete fm = factory().buildFlowDelete() + .setXid(xid) + .setCookie(U64.of(cookie)) + .setBufferId(OFBufferId.NO_BUFFER) + .setMatch(match) + .setFlags(Collections.singleton(OFFlowModFlags.SEND_FLOW_REM)) + .setPriority(flowRule().priority()) + .setTableId(TableId.of(flowRule().tableId())) + .build(); + + return fm; + } + + private List<OFAction> buildActions(List<Instruction> treatments) { + if (treatment == null) { + return Collections.emptyList(); + } + + boolean tableFound = false; + List<OFAction> actions = new LinkedList<>(); + for (Instruction i : treatments) { + switch (i.type()) { + case DROP: + return Collections.emptyList(); + case L0MODIFICATION: + actions.add(buildL0Modification(i)); + break; + case L2MODIFICATION: + actions.add(buildL2Modification(i)); + break; + case L3MODIFICATION: + actions.add(buildL3Modification(i)); + break; + case L4MODIFICATION: + actions.add(buildL4Modification(i)); + break; + case OUTPUT: + OutputInstruction out = (OutputInstruction) i; + OFActionOutput.Builder action = factory().actions().buildOutput() + .setPort(OFPort.of((int) out.port().toLong())); + if (out.port().equals(PortNumber.CONTROLLER)) { + action.setMaxLen(OFPCML_NO_BUFFER); + } + actions.add(action.build()); + break; + case GROUP: + GroupInstruction group = (GroupInstruction) i; + OFActionGroup.Builder groupBuilder = factory().actions().buildGroup() + .setGroup(OFGroup.of(group.groupId().id())); + actions.add(groupBuilder.build()); + break; + case TABLE: + //FIXME: should not occur here. + tableFound = true; + break; + default: + log.warn("Instruction type {} not yet implemented.", i.type()); + } + } + if (tableFound && actions.isEmpty()) { + // handles the case where there are no actions, but there is + // a goto instruction for the next table + return Collections.emptyList(); + } + return actions; + } + + private OFInstruction buildTableGoto(Instructions.TableTypeTransition i) { + OFInstruction instruction = factory().instructions().gotoTable( + TableId.of(i.tableId())); + return instruction; + } + + private OFInstruction buildMetadata(Instructions.MetadataInstruction m) { + OFInstruction instruction = factory().instructions().writeMetadata( + U64.of(m.metadata()), U64.of(m.metadataMask())); + return instruction; + } + + private OFInstruction buildMeter(Instructions.MeterInstruction metered) { + return factory().instructions().meter(metered.meterId().id()); + } + + + private OFAction buildL0Modification(Instruction i) { + L0ModificationInstruction l0m = (L0ModificationInstruction) i; + switch (l0m.subtype()) { + case LAMBDA: + return buildModLambdaInstruction((ModLambdaInstruction) i); + case OCH: + try { + return buildModOchSignalInstruction((ModOchSignalInstruction) i); + } catch (NoMappingFoundException e) { + log.warn(e.getMessage()); + break; + } + default: + log.warn("Unimplemented action type {}.", l0m.subtype()); + break; + } + return null; + } + + private OFAction buildModLambdaInstruction(ModLambdaInstruction instruction) { + return factory().actions().circuit(factory().oxms().ochSigidBasic( + new CircuitSignalID((byte) 1, (byte) 2, instruction.lambda(), (short) 1))); + } + + private OFAction buildModOchSignalInstruction(ModOchSignalInstruction instruction) { + OchSignal signal = instruction.lambda(); + byte gridType = OpenFlowValueMapper.lookupGridType(signal.gridType()); + byte channelSpacing = OpenFlowValueMapper.lookupChannelSpacing(signal.channelSpacing()); + + return factory().actions().circuit(factory().oxms().ochSigidBasic( + new CircuitSignalID(gridType, channelSpacing, + (short) signal.spacingMultiplier(), (short) signal.slotGranularity()) + )); + } + + private OFAction buildL2Modification(Instruction i) { + L2ModificationInstruction l2m = (L2ModificationInstruction) i; + ModEtherInstruction eth; + OFOxm<?> oxm = null; + switch (l2m.subtype()) { + case ETH_DST: + eth = (ModEtherInstruction) l2m; + oxm = factory().oxms().ethDst(MacAddress.of(eth.mac().toLong())); + break; + case ETH_SRC: + eth = (ModEtherInstruction) l2m; + oxm = factory().oxms().ethSrc(MacAddress.of(eth.mac().toLong())); + break; + case VLAN_ID: + ModVlanIdInstruction vlanId = (ModVlanIdInstruction) l2m; + oxm = factory().oxms().vlanVid(OFVlanVidMatch.ofVlan(vlanId.vlanId().toShort())); + break; + case VLAN_PCP: + ModVlanPcpInstruction vlanPcp = (ModVlanPcpInstruction) l2m; + oxm = factory().oxms().vlanPcp(VlanPcp.of(vlanPcp.vlanPcp())); + break; + case MPLS_PUSH: + PushHeaderInstructions pushHeaderInstructions = + (PushHeaderInstructions) l2m; + return factory().actions().pushMpls(EthType.of(pushHeaderInstructions + .ethernetType().toShort())); + case MPLS_POP: + PushHeaderInstructions popHeaderInstructions = + (PushHeaderInstructions) l2m; + return factory().actions().popMpls(EthType.of(popHeaderInstructions + .ethernetType().toShort())); + case MPLS_LABEL: + ModMplsLabelInstruction mplsLabel = + (ModMplsLabelInstruction) l2m; + oxm = factory().oxms().mplsLabel(U32.of(mplsLabel.mplsLabel().toInt())); + break; + case MPLS_BOS: + ModMplsBosInstruction mplsBos = (ModMplsBosInstruction) l2m; + oxm = factory().oxms() + .mplsBos(mplsBos.mplsBos() ? OFBooleanValue.TRUE + : OFBooleanValue.FALSE); + break; + case DEC_MPLS_TTL: + return factory().actions().decMplsTtl(); + case VLAN_POP: + return factory().actions().popVlan(); + case VLAN_PUSH: + PushHeaderInstructions pushVlanInstruction = (PushHeaderInstructions) l2m; + return factory().actions().pushVlan( + EthType.of(pushVlanInstruction.ethernetType().toShort())); + case TUNNEL_ID: + ModTunnelIdInstruction tunnelId = (ModTunnelIdInstruction) l2m; + oxm = factory().oxms().tunnelId(U64.of(tunnelId.tunnelId())); + break; + default: + log.warn("Unimplemented action type {}.", l2m.subtype()); + break; + } + + if (oxm != null) { + return factory().actions().buildSetField().setField(oxm).build(); + } + return null; + } + + private OFAction buildL3Modification(Instruction i) { + L3ModificationInstruction l3m = (L3ModificationInstruction) i; + ModIPInstruction ip; + Ip4Address ip4; + Ip6Address ip6; + OFOxm<?> oxm = null; + switch (l3m.subtype()) { + case IPV4_SRC: + ip = (ModIPInstruction) i; + ip4 = ip.ip().getIp4Address(); + oxm = factory().oxms().ipv4Src(IPv4Address.of(ip4.toInt())); + break; + case IPV4_DST: + ip = (ModIPInstruction) i; + ip4 = ip.ip().getIp4Address(); + oxm = factory().oxms().ipv4Dst(IPv4Address.of(ip4.toInt())); + break; + case IPV6_SRC: + ip = (ModIPInstruction) i; + ip6 = ip.ip().getIp6Address(); + oxm = factory().oxms().ipv6Src(IPv6Address.of(ip6.toOctets())); + break; + case IPV6_DST: + ip = (ModIPInstruction) i; + ip6 = ip.ip().getIp6Address(); + oxm = factory().oxms().ipv6Dst(IPv6Address.of(ip6.toOctets())); + break; + case IPV6_FLABEL: + ModIPv6FlowLabelInstruction flowLabelInstruction = + (ModIPv6FlowLabelInstruction) i; + int flowLabel = flowLabelInstruction.flowLabel(); + oxm = factory().oxms().ipv6Flabel(IPv6FlowLabel.of(flowLabel)); + break; + case DEC_TTL: + return factory().actions().decNwTtl(); + case TTL_IN: + return factory().actions().copyTtlIn(); + case TTL_OUT: + return factory().actions().copyTtlOut(); + default: + log.warn("Unimplemented action type {}.", l3m.subtype()); + break; + } + + if (oxm != null) { + return factory().actions().buildSetField().setField(oxm).build(); + } + return null; + } + + private OFAction buildL4Modification(Instruction i) { + L4ModificationInstruction l4m = (L4ModificationInstruction) i; + ModTransportPortInstruction tp; + OFOxm<?> oxm = null; + switch (l4m.subtype()) { + case TCP_SRC: + tp = (ModTransportPortInstruction) l4m; + oxm = factory().oxms().tcpSrc(TransportPort.of(tp.port().toInt())); + break; + case TCP_DST: + tp = (ModTransportPortInstruction) l4m; + oxm = factory().oxms().tcpDst(TransportPort.of(tp.port().toInt())); + break; + case UDP_SRC: + tp = (ModTransportPortInstruction) l4m; + oxm = factory().oxms().udpSrc(TransportPort.of(tp.port().toInt())); + break; + case UDP_DST: + tp = (ModTransportPortInstruction) l4m; + oxm = factory().oxms().udpDst(TransportPort.of(tp.port().toInt())); + break; + default: + log.warn("Unimplemented action type {}.", l4m.subtype()); + break; + } + + if (oxm != null) { + return factory().actions().buildSetField().setField(oxm).build(); + } + return null; + } + +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowStatsCollector.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowStatsCollector.java new file mode 100644 index 00000000..c4c81afa --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/FlowStatsCollector.java @@ -0,0 +1,100 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import org.onlab.util.SharedExecutors; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TableId; +import org.slf4j.Logger; + +import java.util.Timer; +import java.util.TimerTask; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Collects flow statistics for the specified switch. + */ +class FlowStatsCollector { + + private final Logger log = getLogger(getClass()); + + public static final int SECONDS = 1000; + + private final OpenFlowSwitch sw; + private Timer timer; + private TimerTask task; + + private int pollInterval; + + /** + * Creates a new collector for the given switch and poll frequency. + * + * @param timer timer to use for scheduling + * @param sw switch to pull + * @param pollInterval poll frequency in seconds + */ + FlowStatsCollector(Timer timer, OpenFlowSwitch sw, int pollInterval) { + this.timer = timer; + this.sw = sw; + this.pollInterval = pollInterval; + } + + /** + * Adjusts poll frequency. + * + * @param pollInterval poll frequency in seconds + */ + synchronized void adjustPollInterval(int pollInterval) { + this.pollInterval = pollInterval; + task.cancel(); + task = new InternalTimerTask(); + timer.scheduleAtFixedRate(task, pollInterval * SECONDS, pollInterval * 1000); + } + + private class InternalTimerTask extends TimerTask { + @Override + public void run() { + if (sw.getRole() == RoleState.MASTER) { + log.trace("Collecting stats for {}", sw.getStringId()); + OFFlowStatsRequest request = sw.factory().buildFlowStatsRequest() + .setMatch(sw.factory().matchWildcardAll()) + .setTableId(TableId.ALL) + .setOutPort(OFPort.NO_MASK) + .build(); + sw.sendMsg(request); + } + } + } + + public synchronized void start() { + // Initially start polling quickly. Then drop down to configured value + log.debug("Starting Stats collection thread for {}", sw.getStringId()); + task = new InternalTimerTask(); + SharedExecutors.getTimer().scheduleAtFixedRate(task, 1 * SECONDS, + pollInterval * SECONDS); + } + + public synchronized void stop() { + log.debug("Stopping Stats collection thread for {}", sw.getStringId()); + task.cancel(); + task = null; + } + +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/NoMappingFoundException.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/NoMappingFoundException.java new file mode 100644 index 00000000..898b286d --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/NoMappingFoundException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +/** + * Thrown to indicate that no mapping for the input value is found. + */ +public class NoMappingFoundException extends RuntimeException { + /** + * Creates an instance with the specified values. + * + * @param input input value of mapping causing this exception + * @param output the desired class which the input value is mapped to + */ + public NoMappingFoundException(Object input, Class<?> output) { + super(String.format("No mapping found for %s when converting to %s", input, output.getName())); + } +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java new file mode 100644 index 00000000..de079e03 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowRuleProvider.java @@ -0,0 +1,453 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.ApplicationId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.flow.CompletedBatchOperation; +import org.onosproject.net.flow.FlowEntry; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleBatchEntry; +import org.onosproject.net.flow.FlowRuleBatchOperation; +import org.onosproject.net.flow.FlowRuleExtPayLoad; +import org.onosproject.net.flow.FlowRuleProvider; +import org.onosproject.net.flow.FlowRuleProviderRegistry; +import org.onosproject.net.flow.FlowRuleProviderService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.net.statistic.DefaultLoad; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.RoleState; +import org.onosproject.openflow.controller.ThirdPartyMessage; +import org.osgi.service.component.ComponentContext; +import org.projectfloodlight.openflow.protocol.OFBadRequestCode; +import org.projectfloodlight.openflow.protocol.OFBarrierRequest; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFErrorType; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowRemoved; +import org.projectfloodlight.openflow.protocol.OFFlowStatsReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg; +import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg; +import org.slf4j.Logger; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.Timer; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onlab.util.Tools.get; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to detect network end-station + * hosts. + */ +@Component(immediate = true) +public class OpenFlowRuleProvider extends AbstractProvider + implements FlowRuleProvider { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenFlowController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + private static final int DEFAULT_POLL_FREQUENCY = 10; + @Property(name = "flowPollFrequency", intValue = DEFAULT_POLL_FREQUENCY, + label = "Frequency (in seconds) for polling flow statistics") + private int flowPollFrequency = DEFAULT_POLL_FREQUENCY; + + private FlowRuleProviderService providerService; + + private final InternalFlowProvider listener = new InternalFlowProvider(); + + private Cache<Long, InternalCacheEntry> pendingBatches; + + private final Timer timer = new Timer("onos-openflow-collector"); + private final Map<Dpid, FlowStatsCollector> collectors = Maps.newHashMap(); + + /** + * Creates an OpenFlow host provider. + */ + public OpenFlowRuleProvider() { + super(new ProviderId("of", "org.onosproject.provider.openflow")); + } + + @Activate + public void activate(ComponentContext context) { + cfgService.registerProperties(getClass()); + providerService = providerRegistry.register(this); + controller.addListener(listener); + controller.addEventListener(listener); + + pendingBatches = createBatchCache(); + createCollectors(); + + log.info("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + cfgService.unregisterProperties(getClass(), false); + stopCollectors(); + providerRegistry.unregister(this); + providerService = null; + + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + int newFlowPollFrequency; + try { + String s = get(properties, "flowPollFrequency"); + newFlowPollFrequency = isNullOrEmpty(s) ? flowPollFrequency : Integer.parseInt(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + newFlowPollFrequency = flowPollFrequency; + } + + if (newFlowPollFrequency != flowPollFrequency) { + flowPollFrequency = newFlowPollFrequency; + adjustRate(); + } + + log.info("Settings: flowPollFrequency={}", flowPollFrequency); + } + + private Cache<Long, InternalCacheEntry> createBatchCache() { + return CacheBuilder.newBuilder() + .expireAfterWrite(10, TimeUnit.SECONDS) + .removalListener((RemovalNotification<Long, InternalCacheEntry> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + providerService.batchOperationCompleted(notification.getKey(), + notification.getValue().failedCompletion()); + } + }).build(); + } + + private void createCollectors() { + controller.getSwitches().forEach(this::createCollector); + } + + private void createCollector(OpenFlowSwitch sw) { + FlowStatsCollector fsc = new FlowStatsCollector(timer, sw, flowPollFrequency); + fsc.start(); + collectors.put(new Dpid(sw.getId()), fsc); + } + + private void stopCollectors() { + collectors.values().forEach(FlowStatsCollector::stop); + collectors.clear(); + } + + private void adjustRate() { + DefaultLoad.setPollInterval(flowPollFrequency); + collectors.values().forEach(fsc -> fsc.adjustPollInterval(flowPollFrequency)); + } + + @Override + public void applyFlowRule(FlowRule... flowRules) { + for (FlowRule flowRule : flowRules) { + applyRule(flowRule); + } + } + + private void applyRule(FlowRule flowRule) { + OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId() + .uri())); + FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad(); + if (hasPayload(flowRuleExtPayLoad)) { + OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad()); + sw.sendMsg(msg); + return; + } + sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(), + Optional.empty()).buildFlowAdd()); + } + + @Override + public void removeFlowRule(FlowRule... flowRules) { + for (FlowRule flowRule : flowRules) { + removeRule(flowRule); + } + } + + private void removeRule(FlowRule flowRule) { + OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(flowRule.deviceId() + .uri())); + FlowRuleExtPayLoad flowRuleExtPayLoad = flowRule.payLoad(); + if (hasPayload(flowRuleExtPayLoad)) { + OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad()); + sw.sendMsg(msg); + return; + } + sw.sendMsg(FlowModBuilder.builder(flowRule, sw.factory(), + Optional.empty()).buildFlowDel()); + } + + @Override + public void removeRulesById(ApplicationId id, FlowRule... flowRules) { + // TODO: optimize using the ApplicationId + removeFlowRule(flowRules); + } + + @Override + public void executeBatch(FlowRuleBatchOperation batch) { + + pendingBatches.put(batch.id(), new InternalCacheEntry(batch)); + + OpenFlowSwitch sw = controller.getSwitch(Dpid.dpid(batch.deviceId() + .uri())); + OFFlowMod mod; + for (FlowRuleBatchEntry fbe : batch.getOperations()) { + // flow is the third party privacy flow + + FlowRuleExtPayLoad flowRuleExtPayLoad = fbe.target().payLoad(); + if (hasPayload(flowRuleExtPayLoad)) { + OFMessage msg = new ThirdPartyMessage(flowRuleExtPayLoad.payLoad()); + sw.sendMsg(msg); + continue; + } + FlowModBuilder builder = FlowModBuilder.builder(fbe.target(), sw + .factory(), Optional.of(batch.id())); + switch (fbe.operator()) { + case ADD: + mod = builder.buildFlowAdd(); + break; + case REMOVE: + mod = builder.buildFlowDel(); + break; + case MODIFY: + mod = builder.buildFlowMod(); + break; + default: + log.error("Unsupported batch operation {}; skipping flowmod {}", + fbe.operator(), fbe); + continue; + } + sw.sendMsg(mod); + } + OFBarrierRequest.Builder builder = sw.factory().buildBarrierRequest() + .setXid(batch.id()); + sw.sendMsg(builder.build()); + } + + private boolean hasPayload(FlowRuleExtPayLoad flowRuleExtPayLoad) { + return flowRuleExtPayLoad != null && + flowRuleExtPayLoad.payLoad() != null && + flowRuleExtPayLoad.payLoad().length > 0; + } + + private class InternalFlowProvider + implements OpenFlowSwitchListener, OpenFlowEventListener { + + @Override + public void switchAdded(Dpid dpid) { + createCollector(controller.getSwitch(dpid)); + } + + @Override + public void switchRemoved(Dpid dpid) { + FlowStatsCollector collector = collectors.remove(dpid); + if (collector != null) { + collector.stop(); + } + } + + @Override + public void switchChanged(Dpid dpid) { + } + + @Override + public void portChanged(Dpid dpid, OFPortStatus status) { + // TODO: Decide whether to evict flows internal store. + } + + @Override + public void handleMessage(Dpid dpid, OFMessage msg) { + OpenFlowSwitch sw = controller.getSwitch(dpid); + switch (msg.getType()) { + case FLOW_REMOVED: + OFFlowRemoved removed = (OFFlowRemoved) msg; + + FlowEntry fr = new FlowEntryBuilder(dpid, removed).build(); + providerService.flowRemoved(fr); + break; + case STATS_REPLY: + if (((OFStatsReply) msg).getStatsType() == OFStatsType.FLOW) { + pushFlowMetrics(dpid, (OFFlowStatsReply) msg); + } + break; + case BARRIER_REPLY: + try { + InternalCacheEntry entry = pendingBatches.getIfPresent(msg.getXid()); + if (entry != null) { + providerService + .batchOperationCompleted(msg.getXid(), + entry.completed()); + } else { + log.warn("Received unknown Barrier Reply: {}", + msg.getXid()); + } + } finally { + pendingBatches.invalidate(msg.getXid()); + } + break; + case ERROR: + // TODO: This needs to get suppressed in a better way. + if (msg instanceof OFBadRequestErrorMsg && + ((OFBadRequestErrorMsg) msg).getCode() == OFBadRequestCode.BAD_TYPE) { + log.debug("Received error message {} from {}", msg, dpid); + } else { + log.warn("Received error message {} from {}", msg, dpid); + } + + OFErrorMsg error = (OFErrorMsg) msg; + if (error.getErrType() == OFErrorType.FLOW_MOD_FAILED) { + OFFlowModFailedErrorMsg fmFailed = (OFFlowModFailedErrorMsg) error; + if (fmFailed.getData().getParsedMessage().isPresent()) { + OFMessage m = fmFailed.getData().getParsedMessage().get(); + OFFlowMod fm = (OFFlowMod) m; + InternalCacheEntry entry = + pendingBatches.getIfPresent(msg.getXid()); + if (entry != null) { + entry.appendFailure(new FlowEntryBuilder(dpid, fm).build()); + } else { + log.error("No matching batch for this error: {}", error); + } + } else { + // FIXME: Potentially add flowtracking to avoid this message. + log.error("Flow installation failed but switch didn't" + + " tell us which one."); + } + } + break; + default: + log.debug("Unhandled message type: {}", msg.getType()); + } + + } + + @Override + public void receivedRoleReply(Dpid dpid, RoleState requested, + RoleState response) { + // Do nothing here for now. + } + + private void pushFlowMetrics(Dpid dpid, OFFlowStatsReply replies) { + + DeviceId did = DeviceId.deviceId(Dpid.uri(dpid)); + OpenFlowSwitch sw = controller.getSwitch(dpid); + + List<FlowEntry> flowEntries = replies.getEntries().stream() + .map(entry -> new FlowEntryBuilder(dpid, entry).build()) + .collect(Collectors.toList()); + + providerService.pushFlowMetrics(did, flowEntries); + } + } + + /** + * The internal cache entry holding the original request as well as + * accumulating the any failures along the way. + * <p/> + * If this entry is evicted from the cache then the entire operation is + * considered failed. Otherwise, only the failures reported by the device + * will be propagated up. + */ + private class InternalCacheEntry { + + private final FlowRuleBatchOperation operation; + private final Set<FlowRule> failures = Sets.newConcurrentHashSet(); + + public InternalCacheEntry(FlowRuleBatchOperation operation) { + this.operation = operation; + } + + /** + * Appends a failed rule to the set of failed items. + * + * @param rule the failed rule + */ + public void appendFailure(FlowRule rule) { + failures.add(rule); + } + + /** + * Fails the entire batch and returns the failed operation. + * + * @return the failed operation + */ + public CompletedBatchOperation failedCompletion() { + Set<FlowRule> fails = operation.getOperations().stream() + .map(op -> op.target()).collect(Collectors.toSet()); + return new CompletedBatchOperation(false, + Collections + .unmodifiableSet(fails), + operation.deviceId()); + } + + /** + * Returns the completed operation and whether the batch suceeded. + * + * @return the completed operation + */ + public CompletedBatchOperation completed() { + return new CompletedBatchOperation( + failures.isEmpty(), + Collections + .unmodifiableSet(failures), + operation.deviceId()); + } + } + +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowValueMapper.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowValueMapper.java new file mode 100644 index 00000000..2f0831c6 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/OpenFlowValueMapper.java @@ -0,0 +1,152 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.flow.impl; + +import com.google.common.collect.BiMap; +import com.google.common.collect.EnumHashBiMap; +import org.onosproject.net.ChannelSpacing; +import org.onosproject.net.GridType; +import org.onosproject.net.OchSignalType; + +/** + * Collection of helper methods to convert protocol agnostic models to values used in OpenFlow spec. + */ +final class OpenFlowValueMapper { + + // prohibit instantiation + private OpenFlowValueMapper() {} + + private static final BiMap<GridType, Byte> GRID_TYPES = EnumHashBiMap.create(GridType.class); + static { + // See ONF "Optical Transport Protocol Extensions Version 1.0" for the following values + GRID_TYPES.put(GridType.DWDM, (byte) 1); // OFPGRIDT_DWDM of enum ofp_grid_type + GRID_TYPES.put(GridType.CWDM, (byte) 2); // OFPGRIDT_CWDM of enum ofp_grid_type + GRID_TYPES.put(GridType.FLEX, (byte) 3); // OFPGRIDT_FLEX of enum ofp_grid_type + } + + private static final BiMap<ChannelSpacing, Byte> CHANNEL_SPACING = EnumHashBiMap.create(ChannelSpacing.class); + static { + // See ONF "Optical Transport Protocol Extensions Version 1.0" for the following values + CHANNEL_SPACING.put(ChannelSpacing.CHL_100GHZ, (byte) 1); // OFPCS_100GHZ of enum ofp_chl_spacing + CHANNEL_SPACING.put(ChannelSpacing.CHL_50GHZ, (byte) 2); // OFPCS_50GHZ of enum ofp_chl_spacing + CHANNEL_SPACING.put(ChannelSpacing.CHL_25GHZ, (byte) 3); // OFPCS_25GHZ of enum ofp_chl_spacing + CHANNEL_SPACING.put(ChannelSpacing.CHL_12P5GHZ, (byte) 4); // OFPCS_12P5GHZ of enum ofp_chl_spacing + CHANNEL_SPACING.put(ChannelSpacing.CHL_6P25GHZ, (byte) 5); // OFPCS_6P25GHZ of enum ofp_chl_spacing + } + + private static final BiMap<OchSignalType, Byte> OCH_SIGNAL_TYPES = EnumHashBiMap.create(OchSignalType.class); + static { + // See ONF "Optical Transport Protocol Extensions Version 1.0" for the following values + OCH_SIGNAL_TYPES.put(OchSignalType.FIXED_GRID, (byte) 1); // OFPOCHT_FIX_GRID of enum ofp_och_signal_type + OCH_SIGNAL_TYPES.put(OchSignalType.FLEX_GRID, (byte) 2); // OFPOCHT_FLEX_GRID of enum ofp_och_signal_type + } + + /** + * Looks up the specified input value to the corresponding value with the specified map. + * + * @param map bidirectional mapping + * @param input input value + * @param cls class of output value + * @param <I> type of input value + * @param <O> type of output value + * @return the corresponding value stored in the specified map + * @throws NoMappingFoundException if no corresponding value is found + */ + private static <I, O> O lookup(BiMap<I, O> map, I input, Class<O> cls) { + if (!map.containsKey(input)) { + throw new NoMappingFoundException(input, cls); + } + + return map.get(input); + } + + /** + * Looks up the corresponding byte value defined in + * ONF "Optical Transport Protocol Extensions Version 1.0" + * from the specified {@link GridType} instance. + * + * @param type grid type + * @return the byte value corresponding to the specified grid type + * @throws NoMappingFoundException if the specified grid type is not found + */ + static byte lookupGridType(GridType type) { + return lookup(GRID_TYPES, type, Byte.class); + } + + /** + * Looks up the corresponding {@link GridType} instance + * from the specified byte value for grid type + * defined in ONF "Optical Transport Protocol Extensions Version 1.0". + * + * @param type byte value as grid type defined the spec + * @return the corresponding GridType instance + */ + static GridType lookupGridType(byte type) { + return lookup(GRID_TYPES.inverse(), type, GridType.class); + } + + /** + * Looks up the corresponding byte value for channel spacing defined in + * ONF "Optical Transport Protocol Extensions Version 1.0" + * from the specified {@link ChannelSpacing} instance. + * + * @param spacing channel spacing + * @return byte value corresponding to the specified channel spacing + * @throws NoMappingFoundException if the specified channel spacing is not found + */ + static byte lookupChannelSpacing(ChannelSpacing spacing) { + return lookup(CHANNEL_SPACING, spacing, Byte.class); + } + + /** + * Looks up the corresponding {@link ChannelSpacing} instance + * from the specified byte value for channel spacing + * defined in ONF "Optical Transport Protocol Extensions Version 1.0". + * + * @param spacing byte value as channel spacing defined the spec + * @return the corresponding ChannelSpacing instance + * @throws NoMappingFoundException if the specified channel spacing is not found + */ + static ChannelSpacing lookupChannelSpacing(byte spacing) { + return lookup(CHANNEL_SPACING.inverse(), spacing, ChannelSpacing.class); + } + + /** + * Looks up the corresponding byte value for Och signal type defined in + * ONF "Optical Transport Protocol Extensions Version 1.0" + * from the specified {@link OchSignalType} instance. + * + * @param signalType optical signal type + * @return byte value corresponding to the specified OCh signal type + * @throws NoMappingFoundException if the specified Och signal type is not found + */ + static byte lookupOchSignalType(OchSignalType signalType) { + return lookup(OCH_SIGNAL_TYPES, signalType, Byte.class); + } + + /** + * Looks up the the corresponding {@link OchSignalType} instance + * from the specified byte value for Och signal type defined in + * ONF "Optical Transport Protocol Extensions Version 1.0". + * + * @param signalType byte value as Och singal type defined the spec + * @return the corresponding OchSignalType instance + * @throws NoMappingFoundException if the specified Och signal type is not found + */ + static OchSignalType lookupOchSignalType(byte signalType) { + return lookup(OCH_SIGNAL_TYPES.inverse(), signalType, OchSignalType.class); + } +} diff --git a/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/package-info.java b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/package-info.java new file mode 100644 index 00000000..2acc1510 --- /dev/null +++ b/framework/src/onos/providers/openflow/flow/src/main/java/org/onosproject/provider/of/flow/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses OpenFlow controller as a means of ending and receiving flow information. + */ +package org.onosproject.provider.of.flow.impl; diff --git a/framework/src/onos/providers/openflow/group/pom.xml b/framework/src/onos/providers/openflow/group/pom.xml new file mode 100644 index 00000000..97ac5ffe --- /dev/null +++ b/framework/src/onos/providers/openflow/group/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-provider-group</artifactId> + <packaging>bundle</packaging> + + <description>ONOS OpenFlow protocol group provider</description> + +</project>
\ No newline at end of file diff --git a/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupBucketEntryBuilder.java b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupBucketEntryBuilder.java new file mode 100644 index 00000000..b9de7c0f --- /dev/null +++ b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupBucketEntryBuilder.java @@ -0,0 +1,343 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.group.impl; + +import com.google.common.collect.Lists; + +import org.onlab.packet.Ip4Address; +import org.onlab.packet.MacAddress; +import org.onlab.packet.MplsLabel; +import org.onlab.packet.VlanId; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.core.GroupId; +import org.onosproject.net.Lambda; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.projectfloodlight.openflow.protocol.OFBucket; +import org.projectfloodlight.openflow.protocol.OFGroupType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionCircuit; +import org.projectfloodlight.openflow.protocol.action.OFActionCopyTtlIn; +import org.projectfloodlight.openflow.protocol.action.OFActionCopyTtlOut; +import org.projectfloodlight.openflow.protocol.action.OFActionDecMplsTtl; +import org.projectfloodlight.openflow.protocol.action.OFActionDecNwTtl; +import org.projectfloodlight.openflow.protocol.action.OFActionExperimenter; +import org.projectfloodlight.openflow.protocol.action.OFActionGroup; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.action.OFActionPopMpls; +import org.projectfloodlight.openflow.protocol.action.OFActionPushMpls; +import org.projectfloodlight.openflow.protocol.action.OFActionSetDlDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetDlSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetField; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwDst; +import org.projectfloodlight.openflow.protocol.action.OFActionSetNwSrc; +import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanPcp; +import org.projectfloodlight.openflow.protocol.action.OFActionSetVlanVid; +import org.projectfloodlight.openflow.protocol.oxm.OFOxm; +import org.projectfloodlight.openflow.protocol.oxm.OFOxmOchSigidBasic; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.U8; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.slf4j.Logger; + +import java.util.List; + +import static org.slf4j.LoggerFactory.getLogger; + +/* + * Builder for GroupBucketEntry. + */ +public class GroupBucketEntryBuilder { + + private List<OFBucket> ofBuckets; + private OFGroupType type; + + private final Logger log = getLogger(getClass()); + + /** + * Creates a builder. + * + * @param ofBuckets list of OFBucket + * @param type Group type + */ + public GroupBucketEntryBuilder(List<OFBucket> ofBuckets, OFGroupType type) { + this.ofBuckets = ofBuckets; + this.type = type; + } + + /** + * Builds a GroupBuckets. + * + * @return GroupBuckets object, a list of GroupBuckets + */ + public GroupBuckets build() { + List<GroupBucket> bucketList = Lists.newArrayList(); + + for (OFBucket bucket: ofBuckets) { + TrafficTreatment treatment = buildTreatment(bucket.getActions()); + // TODO: Use GroupBucketEntry + GroupBucket groupBucket = null; + switch (type) { + case INDIRECT: + groupBucket = + DefaultGroupBucket.createIndirectGroupBucket(treatment); + break; + case SELECT: + groupBucket = + DefaultGroupBucket.createSelectGroupBucket(treatment); + break; + case FF: + PortNumber port = + PortNumber.portNumber(bucket.getWatchPort().getPortNumber()); + GroupId groupId = + new DefaultGroupId(bucket.getWatchGroup().getGroupNumber()); + groupBucket = + DefaultGroupBucket.createFailoverGroupBucket(treatment, + port, groupId); + break; + default: + log.error("Unsupported Group type : {}", type); + } + if (groupBucket != null) { + bucketList.add(groupBucket); + } + } + return new GroupBuckets(bucketList); + } + + + private TrafficTreatment buildTreatment(List<OFAction> actions) { + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + // If this is a drop rule + if (actions.size() == 0) { + builder.drop(); + return builder.build(); + } + for (OFAction act : actions) { + switch (act.getType()) { + case OUTPUT: + OFActionOutput out = (OFActionOutput) act; + builder.setOutput( + PortNumber.portNumber(out.getPort().getPortNumber())); + break; + case SET_VLAN_VID: + OFActionSetVlanVid vlan = (OFActionSetVlanVid) act; + builder.setVlanId(VlanId.vlanId(vlan.getVlanVid().getVlan())); + break; + case SET_VLAN_PCP: + OFActionSetVlanPcp pcp = (OFActionSetVlanPcp) act; + builder.setVlanPcp(pcp.getVlanPcp().getValue()); + break; + case POP_VLAN: + builder.popVlan(); + break; + case PUSH_VLAN: + builder.pushVlan(); + break; + case SET_DL_DST: + OFActionSetDlDst dldst = (OFActionSetDlDst) act; + builder.setEthDst( + MacAddress.valueOf(dldst.getDlAddr().getLong())); + break; + case SET_DL_SRC: + OFActionSetDlSrc dlsrc = (OFActionSetDlSrc) act; + builder.setEthSrc( + MacAddress.valueOf(dlsrc.getDlAddr().getLong())); + + break; + case SET_NW_DST: + OFActionSetNwDst nwdst = (OFActionSetNwDst) act; + IPv4Address di = nwdst.getNwAddr(); + builder.setIpDst(Ip4Address.valueOf(di.getInt())); + break; + case SET_NW_SRC: + OFActionSetNwSrc nwsrc = (OFActionSetNwSrc) act; + IPv4Address si = nwsrc.getNwAddr(); + builder.setIpSrc(Ip4Address.valueOf(si.getInt())); + break; + case EXPERIMENTER: + OFActionExperimenter exp = (OFActionExperimenter) act; + if (exp.getExperimenter() == 0x80005A06 || + exp.getExperimenter() == 0x748771) { + OFActionCircuit ct = (OFActionCircuit) exp; + short lambda = ((OFOxmOchSigidBasic) ct.getField()).getValue().getChannelNumber(); + builder.add(Instructions.modL0Lambda(Lambda.indexedLambda(lambda))); + } else { + log.warn("Unsupported OFActionExperimenter {}", exp.getExperimenter()); + } + break; + case SET_FIELD: + OFActionSetField setField = (OFActionSetField) act; + handleSetField(builder, setField.getField()); + break; + case POP_MPLS: + OFActionPopMpls popMpls = (OFActionPopMpls) act; + builder.popMpls((short) popMpls.getEthertype().getValue()); + break; + case PUSH_MPLS: + OFActionPushMpls pushMpls = (OFActionPushMpls) act; + builder.pushMpls(); + break; + case COPY_TTL_IN: + OFActionCopyTtlIn copyTtlIn = (OFActionCopyTtlIn) act; + builder.copyTtlIn(); + break; + case COPY_TTL_OUT: + OFActionCopyTtlOut copyTtlOut = (OFActionCopyTtlOut) act; + builder.copyTtlOut(); + break; + case DEC_MPLS_TTL: + OFActionDecMplsTtl decMplsTtl = (OFActionDecMplsTtl) act; + builder.decMplsTtl(); + break; + case DEC_NW_TTL: + OFActionDecNwTtl decNwTtl = (OFActionDecNwTtl) act; + builder.decNwTtl(); + break; + case GROUP: + OFActionGroup grp = (OFActionGroup) act; + builder.group(new DefaultGroupId(grp.getGroup().getGroupNumber())); + break; + case SET_TP_DST: + case SET_TP_SRC: + case POP_PBB: + case PUSH_PBB: + case SET_MPLS_LABEL: + case SET_MPLS_TC: + case SET_MPLS_TTL: + case SET_NW_ECN: + case SET_NW_TOS: + case SET_NW_TTL: + case SET_QUEUE: + case STRIP_VLAN: + case ENQUEUE: + default: + log.warn("Action type {} not yet implemented.", act.getType()); + } + } + + return builder.build(); + } + + private void handleSetField(TrafficTreatment.Builder builder, OFOxm<?> oxm) { + switch (oxm.getMatchField().id) { + case VLAN_PCP: + @SuppressWarnings("unchecked") + OFOxm<VlanPcp> vlanpcp = (OFOxm<VlanPcp>) oxm; + builder.setVlanPcp(vlanpcp.getValue().getValue()); + break; + case VLAN_VID: + @SuppressWarnings("unchecked") + OFOxm<OFVlanVidMatch> vlanvid = (OFOxm<OFVlanVidMatch>) oxm; + builder.setVlanId(VlanId.vlanId(vlanvid.getValue().getVlan())); + break; + case ETH_DST: + @SuppressWarnings("unchecked") + OFOxm<org.projectfloodlight.openflow.types.MacAddress> ethdst = + (OFOxm<org.projectfloodlight.openflow.types.MacAddress>) oxm; + builder.setEthDst(MacAddress.valueOf(ethdst.getValue().getLong())); + break; + case ETH_SRC: + @SuppressWarnings("unchecked") + OFOxm<org.projectfloodlight.openflow.types.MacAddress> ethsrc = + (OFOxm<org.projectfloodlight.openflow.types.MacAddress>) oxm; + builder.setEthSrc(MacAddress.valueOf(ethsrc.getValue().getLong())); + break; + case IPV4_DST: + @SuppressWarnings("unchecked") + OFOxm<IPv4Address> ip4dst = (OFOxm<IPv4Address>) oxm; + builder.setIpDst(Ip4Address.valueOf(ip4dst.getValue().getInt())); + break; + case IPV4_SRC: + @SuppressWarnings("unchecked") + OFOxm<IPv4Address> ip4src = (OFOxm<IPv4Address>) oxm; + builder.setIpSrc(Ip4Address.valueOf(ip4src.getValue().getInt())); + break; + case MPLS_LABEL: + @SuppressWarnings("unchecked") + OFOxm<U32> labelId = (OFOxm<U32>) oxm; + builder.setMpls(MplsLabel.mplsLabel((int) labelId.getValue().getValue())); + break; + case MPLS_BOS: + @SuppressWarnings("unchecked") + OFOxm<U8> mplsBos = (OFOxm<U8>) oxm; + builder.setMplsBos(mplsBos.getValue() == U8.ZERO ? false : true); + break; + case ARP_OP: + case ARP_SHA: + case ARP_SPA: + case ARP_THA: + case ARP_TPA: + case BSN_EGR_PORT_GROUP_ID: + case BSN_GLOBAL_VRF_ALLOWED: + case BSN_IN_PORTS_128: + case BSN_L3_DST_CLASS_ID: + case BSN_L3_INTERFACE_CLASS_ID: + case BSN_L3_SRC_CLASS_ID: + case BSN_LAG_ID: + case BSN_TCP_FLAGS: + case BSN_UDF0: + case BSN_UDF1: + case BSN_UDF2: + case BSN_UDF3: + case BSN_UDF4: + case BSN_UDF5: + case BSN_UDF6: + case BSN_UDF7: + case BSN_VLAN_XLATE_PORT_GROUP_ID: + case BSN_VRF: + case ETH_TYPE: + case ICMPV4_CODE: + case ICMPV4_TYPE: + case ICMPV6_CODE: + case ICMPV6_TYPE: + case IN_PHY_PORT: + case IN_PORT: + case IPV6_DST: + case IPV6_FLABEL: + case IPV6_ND_SLL: + case IPV6_ND_TARGET: + case IPV6_ND_TLL: + case IPV6_SRC: + case IP_DSCP: + case IP_ECN: + case IP_PROTO: + case METADATA: + case MPLS_TC: + case OCH_SIGID: + case OCH_SIGID_BASIC: + case OCH_SIGTYPE: + case OCH_SIGTYPE_BASIC: + case SCTP_DST: + case SCTP_SRC: + case TCP_DST: + case TCP_SRC: + case TUNNEL_ID: + case UDP_DST: + case UDP_SRC: + default: + log.warn("Set field type {} not yet implemented.", oxm.getMatchField().id); + break; + } + } +} diff --git a/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupModBuilder.java b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupModBuilder.java new file mode 100644 index 00000000..d5804f44 --- /dev/null +++ b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupModBuilder.java @@ -0,0 +1,376 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.group.impl; + +import org.onlab.packet.Ip4Address; +import org.onlab.packet.Ip6Address; +import org.onosproject.core.GroupId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flow.instructions.L0ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L3ModificationInstruction; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.projectfloodlight.openflow.protocol.OFBucket; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFGroupAdd; +import org.projectfloodlight.openflow.protocol.OFGroupDelete; +import org.projectfloodlight.openflow.protocol.OFGroupMod; +import org.projectfloodlight.openflow.protocol.OFGroupType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionGroup; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.oxm.OFOxm; +import org.projectfloodlight.openflow.types.CircuitSignalID; +import org.projectfloodlight.openflow.types.EthType; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.IPv6Address; +import org.projectfloodlight.openflow.types.IPv6FlowLabel; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBooleanValue; +import org.projectfloodlight.openflow.types.OFGroup; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.U32; +import org.projectfloodlight.openflow.types.VlanPcp; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +import static org.slf4j.LoggerFactory.getLogger; + +/* + * Builder for GroupMod. + */ +public final class GroupModBuilder { + + private GroupBuckets buckets; + private GroupId groupId; + private GroupDescription.Type type; + private OFFactory factory; + private Long xid; + + private final Logger log = getLogger(getClass()); + + private static final int OFPCML_NO_BUFFER = 0xffff; + + private GroupModBuilder(GroupBuckets buckets, GroupId groupId, + GroupDescription.Type type, OFFactory factory, + Optional<Long> xid) { + this.buckets = buckets; + this.groupId = groupId; + this.type = type; + this.factory = factory; + this.xid = xid.orElse((long) 0); + } + + /** + * Creates a builder for GroupMod. + * + * @param buckets GroupBuckets object + * @param groupId Group Id to create + * @param type Group type + * @param factory OFFactory object + * @param xid transaction ID + * @return GroupModBuilder object + */ + public static GroupModBuilder builder(GroupBuckets buckets, GroupId groupId, + GroupDescription.Type type, OFFactory factory, + Optional<Long> xid) { + + return new GroupModBuilder(buckets, groupId, type, factory, xid); + } + + /** + * Builds the GroupAdd OF message. + * + * @return GroupAdd OF message + */ + public OFGroupAdd buildGroupAdd() { + + List<OFBucket> ofBuckets = new ArrayList<OFBucket>(); + for (GroupBucket bucket: buckets.buckets()) { + List<OFAction> actions = buildActions(bucket.treatment()); + + OFBucket.Builder bucketBuilder = factory.buildBucket(); + bucketBuilder.setActions(actions); + if (type == GroupDescription.Type.SELECT) { + bucketBuilder.setWeight(1); + } + bucketBuilder.setWatchGroup(OFGroup.ANY); + bucketBuilder.setWatchPort(OFPort.ANY); + OFBucket ofBucket = bucketBuilder.build(); + ofBuckets.add(ofBucket); + } + + OFGroupAdd groupMsg = factory.buildGroupAdd() + .setGroup(OFGroup.of(groupId.id())) + .setBuckets(ofBuckets) + .setGroupType(getOFGroupType(type)) + .setXid(xid) + .build(); + + return groupMsg; + } + + /** + * Builds the GroupMod OF message. + * + * @return GroupMod OF message + */ + public OFGroupMod buildGroupMod() { + List<OFBucket> ofBuckets = new ArrayList<OFBucket>(); + for (GroupBucket bucket: buckets.buckets()) { + List<OFAction> actions = buildActions(bucket.treatment()); + + OFBucket.Builder bucketBuilder = factory.buildBucket(); + bucketBuilder.setActions(actions); + if (type == GroupDescription.Type.SELECT) { + bucketBuilder.setWeight(1); + } + bucketBuilder.setWatchGroup(OFGroup.ANY); + bucketBuilder.setWatchPort(OFPort.ANY); + OFBucket ofBucket = bucketBuilder.build(); + ofBuckets.add(ofBucket); + } + + OFGroupMod groupMsg = factory.buildGroupModify() + .setGroup(OFGroup.of(groupId.id())) + .setBuckets(ofBuckets) + .setGroupType(getOFGroupType(type)) + .setXid(xid) + .build(); + + return groupMsg; + } + + /** + * Builds the GroupDel OF message. + * + * @return GroupDel OF message + */ + public OFGroupDelete buildGroupDel() { + + OFGroupDelete groupMsg = factory.buildGroupDelete() + .setGroup(OFGroup.of(groupId.id())) + .setGroupType(OFGroupType.SELECT) + .setXid(xid) + .build(); + + return groupMsg; + } + + private List<OFAction> buildActions(TrafficTreatment treatment) { + if (treatment == null) { + return Collections.emptyList(); + } + + List<OFAction> actions = new LinkedList<>(); + for (Instruction i : treatment.allInstructions()) { + switch (i.type()) { + case DROP: + log.warn("Saw drop action; assigning drop action"); + return Collections.emptyList(); + case L0MODIFICATION: + actions.add(buildL0Modification(i)); + break; + case L2MODIFICATION: + actions.add(buildL2Modification(i)); + break; + case L3MODIFICATION: + actions.add(buildL3Modification(i)); + break; + case OUTPUT: + Instructions.OutputInstruction out = + (Instructions.OutputInstruction) i; + OFActionOutput.Builder action = factory.actions().buildOutput() + .setPort(OFPort.of((int) out.port().toLong())); + if (out.port().equals(PortNumber.CONTROLLER)) { + action.setMaxLen(OFPCML_NO_BUFFER); + } + actions.add(action.build()); + break; + case GROUP: + Instructions.GroupInstruction grp = + (Instructions.GroupInstruction) i; + OFActionGroup.Builder actgrp = factory.actions().buildGroup() + .setGroup(OFGroup.of(grp.groupId().id())); + actions.add(actgrp.build()); + break; + default: + log.warn("Instruction type {} not yet implemented.", i.type()); + } + } + + return actions; + } + + private OFAction buildL0Modification(Instruction i) { + L0ModificationInstruction l0m = (L0ModificationInstruction) i; + switch (l0m.subtype()) { + case LAMBDA: + L0ModificationInstruction.ModLambdaInstruction ml = + (L0ModificationInstruction.ModLambdaInstruction) i; + return factory.actions().circuit(factory.oxms().ochSigidBasic( + new CircuitSignalID((byte) 1, (byte) 2, ml.lambda(), (short) 1))); + default: + log.warn("Unimplemented action type {}.", l0m.subtype()); + break; + } + return null; + } + + private OFAction buildL2Modification(Instruction i) { + L2ModificationInstruction l2m = (L2ModificationInstruction) i; + L2ModificationInstruction.ModEtherInstruction eth; + OFOxm<?> oxm = null; + switch (l2m.subtype()) { + case ETH_DST: + eth = (L2ModificationInstruction.ModEtherInstruction) l2m; + oxm = factory.oxms().ethDst(MacAddress.of(eth.mac().toLong())); + break; + case ETH_SRC: + eth = (L2ModificationInstruction.ModEtherInstruction) l2m; + oxm = factory.oxms().ethSrc(MacAddress.of(eth.mac().toLong())); + break; + case VLAN_ID: + L2ModificationInstruction.ModVlanIdInstruction vlanId = + (L2ModificationInstruction.ModVlanIdInstruction) l2m; + oxm = factory.oxms().vlanVid(OFVlanVidMatch.ofVlan(vlanId.vlanId().toShort())); + break; + case VLAN_PCP: + L2ModificationInstruction.ModVlanPcpInstruction vlanPcp = + (L2ModificationInstruction.ModVlanPcpInstruction) l2m; + oxm = factory.oxms().vlanPcp(VlanPcp.of(vlanPcp.vlanPcp())); + break; + case VLAN_POP: + return factory.actions().popVlan(); + case VLAN_PUSH: + L2ModificationInstruction.PushHeaderInstructions pushVlanInstruction + = (L2ModificationInstruction.PushHeaderInstructions) l2m; + return factory.actions().pushVlan( + EthType.of(pushVlanInstruction.ethernetType().toShort())); + case MPLS_PUSH: + L2ModificationInstruction.PushHeaderInstructions pushHeaderInstructions = + (L2ModificationInstruction.PushHeaderInstructions) l2m; + return factory.actions().pushMpls(EthType.of(pushHeaderInstructions + .ethernetType().toShort())); + case MPLS_POP: + L2ModificationInstruction.PushHeaderInstructions popHeaderInstructions = + (L2ModificationInstruction.PushHeaderInstructions) l2m; + return factory.actions().popMpls(EthType.of(popHeaderInstructions + .ethernetType().toShort())); + case MPLS_LABEL: + L2ModificationInstruction.ModMplsLabelInstruction mplsLabel = + (L2ModificationInstruction.ModMplsLabelInstruction) l2m; + oxm = factory.oxms().mplsLabel(U32.of(mplsLabel.mplsLabel().toInt())); + break; + case MPLS_BOS: + L2ModificationInstruction.ModMplsBosInstruction mplsBos = + (L2ModificationInstruction.ModMplsBosInstruction) l2m; + oxm = factory.oxms() + .mplsBos(mplsBos.mplsBos() ? OFBooleanValue.TRUE + : OFBooleanValue.FALSE); + break; + case DEC_MPLS_TTL: + return factory.actions().decMplsTtl(); + default: + log.warn("Unimplemented action type {}.", l2m.subtype()); + break; + } + + if (oxm != null) { + return factory.actions().buildSetField().setField(oxm).build(); + } + return null; + } + + private OFAction buildL3Modification(Instruction i) { + L3ModificationInstruction l3m = (L3ModificationInstruction) i; + L3ModificationInstruction.ModIPInstruction ip; + Ip4Address ip4; + Ip6Address ip6; + OFOxm<?> oxm = null; + switch (l3m.subtype()) { + case IPV4_SRC: + ip = (L3ModificationInstruction.ModIPInstruction) i; + ip4 = ip.ip().getIp4Address(); + oxm = factory.oxms().ipv4Src(IPv4Address.of(ip4.toInt())); + break; + case IPV4_DST: + ip = (L3ModificationInstruction.ModIPInstruction) i; + ip4 = ip.ip().getIp4Address(); + oxm = factory.oxms().ipv4Dst(IPv4Address.of(ip4.toInt())); + break; + case IPV6_SRC: + ip = (L3ModificationInstruction.ModIPInstruction) i; + ip6 = ip.ip().getIp6Address(); + oxm = factory.oxms().ipv6Src(IPv6Address.of(ip6.toOctets())); + break; + case IPV6_DST: + ip = (L3ModificationInstruction.ModIPInstruction) i; + ip6 = ip.ip().getIp6Address(); + oxm = factory.oxms().ipv6Dst(IPv6Address.of(ip6.toOctets())); + break; + case IPV6_FLABEL: + L3ModificationInstruction.ModIPv6FlowLabelInstruction flowLabelInstruction = + (L3ModificationInstruction.ModIPv6FlowLabelInstruction) i; + int flowLabel = flowLabelInstruction.flowLabel(); + oxm = factory.oxms().ipv6Flabel(IPv6FlowLabel.of(flowLabel)); + break; + case DEC_TTL: + return factory.actions().decNwTtl(); + case TTL_IN: + return factory.actions().copyTtlIn(); + case TTL_OUT: + return factory.actions().copyTtlOut(); + default: + log.warn("Unimplemented action type {}.", l3m.subtype()); + break; + } + + if (oxm != null) { + return factory.actions().buildSetField().setField(oxm).build(); + } + return null; + } + + private OFGroupType getOFGroupType(GroupDescription.Type groupType) { + switch (groupType) { + case INDIRECT: + return OFGroupType.INDIRECT; + case SELECT: + return OFGroupType.SELECT; + case FAILOVER: + return OFGroupType.FF; + case ALL: + return OFGroupType.ALL; + default: + log.error("Unsupported group type : {}", groupType); + break; + } + return null; + } +} + diff --git a/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupStatsCollector.java b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupStatsCollector.java new file mode 100644 index 00000000..9816426b --- /dev/null +++ b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/GroupStatsCollector.java @@ -0,0 +1,111 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.of.group.impl; + +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFGroupDescStatsRequest; +import org.projectfloodlight.openflow.protocol.OFGroupStatsRequest; +import org.projectfloodlight.openflow.types.OFGroup; +import org.slf4j.Logger; + +import java.util.concurrent.TimeUnit; + +import static org.slf4j.LoggerFactory.getLogger; + +/* + * Sends Group Stats Request and collect the group statistics with a time interval. + */ +public class GroupStatsCollector implements TimerTask { + + private final HashedWheelTimer timer = Timer.getTimer(); + private final OpenFlowSwitch sw; + private final Logger log = getLogger(getClass()); + private final int refreshInterval; + + private Timeout timeout; + + private boolean stopTimer = false; + + /** + * Creates a GroupStatsCollector object. + * + * @param sw Open Flow switch + * @param interval time interval for collecting group statistic + */ + public GroupStatsCollector(OpenFlowSwitch sw, int interval) { + this.sw = sw; + this.refreshInterval = interval; + } + + @Override + public void run(Timeout timeout) throws Exception { + log.trace("Collecting stats for {}", sw.getStringId()); + + sendGroupStatistic(); + + if (!this.stopTimer) { + log.trace("Scheduling stats collection in {} seconds for {}", + this.refreshInterval, this.sw.getStringId()); + timeout.getTimer().newTimeout(this, refreshInterval, + TimeUnit.SECONDS); + } + } + + private void sendGroupStatistic() { + if (log.isTraceEnabled()) { + log.trace("sendGroupStatistics {}:{}", sw.getStringId(), sw.getRole()); + } + if (sw.getRole() != RoleState.MASTER) { + return; + } + Long statsXid = OpenFlowGroupProvider.getXidAndAdd(2); + OFGroupStatsRequest statsRequest = sw.factory().buildGroupStatsRequest() + .setGroup(OFGroup.ALL) + .setXid(statsXid) + .build(); + sw.sendMsg(statsRequest); + + Long descXid = statsXid + 1; + OFGroupDescStatsRequest descStatsRequest = + sw.factory().buildGroupDescStatsRequest() + .setXid(descXid) + .build(); + sw.sendMsg(descStatsRequest); + } + + /** + * Starts the collector. + */ + public void start() { + log.info("Starting Group Stats collection thread for {}", sw.getStringId()); + timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS); + } + + /** + * Stops the collector. + */ + public void stop() { + log.info("Stopping Group Stats collection thread for {}", sw.getStringId()); + this.stopTimer = true; + timeout.cancel(); + } +} diff --git a/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java new file mode 100644 index 00000000..78650fe6 --- /dev/null +++ b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProvider.java @@ -0,0 +1,366 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.of.group.impl; + +import com.google.common.collect.Maps; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.core.GroupId; +import org.onosproject.net.DeviceId; +import org.onosproject.net.group.DefaultGroup; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupOperations; +import org.onosproject.net.group.GroupProvider; +import org.onosproject.net.group.GroupProviderRegistry; +import org.onosproject.net.group.GroupProviderService; +import org.onosproject.net.group.StoredGroupBucketEntry; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFBucketCounter; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFErrorType; +import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry; +import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFGroupMod; +import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry; +import org.projectfloodlight.openflow.protocol.OFGroupStatsReply; +import org.projectfloodlight.openflow.protocol.OFGroupType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to handle Group. + */ +@Component(immediate = true) +public class OpenFlowGroupProvider extends AbstractProvider implements GroupProvider { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenFlowController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected GroupProviderRegistry providerRegistry; + + private GroupProviderService providerService; + + static final int POLL_INTERVAL = 10; + + private final InternalGroupProvider listener = new InternalGroupProvider(); + + private static final AtomicLong XID_COUNTER = new AtomicLong(1); + private final Map<Dpid, GroupStatsCollector> collectors = Maps.newHashMap(); + private final Map<Long, OFStatsReply> groupStats = Maps.newConcurrentMap(); + private final Map<GroupId, GroupOperation> pendingGroupOperations = + Maps.newConcurrentMap(); + + /* Map<Group ID, Transaction ID> */ + private final Map<GroupId, Long> pendingXidMaps = Maps.newConcurrentMap(); + + /** + * Creates a OpenFlow group provider. + */ + public OpenFlowGroupProvider() { + super(new ProviderId("of", "org.onosproject.provider.group")); + } + + @Activate + public void activate() { + providerService = providerRegistry.register(this); + controller.addListener(listener); + controller.addEventListener(listener); + + for (OpenFlowSwitch sw : controller.getSwitches()) { + if (isGroupSupported(sw)) { + GroupStatsCollector gsc = new GroupStatsCollector(sw, POLL_INTERVAL); + gsc.start(); + collectors.put(new Dpid(sw.getId()), gsc); + } + } + + log.info("Started"); + } + + @Deactivate + public void deactivate() { + providerRegistry.unregister(this); + providerService = null; + + log.info("Stopped"); + } + + @Override + public void performGroupOperation(DeviceId deviceId, GroupOperations groupOps) { + Map<OFGroupMod, OpenFlowSwitch> mods = Maps.newIdentityHashMap(); + final Dpid dpid = Dpid.dpid(deviceId.uri()); + OpenFlowSwitch sw = controller.getSwitch(dpid); + for (GroupOperation groupOperation: groupOps.operations()) { + if (sw == null) { + log.error("SW {} is not found", dpid); + return; + } + final Long groupModXid = XID_COUNTER.getAndIncrement(); + GroupModBuilder builder = + GroupModBuilder.builder(groupOperation.buckets(), + groupOperation.groupId(), + groupOperation.groupType(), + sw.factory(), + Optional.of(groupModXid)); + OFGroupMod groupMod = null; + switch (groupOperation.opType()) { + case ADD: + groupMod = builder.buildGroupAdd(); + break; + case MODIFY: + groupMod = builder.buildGroupMod(); + break; + case DELETE: + groupMod = builder.buildGroupDel(); + break; + default: + log.error("Unsupported Group operation"); + } + sw.sendMsg(groupMod); + GroupId groudId = new DefaultGroupId(groupMod.getGroup().getGroupNumber()); + pendingGroupOperations.put(groudId, groupOperation); + pendingXidMaps.put(groudId, groupModXid); + } + } + + private void pushGroupMetrics(Dpid dpid, OFStatsReply statsReply) { + DeviceId deviceId = DeviceId.deviceId(Dpid.uri(dpid)); + + OFGroupStatsReply groupStatsReply = null; + OFGroupDescStatsReply groupDescStatsReply = null; + + synchronized (groupStats) { + if (statsReply.getStatsType() == OFStatsType.GROUP) { + OFStatsReply reply = groupStats.get(statsReply.getXid() + 1); + if (reply != null) { + groupStatsReply = (OFGroupStatsReply) statsReply; + groupDescStatsReply = (OFGroupDescStatsReply) reply; + groupStats.remove(statsReply.getXid() + 1); + } else { + groupStats.put(statsReply.getXid(), statsReply); + } + } else if (statsReply.getStatsType() == OFStatsType.GROUP_DESC) { + OFStatsReply reply = groupStats.get(statsReply.getXid() - 1); + if (reply != null) { + groupStatsReply = (OFGroupStatsReply) reply; + groupDescStatsReply = (OFGroupDescStatsReply) statsReply; + groupStats.remove(statsReply.getXid() - 1); + } else { + groupStats.put(statsReply.getXid(), statsReply); + } + } + } + + if (groupStatsReply != null && groupDescStatsReply != null) { + Collection<Group> groups = buildGroupMetrics(deviceId, + groupStatsReply, groupDescStatsReply); + providerService.pushGroupMetrics(deviceId, groups); + for (Group group: groups) { + pendingGroupOperations.remove(group.id()); + pendingXidMaps.remove(group.id()); + } + } + } + + private Collection<Group> buildGroupMetrics(DeviceId deviceId, + OFGroupStatsReply groupStatsReply, + OFGroupDescStatsReply groupDescStatsReply) { + + Map<Integer, Group> groups = Maps.newHashMap(); + + + for (OFGroupDescStatsEntry entry: groupDescStatsReply.getEntries()) { + int id = entry.getGroup().getGroupNumber(); + GroupId groupId = new DefaultGroupId(id); + GroupDescription.Type type = getGroupType(entry.getGroupType()); + GroupBuckets buckets = new GroupBucketEntryBuilder(entry.getBuckets(), + entry.getGroupType()).build(); + DefaultGroup group = new DefaultGroup(groupId, deviceId, type, buckets); + groups.put(id, group); + } + + for (OFGroupStatsEntry entry: groupStatsReply.getEntries()) { + int groupId = entry.getGroup().getGroupNumber(); + DefaultGroup group = (DefaultGroup) groups.get(groupId); + if (group != null) { + group.setBytes(entry.getByteCount().getValue()); + group.setLife(entry.getDurationSec()); + group.setPackets(entry.getPacketCount().getValue()); + group.setReferenceCount(entry.getRefCount()); + int bucketIndex = 0; + for (OFBucketCounter bucketStats:entry.getBucketStats()) { + ((StoredGroupBucketEntry) group.buckets().buckets() + .get(bucketIndex)) + .setPackets(bucketStats + .getPacketCount().getValue()); + ((StoredGroupBucketEntry) group.buckets().buckets() + .get(bucketIndex)) + .setBytes(entry.getBucketStats() + .get(bucketIndex) + .getByteCount().getValue()); + bucketIndex++; + } + } + } + + return groups.values(); + } + + private GroupDescription.Type getGroupType(OFGroupType type) { + switch (type) { + case ALL: + return GroupDescription.Type.ALL; + case INDIRECT: + return GroupDescription.Type.INDIRECT; + case SELECT: + return GroupDescription.Type.SELECT; + case FF: + return GroupDescription.Type.FAILOVER; + default: + log.error("Unsupported OF group type : {}", type); + break; + } + return null; + } + + /** + * Returns a transaction ID for entire group operations and increases + * the counter by the number given. + * + * @param increase the amount to increase the counter by + * @return a transaction ID + */ + public static long getXidAndAdd(int increase) { + return XID_COUNTER.getAndAdd(increase); + } + + private boolean isGroupSupported(OpenFlowSwitch sw) { + if (sw.factory().getVersion() == OFVersion.OF_10 || + sw.factory().getVersion() == OFVersion.OF_11 || + sw.factory().getVersion() == OFVersion.OF_12) { + return false; + } + + return true; + } + + private class InternalGroupProvider + implements OpenFlowSwitchListener, OpenFlowEventListener { + + @Override + public void handleMessage(Dpid dpid, OFMessage msg) { + switch (msg.getType()) { + case STATS_REPLY: + pushGroupMetrics(dpid, (OFStatsReply) msg); + break; + case ERROR: + OFErrorMsg errorMsg = (OFErrorMsg) msg; + if (errorMsg.getErrType() == OFErrorType.GROUP_MOD_FAILED) { + GroupId pendingGroupId = null; + for (Map.Entry<GroupId, Long> entry: pendingXidMaps.entrySet()) { + if (entry.getValue() == errorMsg.getXid()) { + pendingGroupId = entry.getKey(); + break; + } + } + if (pendingGroupId == null) { + log.warn("Error for unknown group operation: {}", + errorMsg.getXid()); + } else { + GroupOperation operation = + pendingGroupOperations.get(pendingGroupId); + DeviceId deviceId = DeviceId.deviceId(Dpid.uri(dpid)); + if (operation != null) { + providerService.groupOperationFailed(deviceId, + operation); + pendingGroupOperations.remove(pendingGroupId); + pendingXidMaps.remove(pendingGroupId); + log.warn("Received an group mod error {}", msg); + } else { + log.error("Cannot find pending group operation with group ID: {}", + pendingGroupId); + } + } + break; + } + default: + break; + } + } + + @Override + public void switchAdded(Dpid dpid) { + OpenFlowSwitch sw = controller.getSwitch(dpid); + if (isGroupSupported(sw)) { + GroupStatsCollector gsc = new GroupStatsCollector( + controller.getSwitch(dpid), POLL_INTERVAL); + gsc.start(); + collectors.put(dpid, gsc); + } + } + + @Override + public void switchRemoved(Dpid dpid) { + GroupStatsCollector collector = collectors.remove(dpid); + if (collector != null) { + collector.stop(); + } + } + + @Override + public void switchChanged(Dpid dpid) { + } + + @Override + public void portChanged(Dpid dpid, OFPortStatus status) { + } + + @Override + public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) { + } + } + +} diff --git a/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/package-info.java b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/package-info.java new file mode 100644 index 00000000..9fda4a31 --- /dev/null +++ b/framework/src/onos/providers/openflow/group/src/main/java/org/onosproject/provider/of/group/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses OpenFlow controller as a means of device port group management. + */ +package org.onosproject.provider.of.group.impl;
\ No newline at end of file diff --git a/framework/src/onos/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java b/framework/src/onos/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java new file mode 100644 index 00000000..d66ba090 --- /dev/null +++ b/framework/src/onos/providers/openflow/group/src/test/java/org/onosproject/provider/of/group/impl/OpenFlowGroupProviderTest.java @@ -0,0 +1,397 @@ +package org.onosproject.provider.of.group.impl; + +import com.google.common.collect.Lists; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.core.GroupId; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.group.DefaultGroupBucket; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupBucket; +import org.onosproject.net.group.GroupBuckets; +import org.onosproject.net.group.GroupDescription; +import org.onosproject.net.group.GroupOperation; +import org.onosproject.net.group.GroupOperations; +import org.onosproject.net.group.GroupProvider; +import org.onosproject.net.group.GroupProviderRegistry; +import org.onosproject.net.group.GroupProviderService; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.PacketListener; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFFactories; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFGroupMod; +import org.projectfloodlight.openflow.protocol.OFGroupModFailedCode; +import org.projectfloodlight.openflow.protocol.OFGroupStatsReply; +import org.projectfloodlight.openflow.protocol.OFGroupType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.errormsg.OFGroupModFailedErrorMsg; +import org.projectfloodlight.openflow.types.OFGroup; + +import java.util.Collection; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.*; + +public class OpenFlowGroupProviderTest { + + OpenFlowGroupProvider provider = new OpenFlowGroupProvider(); + private final OpenFlowController controller = new TestController(); + GroupProviderRegistry providerRegistry = new TestGroupProviderRegistry(); + GroupProviderService providerService; + + private DeviceId deviceId = DeviceId.deviceId("of:0000000000000001"); + private Dpid dpid1 = Dpid.dpid(deviceId.uri()); + + @Before + public void setUp() { + provider.controller = controller; + provider.providerRegistry = providerRegistry; + provider.activate(); + } + + @Test + public void basics() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + } + + @Test + public void addGroup() { + + GroupId groupId = new DefaultGroupId(1); + + List<GroupBucket> bucketList = Lists.newArrayList(); + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + builder.setOutput(PortNumber.portNumber(1)); + GroupBucket bucket = + DefaultGroupBucket.createSelectGroupBucket(builder.build()); + bucketList.add(bucket); + GroupBuckets buckets = new GroupBuckets(bucketList); + + List<GroupOperation> operationList = Lists.newArrayList(); + GroupOperation operation = GroupOperation.createAddGroupOperation(groupId, + GroupDescription.Type.SELECT, buckets); + operationList.add(operation); + GroupOperations operations = new GroupOperations(operationList); + + provider.performGroupOperation(deviceId, operations); + + final Dpid dpid = Dpid.dpid(deviceId.uri()); + TestOpenFlowSwitch sw = (TestOpenFlowSwitch) controller.getSwitch(dpid); + assertNotNull("Switch should not be nul", sw); + assertNotNull("OFGroupMsg should not be null", sw.msg); + + } + + + @Test + public void groupModFailure() { + TestOpenFlowGroupProviderService testProviderService = + (TestOpenFlowGroupProviderService) providerService; + + GroupId groupId = new DefaultGroupId(1); + List<GroupBucket> bucketList = Lists.newArrayList(); + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + builder.setOutput(PortNumber.portNumber(1)); + GroupBucket bucket = + DefaultGroupBucket.createSelectGroupBucket(builder.build()); + bucketList.add(bucket); + GroupBuckets buckets = new GroupBuckets(bucketList); + List<GroupOperation> operationList = Lists.newArrayList(); + GroupOperation operation = GroupOperation.createAddGroupOperation(groupId, + GroupDescription.Type.SELECT, buckets); + operationList.add(operation); + GroupOperations operations = new GroupOperations(operationList); + + provider.performGroupOperation(deviceId, operations); + + OFGroupModFailedErrorMsg.Builder errorBuilder = + OFFactories.getFactory(OFVersion.OF_13).errorMsgs().buildGroupModFailedErrorMsg(); + OFGroupMod.Builder groupBuilder = OFFactories.getFactory(OFVersion.OF_13).buildGroupModify(); + groupBuilder.setGroupType(OFGroupType.ALL); + groupBuilder.setGroup(OFGroup.of(1)); + errorBuilder.setCode(OFGroupModFailedCode.GROUP_EXISTS); + errorBuilder.setXid(provider.getXidAndAdd(0) - 1); + + controller.processPacket(dpid1, errorBuilder.build()); + + assertNotNull("Operation failed should not be null", + testProviderService.failedOperation); + } + + + @Test + public void groupStatsEvent() { + TestOpenFlowGroupProviderService testProviderService = + (TestOpenFlowGroupProviderService) providerService; + + OFGroupStatsReply.Builder rep1 = + OFFactories.getFactory(OFVersion.OF_13).buildGroupStatsReply(); + rep1.setXid(1); + controller.processPacket(dpid1, rep1.build()); + OFGroupDescStatsReply.Builder rep2 = + OFFactories.getFactory(OFVersion.OF_13).buildGroupDescStatsReply(); + assertNull("group entries is not set yet", testProviderService.getGroupEntries()); + + rep2.setXid(2); + controller.processPacket(dpid1, rep2.build()); + assertNotNull("group entries should be set", testProviderService.getGroupEntries()); + } + + + + @After + public void tearDown() { + provider.deactivate(); + provider.providerRegistry = null; + provider.controller = null; + } + + private class TestOpenFlowGroupProviderService + extends AbstractProviderService<GroupProvider> + implements GroupProviderService { + + Collection<Group> groups = null; + GroupOperation failedOperation = null; + + protected TestOpenFlowGroupProviderService(GroupProvider provider) { + super(provider); + } + + @Override + public void groupOperationFailed(DeviceId deviceId, GroupOperation operation) { + this.failedOperation = operation; + } + + @Override + public void pushGroupMetrics(DeviceId deviceId, Collection<Group> groupEntries) { + this.groups = groupEntries; + } + + public Collection<Group> getGroupEntries() { + return groups; + } + } + + private class TestController implements OpenFlowController { + + OpenFlowEventListener eventListener = null; + List<OpenFlowSwitch> switches = Lists.newArrayList(); + + public TestController() { + OpenFlowSwitch testSwitch = new TestOpenFlowSwitch(); + switches.add(testSwitch); + } + + @Override + public void addListener(OpenFlowSwitchListener listener) { + } + + @Override + public void removeListener(OpenFlowSwitchListener listener) { + + } + + @Override + public void addPacketListener(int priority, PacketListener listener) { + + } + + @Override + public void removePacketListener(PacketListener listener) { + + } + + @Override + public void addEventListener(OpenFlowEventListener listener) { + this.eventListener = listener; + } + + @Override + public void removeEventListener(OpenFlowEventListener listener) { + + } + + @Override + public void write(Dpid dpid, OFMessage msg) { + + } + + @Override + public void processPacket(Dpid dpid, OFMessage msg) { + eventListener.handleMessage(dpid, msg); + } + + @Override + public void setRole(Dpid dpid, RoleState role) { + + } + + @Override + public Iterable<OpenFlowSwitch> getSwitches() { + return switches; + } + + @Override + public Iterable<OpenFlowSwitch> getMasterSwitches() { + return null; + } + + @Override + public Iterable<OpenFlowSwitch> getEqualSwitches() { + return null; + } + + @Override + public OpenFlowSwitch getSwitch(Dpid dpid) { + return switches.get(0); + } + + @Override + public OpenFlowSwitch getMasterSwitch(Dpid dpid) { + return null; + } + + @Override + public OpenFlowSwitch getEqualSwitch(Dpid dpid) { + return null; + } + + } + + private class TestGroupProviderRegistry implements GroupProviderRegistry { + + @Override + public GroupProviderService register(GroupProvider provider) { + providerService = new TestOpenFlowGroupProviderService(provider); + return providerService; + } + + @Override + public void unregister(GroupProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + } + + private class TestOpenFlowSwitch implements OpenFlowSwitch { + + OFMessage msg = null; + + @Override + public void sendMsg(OFMessage msg) { + this.msg = msg; + } + + @Override + public void sendMsg(List<OFMessage> msgs) { + + } + + @Override + public void handleMessage(OFMessage fromSwitch) { + + } + + @Override + public void setRole(RoleState role) { + + } + + @Override + public RoleState getRole() { + return null; + } + + @Override + public List<OFPortDesc> getPorts() { + return null; + } + + @Override + public OFFactory factory() { + return OFFactories.getFactory(OFVersion.OF_13); + } + + @Override + public String getStringId() { + return null; + } + + @Override + public long getId() { + return 0; + } + + @Override + public String manufacturerDescription() { + return null; + } + + @Override + public String datapathDescription() { + return null; + } + + @Override + public String hardwareDescription() { + return null; + } + + @Override + public String softwareDescription() { + return null; + } + + @Override + public String serialNumber() { + return null; + } + + @Override + public boolean isConnected() { + return false; + } + + @Override + public void disconnectSwitch() { + + } + + @Override + public void returnRoleReply(RoleState requested, RoleState response) { + + } + + @Override + public Device.Type deviceType() { + return Device.Type.SWITCH; + } + + @Override + public String channelId() { + return null; + } + + } +}
\ No newline at end of file diff --git a/framework/src/onos/providers/openflow/meter/pom.xml b/framework/src/onos/providers/openflow/meter/pom.xml new file mode 100644 index 00000000..9de5c1b0 --- /dev/null +++ b/framework/src/onos/providers/openflow/meter/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-provider-meter</artifactId> + <packaging>bundle</packaging> + + <description>ONOS OpenFlow protocol meter provider</description> + +</project>
\ No newline at end of file diff --git a/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/MeterModBuilder.java b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/MeterModBuilder.java new file mode 100644 index 00000000..c07354b6 --- /dev/null +++ b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/MeterModBuilder.java @@ -0,0 +1,159 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.meter.impl; + +import org.onosproject.net.meter.Band; +import org.onosproject.net.meter.Meter; +import org.onosproject.net.meter.MeterId; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMeterFlags; +import org.projectfloodlight.openflow.protocol.OFMeterMod; +import org.projectfloodlight.openflow.protocol.OFMeterModCommand; +import org.projectfloodlight.openflow.protocol.meterband.OFMeterBand; +import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandDrop; +import org.projectfloodlight.openflow.protocol.meterband.OFMeterBandDscpRemark; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Builder for a meter modification. + */ +public final class MeterModBuilder { + + private final Logger log = getLogger(getClass()); + + private final long xid; + private final OFFactory factory; + private Meter.Unit unit = Meter.Unit.KB_PER_SEC; + private boolean burst = false; + private Long id; + private Collection<Band> bands; + + public MeterModBuilder(long xid, OFFactory factory) { + this.xid = xid; + this.factory = factory; + } + + public static MeterModBuilder builder(long xid, OFFactory factory) { + return new MeterModBuilder(xid, factory); + } + + public MeterModBuilder withRateUnit(Meter.Unit unit) { + this.unit = unit; + return this; + } + + public MeterModBuilder burst() { + this.burst = true; + return this; + } + + public MeterModBuilder withId(MeterId meterId) { + this.id = meterId.id(); + return this; + } + + public MeterModBuilder withBands(Collection<Band> bands) { + this.bands = bands; + return this; + } + + public OFMeterMod add() { + validate(); + OFMeterMod.Builder builder = builderMeterMod(); + builder.setCommand(OFMeterModCommand.ADD.ordinal()); + return builder.build(); + } + + public OFMeterMod remove() { + validate(); + OFMeterMod.Builder builder = builderMeterMod(); + builder.setCommand(OFMeterModCommand.DELETE.ordinal()); + return builder.build(); + } + + public OFMeterMod modify() { + validate(); + OFMeterMod.Builder builder = builderMeterMod(); + builder.setCommand(OFMeterModCommand.MODIFY.ordinal()); + return builder.build(); + } + + private OFMeterMod.Builder builderMeterMod() { + OFMeterMod.Builder builder = factory.buildMeterMod(); + int flags = 0; + if (burst) { + // covering loxi short comings. + flags |= 1 << OFMeterFlags.BURST.ordinal(); + } + switch (unit) { + case PKTS_PER_SEC: + flags |= 1 << OFMeterFlags.PKTPS.ordinal(); + break; + case KB_PER_SEC: + flags |= 1 << OFMeterFlags.KBPS.ordinal(); + break; + default: + log.warn("Unknown unit type {}", unit); + } + //FIXME: THIS WILL CHANGE IN OF1.4 to setBands. + builder.setMeters(buildBands()); + builder.setFlags(flags) + .setMeterId(id) + .setXid(xid); + return builder; + } + + private List<OFMeterBand> buildBands() { + return bands.stream().map(b -> { + switch (b.type()) { + case DROP: + OFMeterBandDrop.Builder dropBuilder = + factory.meterBands().buildDrop(); + if (burst) { + dropBuilder.setBurstSize(b.burst()); + } + dropBuilder.setRate(b.rate()); + return dropBuilder.build(); + case REMARK: + OFMeterBandDscpRemark.Builder remarkBand = + factory.meterBands().buildDscpRemark(); + if (burst) { + remarkBand.setBurstSize(b.burst()); + } + remarkBand.setRate(b.rate()); + remarkBand.setPrecLevel(b.dropPrecedence()); + return remarkBand.build(); + default: + log.warn("Unknown band type {}", b.type()); + return null; + } + }).filter(value -> value != null).collect(Collectors.toList()); + } + + private void validate() { + checkNotNull(id, "id cannot be null"); + checkNotNull(bands, "Must have bands"); + checkArgument(bands.size() > 0, "Must have at lease one band"); + } +} diff --git a/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/MeterStatsCollector.java b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/MeterStatsCollector.java new file mode 100644 index 00000000..83dc6458 --- /dev/null +++ b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/MeterStatsCollector.java @@ -0,0 +1,103 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.of.meter.impl; + +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFMeterStatsRequest; +import org.slf4j.Logger; + +import java.util.concurrent.TimeUnit; + +import static org.slf4j.LoggerFactory.getLogger; + +/* + * Sends Meter Stats Request and collect the Meter statistics with a time interval. + */ +public class MeterStatsCollector implements TimerTask { + + private final HashedWheelTimer timer = Timer.getTimer(); + private final OpenFlowSwitch sw; + private final Logger log = getLogger(getClass()); + private final int refreshInterval; + + private Timeout timeout; + + private boolean stopTimer = false; + + /** + * Creates a GroupStatsCollector object. + * + * @param sw Open Flow switch + * @param interval time interval for collecting group statistic + */ + public MeterStatsCollector(OpenFlowSwitch sw, int interval) { + this.sw = sw; + this.refreshInterval = interval; + } + + @Override + public void run(Timeout timeout) throws Exception { + log.trace("Collecting stats for {}", sw.getStringId()); + + sendMeterStatistic(); + + if (!this.stopTimer) { + log.trace("Scheduling stats collection in {} seconds for {}", + this.refreshInterval, this.sw.getStringId()); + timeout.getTimer().newTimeout(this, refreshInterval, + TimeUnit.SECONDS); + } + } + + private void sendMeterStatistic() { + if (log.isTraceEnabled()) { + log.trace("sendMeterStatistics {}:{}", sw.getStringId(), sw.getRole()); + } + if (sw.getRole() != RoleState.MASTER) { + return; + } + + OFMeterStatsRequest.Builder builder = + sw.factory().buildMeterStatsRequest(); + builder.setXid(0).setMeterId(0xFFFFFFFF); + + sw.sendMsg(builder.build()); + + } + + /** + * Starts the collector. + */ + public void start() { + log.info("Starting Meter Stats collection thread for {}", sw.getStringId()); + timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS); + } + + /** + * Stops the collector. + */ + public void stop() { + log.info("Stopping Meter Stats collection thread for {}", sw.getStringId()); + this.stopTimer = true; + timeout.cancel(); + } +} diff --git a/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java new file mode 100644 index 00000000..f5a777be --- /dev/null +++ b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProvider.java @@ -0,0 +1,393 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.of.meter.impl; + + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.RemovalCause; +import com.google.common.cache.RemovalNotification; +import com.google.common.collect.Maps; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.core.CoreService; +import org.onosproject.net.meter.Band; +import org.onosproject.net.meter.DefaultBand; +import org.onosproject.net.meter.DefaultMeter; +import org.onosproject.net.meter.Meter; +import org.onosproject.net.meter.MeterFailReason; +import org.onosproject.net.meter.MeterId; +import org.onosproject.net.meter.MeterOperation; +import org.onosproject.net.meter.MeterOperations; +import org.onosproject.net.meter.MeterProvider; +import org.onosproject.net.meter.MeterProviderRegistry; +import org.onosproject.net.meter.MeterProviderService; +import org.onosproject.net.meter.MeterState; +import org.onosproject.net.DeviceId; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFErrorType; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFMeterBandStats; +import org.projectfloodlight.openflow.protocol.OFMeterConfigStatsReply; +import org.projectfloodlight.openflow.protocol.OFMeterStats; +import org.projectfloodlight.openflow.protocol.OFMeterStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.errormsg.OFMeterModFailedErrorMsg; +import org.slf4j.Logger; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Provider which uses an OpenFlow controller to handle meters. + */ +@Component(immediate = true, enabled = true) +public class OpenFlowMeterProvider extends AbstractProvider implements MeterProvider { + + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenFlowController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MeterProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + private MeterProviderService providerService; + + private static final AtomicLong XID_COUNTER = new AtomicLong(1); + + static final int POLL_INTERVAL = 10; + static final long TIMEOUT = 30; + + private Cache<Long, MeterOperation> pendingOperations; + + + private InternalMeterListener listener = new InternalMeterListener(); + private Map<Dpid, MeterStatsCollector> collectors = Maps.newHashMap(); + + /** + * Creates a OpenFlow meter provider. + */ + public OpenFlowMeterProvider() { + super(new ProviderId("of", "org.onosproject.provider.meter")); + } + + @Activate + public void activate() { + providerService = providerRegistry.register(this); + + pendingOperations = CacheBuilder.newBuilder() + .expireAfterWrite(TIMEOUT, TimeUnit.SECONDS) + .removalListener((RemovalNotification<Long, MeterOperation> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + providerService.meterOperationFailed(notification.getValue(), + MeterFailReason.TIMEOUT); + } + }).build(); + + controller.addEventListener(listener); + controller.addListener(listener); + + controller.getSwitches().forEach((sw -> createStatsCollection(sw))); + } + + @Deactivate + public void deactivate() { + providerRegistry.unregister(this); + controller.removeEventListener(listener); + controller.removeListener(listener); + providerService = null; + } + + @Override + public void performMeterOperation(DeviceId deviceId, MeterOperations meterOps) { + Dpid dpid = Dpid.dpid(deviceId.uri()); + OpenFlowSwitch sw = controller.getSwitch(dpid); + if (sw == null) { + log.error("Unknown device {}", deviceId); + meterOps.operations().forEach(op -> + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN_DEVICE) + ); + return; + } + + meterOps.operations().forEach(op -> performOperation(sw, op)); + } + + @Override + public void performMeterOperation(DeviceId deviceId, MeterOperation meterOp) { + Dpid dpid = Dpid.dpid(deviceId.uri()); + OpenFlowSwitch sw = controller.getSwitch(dpid); + if (sw == null) { + log.error("Unknown device {}", deviceId); + providerService.meterOperationFailed(meterOp, + MeterFailReason.UNKNOWN_DEVICE); + return; + } + + performOperation(sw, meterOp); + + } + + private void performOperation(OpenFlowSwitch sw, MeterOperation op) { + + pendingOperations.put(op.meter().id().id(), op); + + + Meter meter = op.meter(); + MeterModBuilder builder = MeterModBuilder.builder(meter.id().id(), sw.factory()); + if (meter.isBurst()) { + builder.burst(); + } + builder.withBands(meter.bands()) + .withId(meter.id()) + .withRateUnit(meter.unit()); + + switch (op.type()) { + case ADD: + sw.sendMsg(builder.add()); + break; + case REMOVE: + sw.sendMsg(builder.remove()); + break; + case MODIFY: + sw.sendMsg(builder.modify()); + break; + default: + log.warn("Unknown Meter command {}; not sending anything", + op.type()); + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN_COMMAND); + } + + } + + private void createStatsCollection(OpenFlowSwitch sw) { + if (isMeterSupported(sw)) { + MeterStatsCollector msc = new MeterStatsCollector(sw, POLL_INTERVAL); + msc.start(); + collectors.put(new Dpid(sw.getId()), msc); + } + } + + private boolean isMeterSupported(OpenFlowSwitch sw) { + if (sw.factory().getVersion() == OFVersion.OF_10 || + sw.factory().getVersion() == OFVersion.OF_11 || + sw.factory().getVersion() == OFVersion.OF_12) { + return false; + } + + return true; + } + + private void pushMeterStats(Dpid dpid, OFStatsReply msg) { + DeviceId deviceId = DeviceId.deviceId(Dpid.uri(dpid)); + + if (msg.getStatsType() == OFStatsType.METER) { + OFMeterStatsReply reply = (OFMeterStatsReply) msg; + Collection<Meter> meters = buildMeters(deviceId, reply.getEntries()); + //TODO do meter accounting here. + providerService.pushMeterMetrics(deviceId, meters); + } else if (msg.getStatsType() == OFStatsType.METER_CONFIG) { + OFMeterConfigStatsReply reply = (OFMeterConfigStatsReply) msg; + // FIXME: Map<Long, Meter> meters = collectMeters(deviceId, reply); + } + + } + + private Map<Long, Meter> collectMeters(DeviceId deviceId, + OFMeterConfigStatsReply reply) { + return Maps.newHashMap(); + //TODO: Needs a fix to be applied to loxi MeterConfig stat is incorrect + } + + private Collection<Meter> buildMeters(DeviceId deviceId, + List<OFMeterStats> entries) { + return entries.stream().map(stat -> { + DefaultMeter.Builder builder = DefaultMeter.builder(); + Collection<Band> bands = buildBands(stat.getBandStats()); + builder.forDevice(deviceId) + .withId(MeterId.meterId(stat.getMeterId())) + //FIXME: need to encode appId in meter id, but that makes + // things a little annoying for debugging + .fromApp(coreService.getAppId("org.onosproject.core")) + .withBands(bands); + DefaultMeter meter = builder.build(); + meter.setState(MeterState.ADDED); + meter.setLife(stat.getDurationSec()); + meter.setProcessedBytes(stat.getByteInCount().getValue()); + meter.setProcessedPackets(stat.getPacketInCount().getValue()); + meter.setReferenceCount(stat.getFlowCount()); + + // marks the meter as seen on the dataplane + pendingOperations.invalidate(stat.getMeterId()); + return meter; + }).collect(Collectors.toSet()); + } + + private Collection<Band> buildBands(List<OFMeterBandStats> bandStats) { + return bandStats.stream().map(stat -> { + DefaultBand band = DefaultBand.builder().build(); + band.setBytes(stat.getByteBandCount().getValue()); + band.setPackets(stat.getPacketBandCount().getValue()); + return band; + }).collect(Collectors.toSet()); + } + + private void signalMeterError(OFMeterModFailedErrorMsg meterError, + MeterOperation op) { + switch (meterError.getCode()) { + case UNKNOWN: + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN_DEVICE); + break; + case METER_EXISTS: + providerService.meterOperationFailed(op, + MeterFailReason.EXISTING_METER); + break; + case INVALID_METER: + providerService.meterOperationFailed(op, + MeterFailReason.INVALID_METER); + break; + case UNKNOWN_METER: + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN); + break; + case BAD_COMMAND: + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN_COMMAND); + break; + case BAD_FLAGS: + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN_FLAGS); + break; + case BAD_RATE: + providerService.meterOperationFailed(op, + MeterFailReason.BAD_RATE); + break; + case BAD_BURST: + providerService.meterOperationFailed(op, + MeterFailReason.BAD_BURST); + break; + case BAD_BAND: + providerService.meterOperationFailed(op, + MeterFailReason.BAD_BAND); + break; + case BAD_BAND_VALUE: + providerService.meterOperationFailed(op, + MeterFailReason.BAD_BAND_VALUE); + break; + case OUT_OF_METERS: + providerService.meterOperationFailed(op, + MeterFailReason.OUT_OF_METERS); + break; + case OUT_OF_BANDS: + providerService.meterOperationFailed(op, + MeterFailReason.OUT_OF_BANDS); + break; + default: + providerService.meterOperationFailed(op, + MeterFailReason.UNKNOWN); + } + } + + private class InternalMeterListener + implements OpenFlowSwitchListener, OpenFlowEventListener { + @Override + public void handleMessage(Dpid dpid, OFMessage msg) { + switch (msg.getType()) { + case STATS_REPLY: + pushMeterStats(dpid, (OFStatsReply) msg); + break; + case ERROR: + OFErrorMsg error = (OFErrorMsg) msg; + if (error.getErrType() == OFErrorType.METER_MOD_FAILED) { + MeterOperation op = + pendingOperations.getIfPresent(error.getXid()); + pendingOperations.invalidate(error.getXid()); + if (op == null) { + log.warn("Unknown Meter operation failed {}", error); + } else { + OFMeterModFailedErrorMsg meterError = + (OFMeterModFailedErrorMsg) error; + signalMeterError(meterError, op); + } + } + break; + default: + break; + } + + } + + @Override + public void switchAdded(Dpid dpid) { + createStatsCollection(controller.getSwitch(dpid)); + } + + @Override + public void switchRemoved(Dpid dpid) { + MeterStatsCollector msc = collectors.remove(dpid); + if (msc != null) { + msc.stop(); + } + } + + @Override + public void switchChanged(Dpid dpid) { + + } + + @Override + public void portChanged(Dpid dpid, OFPortStatus status) { + + } + + @Override + public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) { + + } + } + + + +} diff --git a/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/package-info.java b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/package-info.java new file mode 100644 index 00000000..9515712c --- /dev/null +++ b/framework/src/onos/providers/openflow/meter/src/main/java/org/onosproject/provider/of/meter/impl/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses OpenFlow controller as a means of device metering management. + */ +package org.onosproject.provider.of.meter.impl;
\ No newline at end of file diff --git a/framework/src/onos/providers/openflow/meter/src/test/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProviderTest.java b/framework/src/onos/providers/openflow/meter/src/test/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProviderTest.java new file mode 100644 index 00000000..0c5d5389 --- /dev/null +++ b/framework/src/onos/providers/openflow/meter/src/test/java/org/onosproject/provider/of/meter/impl/OpenFlowMeterProviderTest.java @@ -0,0 +1,22 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.meter.impl; + + +public class OpenFlowMeterProviderTest { + + +}
\ No newline at end of file diff --git a/framework/src/onos/providers/openflow/packet/pom.xml b/framework/src/onos/providers/openflow/packet/pom.xml new file mode 100644 index 00000000..f7f62d8e --- /dev/null +++ b/framework/src/onos/providers/openflow/packet/pom.xml @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-provider-packet</artifactId> + <packaging>bundle</packaging> + + <description>ONOS OpenFlow protocol packet provider</description> + +</project> diff --git a/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java b/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java new file mode 100644 index 00000000..6d153103 --- /dev/null +++ b/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowCorePacketContext.java @@ -0,0 +1,99 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.packet.impl; + +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Ethernet; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instruction.Type; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.packet.DefaultPacketContext; +import org.onosproject.net.packet.InboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.openflow.controller.OpenFlowPacketContext; +import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Packet context used with the OpenFlow providers. + */ +public class OpenFlowCorePacketContext extends DefaultPacketContext { + + private static final Logger log = LoggerFactory.getLogger(OpenFlowCorePacketContext.class); + + private final OpenFlowPacketContext ofPktCtx; + + /** + * Creates a new OpenFlow core packet context. + * + * @param time creation time + * @param inPkt inbound packet + * @param outPkt outbound packet + * @param block whether the context is blocked or not + * @param ofPktCtx OpenFlow packet context + */ + protected OpenFlowCorePacketContext(long time, InboundPacket inPkt, + OutboundPacket outPkt, boolean block, + OpenFlowPacketContext ofPktCtx) { + super(time, inPkt, outPkt, block); + this.ofPktCtx = ofPktCtx; + } + + @Override + public void send() { + if (!this.block()) { + if (outPacket() == null) { + sendPacket(null); + } else { + try { + Ethernet eth = Ethernet.deserializer() + .deserialize(outPacket().data().array(), 0, + outPacket().data().array().length); + sendPacket(eth); + } catch (DeserializationException e) { + log.warn("Unable to deserialize packet"); + } + } + } + } + + private void sendPacket(Ethernet eth) { + List<Instruction> ins = treatmentBuilder().build().allInstructions(); + OFPort p = null; + //TODO: support arbitrary list of treatments must be supported in ofPacketContext + for (Instruction i : ins) { + if (i.type() == Type.OUTPUT) { + p = buildPort(((OutputInstruction) i).port()); + break; //for now... + } + } + if (eth == null) { + ofPktCtx.build(p); + } else { + ofPktCtx.build(eth, p); + } + ofPktCtx.send(); + } + + private OFPort buildPort(PortNumber port) { + return OFPort.of((int) port.toLong()); + } + +} diff --git a/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java b/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java new file mode 100644 index 00000000..dc79feff --- /dev/null +++ b/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProvider.java @@ -0,0 +1,176 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.packet.impl; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; +import org.onosproject.net.packet.DefaultInboundPacket; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketProvider; +import org.onosproject.net.packet.PacketProviderRegistry; +import org.onosproject.net.packet.PacketProviderService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowPacketContext; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.PacketListener; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.ver10.OFFactoryVer10; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; + +import java.nio.ByteBuffer; +import java.util.Collections; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Provider which uses an OpenFlow controller to detect network + * infrastructure links. + */ +@Component(immediate = true) +public class OpenFlowPacketProvider extends AbstractProvider implements PacketProvider { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PacketProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OpenFlowController controller; + + private PacketProviderService providerService; + + private final InternalPacketProvider listener = new InternalPacketProvider(); + + /** + * Creates an OpenFlow link provider. + */ + public OpenFlowPacketProvider() { + super(new ProviderId("of", "org.onosproject.provider.openflow")); + } + + @Activate + public void activate() { + providerService = providerRegistry.register(this); + controller.addPacketListener(20, listener); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + providerRegistry.unregister(this); + controller.removePacketListener(listener); + providerService = null; + log.info("Stopped"); + } + + @Override + public void emit(OutboundPacket packet) { + DeviceId devId = packet.sendThrough(); + String scheme = devId.toString().split(":")[0]; + + if (!scheme.equals(this.id().scheme())) { + throw new IllegalArgumentException( + "Don't know how to handle Device with scheme " + scheme); + } + + Dpid dpid = Dpid.dpid(devId.uri()); + OpenFlowSwitch sw = controller.getSwitch(dpid); + if (sw == null) { + log.warn("Device {} isn't available?", devId); + return; + } + + //Ethernet eth = new Ethernet(); + //eth.deserialize(packet.data().array(), 0, packet.data().array().length); + OFPortDesc p = null; + for (Instruction inst : packet.treatment().allInstructions()) { + if (inst.type().equals(Instruction.Type.OUTPUT)) { + p = portDesc(((OutputInstruction) inst).port()); + OFPacketOut po = packetOut(sw, packet.data().array(), p.getPortNo()); + sw.sendMsg(po); + } + } + + } + + private OFPortDesc portDesc(PortNumber port) { + OFPortDesc.Builder builder = OFFactoryVer10.INSTANCE.buildPortDesc(); + builder.setPortNo(OFPort.of((int) port.toLong())); + + return builder.build(); + } + + private OFPacketOut packetOut(OpenFlowSwitch sw, byte[] eth, OFPort out) { + OFPacketOut.Builder builder = sw.factory().buildPacketOut(); + OFAction act = sw.factory().actions() + .buildOutput() + .setPort(out) + .build(); + return builder + .setBufferId(OFBufferId.NO_BUFFER) + .setInPort(OFPort.CONTROLLER) + .setActions(Collections.singletonList(act)) + .setData(eth) + .build(); + } + + /** + * Internal Packet Provider implementation. + * + */ + private class InternalPacketProvider implements PacketListener { + + @Override + public void handlePacket(OpenFlowPacketContext pktCtx) { + DeviceId id = DeviceId.deviceId(Dpid.uri(pktCtx.dpid().value())); + + DefaultInboundPacket inPkt = new DefaultInboundPacket( + new ConnectPoint(id, PortNumber.portNumber(pktCtx.inPort())), + pktCtx.parsed(), ByteBuffer.wrap(pktCtx.unparsed())); + + DefaultOutboundPacket outPkt = null; + if (!pktCtx.isBuffered()) { + outPkt = new DefaultOutboundPacket(id, null, + ByteBuffer.wrap(pktCtx.unparsed())); + } + + OpenFlowCorePacketContext corePktCtx = + new OpenFlowCorePacketContext(System.currentTimeMillis(), + inPkt, outPkt, pktCtx.isHandled(), pktCtx); + providerService.processPacket(corePktCtx); + } + + } + + +} diff --git a/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/package-info.java b/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/package-info.java new file mode 100644 index 00000000..dd1130c0 --- /dev/null +++ b/framework/src/onos/providers/openflow/packet/src/main/java/org/onosproject/provider/of/packet/impl/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Provider that uses OpenFlow controller as a means of intercepting and + * emitting packets. + */ +package org.onosproject.provider.of.packet.impl; diff --git a/framework/src/onos/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java b/framework/src/onos/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java new file mode 100644 index 00000000..5fded926 --- /dev/null +++ b/framework/src/onos/providers/openflow/packet/src/test/java/org/onosproject/provider/of/packet/impl/OpenFlowPacketProviderTest.java @@ -0,0 +1,431 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.of.packet.impl; + +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.ARP; +import org.onlab.packet.Ethernet; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.packet.DefaultOutboundPacket; +import org.onosproject.net.packet.OutboundPacket; +import org.onosproject.net.packet.PacketContext; +import org.onosproject.net.packet.PacketProvider; +import org.onosproject.net.packet.PacketProviderRegistry; +import org.onosproject.net.packet.PacketProviderService; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.openflow.controller.DefaultOpenFlowPacketContext; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowController; +import org.onosproject.openflow.controller.OpenFlowEventListener; +import org.onosproject.openflow.controller.OpenFlowPacketContext; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.OpenFlowSwitchListener; +import org.onosproject.openflow.controller.PacketListener; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketInReason; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.ver10.OFFactoryVer10; +import org.projectfloodlight.openflow.types.MacAddress; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.*; + + +public class OpenFlowPacketProviderTest { + + private static final int PN1 = 100; + private static final int PN2 = 200; + private static final int PN3 = 300; + private static final short VLANID = (short) 100; + + private static final DeviceId DID = DeviceId.deviceId("of:1"); + private static final DeviceId DID_MISSING = DeviceId.deviceId("of:2"); + private static final DeviceId DID_WRONG = DeviceId.deviceId("test:1"); + private static final PortNumber P1 = PortNumber.portNumber(PN1); + private static final PortNumber P2 = PortNumber.portNumber(PN2); + private static final PortNumber P3 = PortNumber.portNumber(PN3); + + private static final Instruction INST1 = Instructions.createOutput(P1); + private static final Instruction INST2 = Instructions.createOutput(P2); + private static final Instruction INST3 = Instructions.createOutput(P3); + + private static final OFPortDesc PD1 = portDesc(PN1); + private static final OFPortDesc PD2 = portDesc(PN2); + + private static final List<OFPortDesc> PLIST = Lists.newArrayList(PD1, PD2); + private static final TrafficTreatment TR = treatment(INST1, INST2); + private static final TrafficTreatment TR_MISSING = treatment(INST1, INST3); + + private static final byte[] ANY = new byte [] {0, 0, 0, 0}; + + private final OpenFlowPacketProvider provider = new OpenFlowPacketProvider(); + private final TestPacketRegistry registry = new TestPacketRegistry(); + private final TestController controller = new TestController(); + + private final TestOpenFlowSwitch sw = new TestOpenFlowSwitch(); + + @Before + public void startUp() { + provider.providerRegistry = registry; + provider.controller = controller; + provider.activate(); + assertNotNull("listener should be registered", registry.listener); + } + + @After + public void teardown() { + provider.deactivate(); + assertNull("listeners shouldn't be registered", registry.listener); + provider.controller = null; + provider.providerRegistry = null; + } + + @Test(expected = IllegalArgumentException.class) + public void wrongScheme() { + sw.setRole(RoleState.MASTER); + OutboundPacket schemeFailPkt = outPacket(DID_WRONG, TR, null); + provider.emit(schemeFailPkt); + assertEquals("message sent incorrectly", 0, sw.sent.size()); + } + + @Test + public void emit() { + + MacAddress mac1 = MacAddress.of("00:00:00:11:00:01"); + MacAddress mac2 = MacAddress.of("00:00:00:22:00:02"); + + ARP arp = new ARP(); + arp.setSenderProtocolAddress(ANY) + .setSenderHardwareAddress(mac1.getBytes()) + .setTargetHardwareAddress(mac2.getBytes()) + .setTargetProtocolAddress(ANY) + .setHardwareType((short) 0) + .setProtocolType((short) 0) + .setHardwareAddressLength((byte) 6) + .setProtocolAddressLength((byte) 4) + .setOpCode((byte) 0); + + Ethernet eth = new Ethernet(); + eth.setVlanID(VLANID) + .setEtherType(Ethernet.TYPE_ARP) + .setSourceMACAddress("00:00:00:11:00:01") + .setDestinationMACAddress("00:00:00:22:00:02") + .setPayload(arp); + + //the should-be working setup. + OutboundPacket passPkt = outPacket(DID, TR, eth); + sw.setRole(RoleState.MASTER); + provider.emit(passPkt); + assertEquals("invalid switch", sw, controller.current); + assertEquals("message not sent", PLIST.size(), sw.sent.size()); + sw.sent.clear(); + + //wrong Role + //sw.setRole(RoleState.SLAVE); + //provider.emit(passPkt); + //assertEquals("invalid switch", sw, controller.current); + //assertEquals("message sent incorrectly", 0, sw.sent.size()); + + //sw.setRole(RoleState.MASTER); + + //missing switch + OutboundPacket swFailPkt = outPacket(DID_MISSING, TR, eth); + provider.emit(swFailPkt); + assertNull("invalid switch", controller.current); + assertEquals("message sent incorrectly", 0, sw.sent.size()); + + //to missing port + //OutboundPacket portFailPkt = outPacket(DID, TR_MISSING, eth); + //provider.emit(portFailPkt); + //assertEquals("extra message sent", 1, sw.sent.size()); + + } + + @Test + public void handlePacket() { + OFPacketIn pkt = sw.factory().buildPacketIn() + .setBufferId(OFBufferId.NO_BUFFER) + .setInPort(OFPort.NO_MASK) + .setReason(OFPacketInReason.INVALID_TTL) + .build(); + + controller.processPacket(null, pkt); + assertNotNull("message unprocessed", registry.ctx); + + } + + private static OFPortDesc portDesc(int port) { + OFPortDesc.Builder builder = OFFactoryVer10.INSTANCE.buildPortDesc(); + builder.setPortNo(OFPort.of(port)); + + return builder.build(); + } + + private static TrafficTreatment treatment(Instruction ... insts) { + TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); + for (Instruction i : insts) { + builder.add(i); + } + return builder.build(); + } + + private static OutboundPacket outPacket( + DeviceId d, TrafficTreatment t, Ethernet e) { + ByteBuffer buf = null; + if (e != null) { + buf = ByteBuffer.wrap(e.serialize()); + } + return new DefaultOutboundPacket(d, t, buf); + } + + private class TestPacketRegistry implements PacketProviderRegistry { + + PacketProvider listener = null; + PacketContext ctx = null; + + @Override + public PacketProviderService register(PacketProvider provider) { + listener = provider; + return new TestPacketProviderService(); + } + + @Override + public void unregister(PacketProvider provider) { + listener = null; + } + + @Override + public Set<ProviderId> getProviders() { + return Sets.newHashSet(listener.id()); + } + + private class TestPacketProviderService implements PacketProviderService { + + @Override + public PacketProvider provider() { + return null; + } + + @Override + public void processPacket(PacketContext context) { + ctx = context; + } + + } + } + + private class TestController implements OpenFlowController { + + PacketListener pktListener; + OpenFlowSwitch current; + + @Override + public Iterable<OpenFlowSwitch> getSwitches() { + return null; + } + + @Override + public Iterable<OpenFlowSwitch> getMasterSwitches() { + return null; + } + + @Override + public Iterable<OpenFlowSwitch> getEqualSwitches() { + return null; + } + + @Override + public OpenFlowSwitch getSwitch(Dpid dpid) { + if (dpid.equals(Dpid.dpid(DID.uri()))) { + current = sw; + } else { + current = null; + } + return current; + } + + @Override + public OpenFlowSwitch getMasterSwitch(Dpid dpid) { + return null; + } + + @Override + public OpenFlowSwitch getEqualSwitch(Dpid dpid) { + return null; + } + + @Override + public void addListener(OpenFlowSwitchListener listener) { + } + + @Override + public void removeListener(OpenFlowSwitchListener listener) { + } + + @Override + public void addPacketListener(int priority, PacketListener listener) { + pktListener = listener; + } + + @Override + public void removePacketListener(PacketListener listener) { + } + + @Override + public void addEventListener(OpenFlowEventListener listener) { + } + + @Override + public void removeEventListener(OpenFlowEventListener listener) { + } + + @Override + public void write(Dpid dpid, OFMessage msg) { + } + + @Override + public void processPacket(Dpid dpid, OFMessage msg) { + OpenFlowPacketContext pktCtx = + DefaultOpenFlowPacketContext. + packetContextFromPacketIn(sw, (OFPacketIn) msg); + pktListener.handlePacket(pktCtx); + } + + @Override + public void setRole(Dpid dpid, RoleState role) { + } + + } + + private class TestOpenFlowSwitch implements OpenFlowSwitch { + + RoleState state; + List<OFMessage> sent = new ArrayList<OFMessage>(); + OFFactory factory = OFFactoryVer10.INSTANCE; + + @Override + public void sendMsg(OFMessage msg) { + sent.add(msg); + } + + @Override + public void sendMsg(List<OFMessage> msgs) { + } + + @Override + public void handleMessage(OFMessage fromSwitch) { + } + + @Override + public void setRole(RoleState role) { + state = role; + } + + @Override + public RoleState getRole() { + return state; + } + + @Override + public List<OFPortDesc> getPorts() { + return PLIST; + } + + @Override + public OFFactory factory() { + return factory; + } + + @Override + public String getStringId() { + return null; + } + + @Override + public long getId() { + return 0; + } + + @Override + public String manufacturerDescription() { + return null; + } + + @Override + public String datapathDescription() { + return null; + } + + @Override + public String hardwareDescription() { + return null; + } + + @Override + public String softwareDescription() { + return null; + } + + @Override + public String serialNumber() { + return null; + } + + @Override + public boolean isConnected() { + return true; + } + + @Override + public void disconnectSwitch() { + } + + @Override + public void returnRoleReply(RoleState requested, RoleState reponse) { + } + @Override + public Device.Type deviceType() { + return Device.Type.SWITCH; + } + + @Override + public String channelId() { + return "1.2.3.4:1"; + } + + + } + +} diff --git a/framework/src/onos/providers/openflow/pom.xml b/framework/src/onos/providers/openflow/pom.xml new file mode 100644 index 00000000..99ff6649 --- /dev/null +++ b/framework/src/onos/providers/openflow/pom.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-of-providers</artifactId> + <packaging>pom</packaging> + + <description>ONOS OpenFlow protocol adapters</description> + + <modules> + <module>device</module> + <module>packet</module> + <module>flow</module> + <module>group</module> + <module>meter</module> + <module>app</module> + </modules> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-api</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-of-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/ovsdb/app/app.xml b/framework/src/onos/providers/ovsdb/app/app.xml new file mode 100644 index 00000000..c3334994 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/app/app.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<app name="org.onosproject.ovsdb" origin="ON.Lab" version="${project.version}" + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" + features="${project.artifactId}"> + <description>${project.description}</description> + + <artifact>mvn:${project.groupId}/onos-ovsdb-rfc/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-ovsdb-api/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-ovsdb-ctl/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-drivers/${project.version}</artifact> + + <artifact>mvn:${project.groupId}/onos-ovsdb-provider-device/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-ovsdb-provider-host/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-ovsdb-provider-tunnel/${project.version}</artifact> +</app> diff --git a/framework/src/onos/providers/ovsdb/app/features.xml b/framework/src/onos/providers/ovsdb/app/features.xml new file mode 100644 index 00000000..19fab4cd --- /dev/null +++ b/framework/src/onos/providers/ovsdb/app/features.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> + <feature name="${project.artifactId}" version="${project.version}" + description="${project.description}"> + <feature>onos-api</feature> + <bundle>mvn:io.netty/netty-common/4.0.23.Final</bundle> + <bundle>mvn:io.netty/netty-buffer/4.0.23.Final</bundle> + <bundle>mvn:io.netty/netty-transport/4.0.23.Final</bundle> + <bundle>mvn:io.netty/netty-handler/4.0.23.Final</bundle> + <bundle>mvn:io.netty/netty-codec/4.0.23.Final</bundle> + <bundle>mvn:${project.groupId}/onos-ovsdb-rfc/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-ovsdb-api/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-ovsdb-ctl/${project.version}</bundle> + + <bundle>mvn:${project.groupId}/onos-ovsdb-provider-device/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-ovsdb-provider-host/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-ovsdb-provider-tunnel/${project.version}</bundle> + </feature> +</features> diff --git a/framework/src/onos/providers/ovsdb/app/pom.xml b/framework/src/onos/providers/ovsdb/app/pom.xml new file mode 100644 index 00000000..7bb506ba --- /dev/null +++ b/framework/src/onos/providers/ovsdb/app/pom.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-ovsdatabase</artifactId> + <packaging>pom</packaging> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-rfc</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-ctl</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-drivers</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-provider-device</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-provider-host</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-provider-tunnel</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/ovsdb/device/pom.xml b/framework/src/onos/providers/ovsdb/device/pom.xml new file mode 100644 index 00000000..e5010fa6 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/device/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + </parent> + + <artifactId>onos-ovsdb-provider-device</artifactId> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/ovsdb/device/src/main/java/org/onosproject/ovsdb/providers/device/OvsdbDeviceProvider.java b/framework/src/onos/providers/ovsdb/device/src/main/java/org/onosproject/ovsdb/providers/device/OvsdbDeviceProvider.java new file mode 100644 index 00000000..10e745e3 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/device/src/main/java/org/onosproject/ovsdb/providers/device/OvsdbDeviceProvider.java @@ -0,0 +1,134 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.ovsdb.providers.device; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +import java.net.URI; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.ChassisId; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbNodeId; +import org.onosproject.ovsdb.controller.OvsdbNodeListener; +import org.slf4j.Logger; + +/** + * Provider which uses an ovsdb controller to detect device. + */ +@Component(immediate = true) +@Service +public class OvsdbDeviceProvider extends AbstractProvider + implements DeviceProvider { + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected OvsdbController controller; + + private DeviceProviderService providerService; + private OvsdbNodeListener innerNodeListener = new InnerOvsdbNodeListener(); + protected static final String ISNOTNULL = "OvsdbNodeId is not null"; + private static final String UNKNOWN = "unknown"; + + @Activate + public void activate() { + providerService = providerRegistry.register(this); + controller.addNodeListener(innerNodeListener); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + providerRegistry.unregister(this); + providerService = null; + log.info("Stopped"); + } + + public OvsdbDeviceProvider() { + super(new ProviderId("ovsdb", "org.onosproject.ovsdb.provider.device")); + } + + @Override + public void triggerProbe(DeviceId deviceId) { + // TODO: This will be implemented later. + log.info("Triggering probe on device {}", deviceId); + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + // TODO: This will be implemented later. + } + + @Override + public boolean isReachable(DeviceId deviceId) { + return true; + } + + private class InnerOvsdbNodeListener implements OvsdbNodeListener { + + @Override + public void nodeAdded(OvsdbNodeId nodeId) { + checkNotNull(nodeId, ISNOTNULL); + DeviceId deviceId = DeviceId.deviceId(nodeId.toString()); + URI uri = URI.create(nodeId.toString()); + ChassisId cid = new ChassisId(); + String ipAddress = nodeId.getIpAddress(); + SparseAnnotations annotations = DefaultAnnotations.builder() + .set("ipaddress", ipAddress).build(); + DeviceDescription deviceDescription = new DefaultDeviceDescription( + uri, + Device.Type.CONTROLLER, + UNKNOWN, UNKNOWN, + UNKNOWN, UNKNOWN, + cid, + annotations); + providerService.deviceConnected(deviceId, deviceDescription); + + } + + @Override + public void nodeRemoved(OvsdbNodeId nodeId) { + checkNotNull(nodeId, ISNOTNULL); + DeviceId deviceId = DeviceId.deviceId(nodeId.toString()); + providerService.deviceDisconnected(deviceId); + + } + } +} diff --git a/framework/src/onos/providers/ovsdb/device/src/main/java/org/onosproject/ovsdb/providers/device/package-info.java b/framework/src/onos/providers/ovsdb/device/src/main/java/org/onosproject/ovsdb/providers/device/package-info.java new file mode 100644 index 00000000..1636371a --- /dev/null +++ b/framework/src/onos/providers/ovsdb/device/src/main/java/org/onosproject/ovsdb/providers/device/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + *Provider that uses ovsdb controller as a means of infrastructure device discovery. + * + */ +package org.onosproject.ovsdb.providers.device;
\ No newline at end of file diff --git a/framework/src/onos/providers/ovsdb/device/src/test/java/org/onosproject/ovsdb/providers/device/OvsdbDeviceProviderTest.java b/framework/src/onos/providers/ovsdb/device/src/test/java/org/onosproject/ovsdb/providers/device/OvsdbDeviceProviderTest.java new file mode 100644 index 00000000..5e4c5677 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/device/src/test/java/org/onosproject/ovsdb/providers/device/OvsdbDeviceProviderTest.java @@ -0,0 +1,198 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.ovsdb.providers.device; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.device.PortStatistics; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.ovsdb.controller.OvsdbClientService; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbEventListener; +import org.onosproject.ovsdb.controller.OvsdbNodeId; +import org.onosproject.ovsdb.controller.OvsdbNodeListener; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +/** + * Test for ovsdb device provider. + */ +public class OvsdbDeviceProviderTest { + private final OvsdbDeviceProvider provider = new OvsdbDeviceProvider(); + private final TestDeviceRegistry registry = new TestDeviceRegistry(); + private final TestController controller = new TestController(); + + @Before + public void startUp() { + provider.providerRegistry = registry; + provider.controller = controller; + provider.activate(); + assertNotNull("provider should be registered", registry.provider); + } + + @After + public void tearDown() { + provider.deactivate(); + provider.controller = null; + provider.providerRegistry = null; + } + + @Test + public void triggerProbe() { + + } + + @Test + public void testNodeAdded() { + controller.listener.nodeAdded(new OvsdbNodeId(IpAddress + .valueOf("192.168.202.36"), 5000)); + assertEquals("ovsdb node added", 1, registry.connected.size()); + } + + @Test + public void testNodeRemoved() { + controller.listener.nodeAdded(new OvsdbNodeId(IpAddress + .valueOf("192.168.202.36"), 5000)); + controller.listener.nodeRemoved(new OvsdbNodeId(IpAddress + .valueOf("192.168.202.36"), 5000)); + assertEquals("ovsdb node removded", 0, registry.connected.size()); + } + + private class TestDeviceRegistry implements DeviceProviderRegistry { + DeviceProvider provider; + + Set<DeviceId> connected = new HashSet<>(); + Multimap<DeviceId, PortDescription> ports = HashMultimap.create(); + PortDescription descr = null; + + @Override + public DeviceProviderService register(DeviceProvider provider) { + this.provider = provider; + return new TestProviderService(); + } + + @Override + public void unregister(DeviceProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + private class TestProviderService implements DeviceProviderService { + + @Override + public DeviceProvider provider() { + return null; + } + + @Override + public void deviceConnected(DeviceId deviceId, + DeviceDescription deviceDescription) { + connected.add(deviceId); + } + + @Override + public void deviceDisconnected(DeviceId deviceId) { + connected.remove(deviceId); + ports.removeAll(deviceId); + } + + @Override + public void updatePorts(DeviceId deviceId, + List<PortDescription> portDescriptions) { + for (PortDescription p : portDescriptions) { + ports.put(deviceId, p); + } + } + + @Override + public void portStatusChanged(DeviceId deviceId, + PortDescription portDescription) { + ports.put(deviceId, portDescription); + descr = portDescription; + } + + @Override + public void receivedRoleReply(DeviceId deviceId, + MastershipRole requested, + MastershipRole response) { + } + + @Override + public void updatePortStatistics(DeviceId deviceId, + Collection<PortStatistics> portStatistics) { + + } + + } + } + + private class TestController implements OvsdbController { + OvsdbNodeListener listener = null; + + @Override + public void addNodeListener(OvsdbNodeListener listener) { + this.listener = listener; + } + + @Override + public void removeNodeListener(OvsdbNodeListener listener) { + this.listener = null; + } + + @Override + public void addOvsdbEventListener(OvsdbEventListener listener) { + + } + + @Override + public void removeOvsdbEventListener(OvsdbEventListener listener) { + + } + + @Override + public List<OvsdbNodeId> getNodeIds() { + return null; + } + + @Override + public OvsdbClientService getOvsdbClient(OvsdbNodeId nodeId) { + return null; + } + + } + +} diff --git a/framework/src/onos/providers/ovsdb/host/pom.xml b/framework/src/onos/providers/ovsdb/host/pom.xml new file mode 100644 index 00000000..0bd88555 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/host/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + </parent> + + <artifactId>onos-ovsdb-provider-host</artifactId> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-api</artifactId> + <version>${project.version}</version> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/ovsdb/host/src/main/java/org/onosproject/ovsdb/provider/host/OvsdbHostProvider.java b/framework/src/onos/providers/ovsdb/host/src/main/java/org/onosproject/ovsdb/provider/host/OvsdbHostProvider.java new file mode 100644 index 00000000..8d2fd142 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/host/src/main/java/org/onosproject/ovsdb/provider/host/OvsdbHostProvider.java @@ -0,0 +1,148 @@ +/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.ovsdb.provider.host;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.onlab.util.Tools.toHex;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.CoreService;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.SparseAnnotations;
+import org.onosproject.net.host.DefaultHostDescription;
+import org.onosproject.net.host.HostDescription;
+import org.onosproject.net.host.HostProvider;
+import org.onosproject.net.host.HostProviderRegistry;
+import org.onosproject.net.host.HostProviderService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.provider.AbstractProvider;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.ovsdb.controller.EventSubject;
+import org.onosproject.ovsdb.controller.OvsdbController;
+import org.onosproject.ovsdb.controller.OvsdbEvent;
+import org.onosproject.ovsdb.controller.OvsdbEventListener;
+import org.onosproject.ovsdb.controller.OvsdbEventSubject;
+import org.slf4j.Logger;
+
+/**
+ * Provider which uses an ovsdb controller to detect host.
+ */
+@Component(immediate = true)
+@Service
+public class OvsdbHostProvider extends AbstractProvider implements HostProvider {
+ private final Logger log = getLogger(getClass());
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostProviderRegistry providerRegistry;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected HostService hostService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected CoreService coreService;
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected OvsdbController controller;
+
+ private HostProviderService providerService;
+ private OvsdbEventListener innerEventListener = new InnerOvsdbEventListener();
+
+ @Activate
+ public void activate() {
+ providerService = providerRegistry.register(this);
+ controller.addOvsdbEventListener(innerEventListener);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ providerRegistry.unregister(this);
+ providerService = null;
+ log.info("Stopped");
+ }
+
+ public OvsdbHostProvider() {
+ super(new ProviderId("ovsdb", "org.onosproject.ovsdb.provider.host"));
+ }
+
+ @Override
+ public void triggerProbe(Host host) {
+ log.info("Triggering probe on host {}", host);
+ }
+
+ private class InnerOvsdbEventListener implements OvsdbEventListener {
+
+ @Override
+ public void handle(OvsdbEvent<EventSubject> event) {
+ OvsdbEventSubject subject = null;
+ if (event.subject() instanceof OvsdbEventSubject) {
+ subject = (OvsdbEventSubject) event.subject();
+ }
+ checkNotNull(subject, "EventSubject is not null");
+ // If ifaceid is null,it indicates this is not a vm port.
+ if (subject.ifaceid() == null) {
+ return;
+ }
+ switch (event.type()) {
+ case PORT_ADDED:
+ HostId hostId = HostId.hostId(subject.hwAddress(), null);
+ DeviceId deviceId = DeviceId.deviceId(uri(subject.dpid().value()));
+ PortNumber portNumber = PortNumber.portNumber(subject
+ .portNumber().value(), subject.portName().value());
+ HostLocation loaction = new HostLocation(deviceId, portNumber,
+ 0L);
+ SparseAnnotations annotations = DefaultAnnotations.builder()
+ .set("ifaceid", subject.ifaceid().value()).build();
+ HostDescription hostDescription = new DefaultHostDescription(
+ subject.hwAddress(),
+ VlanId.vlanId(),
+ loaction,
+ annotations);
+ providerService.hostDetected(hostId, hostDescription);
+ break;
+ case PORT_REMOVED:
+ HostId host = HostId.hostId(subject.hwAddress(), null);
+ providerService.hostVanished(host);
+ break;
+ default:
+ break;
+ }
+
+ }
+
+ }
+
+ public URI uri(String value) {
+ try {
+ return new URI("of", toHex(Long.valueOf(value)), null);
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+}
diff --git a/framework/src/onos/providers/ovsdb/host/src/main/java/org/onosproject/ovsdb/provider/host/package-info.java b/framework/src/onos/providers/ovsdb/host/src/main/java/org/onosproject/ovsdb/provider/host/package-info.java new file mode 100644 index 00000000..895ae2ec --- /dev/null +++ b/framework/src/onos/providers/ovsdb/host/src/main/java/org/onosproject/ovsdb/provider/host/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + *Provider that uses ovsdb controller as a means of infrastructure host discovery. + * + */ +package org.onosproject.ovsdb.provider.host;
\ No newline at end of file diff --git a/framework/src/onos/providers/ovsdb/host/src/test/java/org/onosproject/ovsdb/provider/host/OvsdbHostProviderTest.java b/framework/src/onos/providers/ovsdb/host/src/test/java/org/onosproject/ovsdb/provider/host/OvsdbHostProviderTest.java new file mode 100644 index 00000000..bf18606c --- /dev/null +++ b/framework/src/onos/providers/ovsdb/host/src/test/java/org/onosproject/ovsdb/provider/host/OvsdbHostProviderTest.java @@ -0,0 +1,199 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.ovsdb.provider.host; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.MacAddress; +import org.onosproject.net.DeviceId; +import org.onosproject.net.HostId; +import org.onosproject.net.host.HostDescription; +import org.onosproject.net.host.HostProvider; +import org.onosproject.net.host.HostProviderRegistry; +import org.onosproject.net.host.HostProviderService; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.ovsdb.controller.DefaultEventSubject; +import org.onosproject.ovsdb.controller.EventSubject; +import org.onosproject.ovsdb.controller.OvsdbClientService; +import org.onosproject.ovsdb.controller.OvsdbController; +import org.onosproject.ovsdb.controller.OvsdbDatapathId; +import org.onosproject.ovsdb.controller.OvsdbEvent; +import org.onosproject.ovsdb.controller.OvsdbEventListener; +import org.onosproject.ovsdb.controller.OvsdbIfaceId; +import org.onosproject.ovsdb.controller.OvsdbNodeId; +import org.onosproject.ovsdb.controller.OvsdbNodeListener; +import org.onosproject.ovsdb.controller.OvsdbPortName; +import org.onosproject.ovsdb.controller.OvsdbPortNumber; +import org.onosproject.ovsdb.controller.OvsdbPortType; + +/** + * Test for ovsdb host provider. + */ +public class OvsdbHostProviderTest { + private static final MacAddress MAC = MacAddress + .valueOf("00:00:11:00:00:01"); + private final OvsdbHostProvider provider = new OvsdbHostProvider(); + private final TestHostRegistry hostRegistry = new TestHostRegistry(); + protected OvsdbControllerTest controller = new OvsdbControllerTest(); + private TestHostProviderService providerService; + + @Before + public void setUp() { + provider.providerRegistry = hostRegistry; + provider.controller = controller; + provider.activate(); + } + + @Test + public void basics() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + } + + @Test + public void portAdded() { + DefaultEventSubject eventSubject = new DefaultEventSubject(MAC, null, + new OvsdbPortName("portName"), + new OvsdbPortNumber(0L), + new OvsdbDatapathId("10002"), + new OvsdbPortType("vxlan"), + new OvsdbIfaceId("102345")); + controller.ovsdbEventListener + .handle(new OvsdbEvent<EventSubject>( + OvsdbEvent.Type.PORT_ADDED, + eventSubject)); + assertNotNull("never went throught the provider service", + providerService.added); + + } + + @Test + public void portRemoved() { + DefaultEventSubject eventSubject = new DefaultEventSubject(MAC, null, + new OvsdbPortName("portName"), + new OvsdbPortNumber(0L), + new OvsdbDatapathId("10002"), + new OvsdbPortType("vxlan"), + new OvsdbIfaceId("102345")); + controller.ovsdbEventListener + .handle(new OvsdbEvent<EventSubject>( + OvsdbEvent.Type.PORT_REMOVED, + eventSubject)); + assertEquals("port status unhandled", 1, providerService.removeCount); + } + + @After + public void tearDown() { + provider.deactivate(); + provider.coreService = null; + provider.providerRegistry = null; + } + + private class TestHostRegistry implements HostProviderRegistry { + + @Override + public HostProviderService register(HostProvider provider) { + providerService = new TestHostProviderService(provider); + return providerService; + } + + @Override + public void unregister(HostProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + } + + private class TestHostProviderService + extends AbstractProviderService<HostProvider> + implements HostProviderService { + + DeviceId added = null; + DeviceId moved = null; + DeviceId spine = null; + public int removeCount; + + protected TestHostProviderService(HostProvider provider) { + super(provider); + } + + @Override + public void hostDetected(HostId hostId, HostDescription hostDescription) { + DeviceId descr = hostDescription.location().deviceId(); + if (added == null) { + added = descr; + } else if ((moved == null) && !descr.equals(added)) { + moved = descr; + } else { + spine = descr; + } + } + + @Override + public void hostVanished(HostId hostId) { + removeCount++; + } + + } + + private class OvsdbControllerTest implements OvsdbController { + private OvsdbEventListener ovsdbEventListener = null; + + @Override + public void addNodeListener(OvsdbNodeListener listener) { + + } + + @Override + public void removeNodeListener(OvsdbNodeListener listener) { + + } + + @Override + public void addOvsdbEventListener(OvsdbEventListener listener) { + ovsdbEventListener = listener; + + } + + @Override + public void removeOvsdbEventListener(OvsdbEventListener listener) { + ovsdbEventListener = null; + + } + + @Override + public List<OvsdbNodeId> getNodeIds() { + return null; + } + + @Override + public OvsdbClientService getOvsdbClient(OvsdbNodeId nodeId) { + return null; + } + } +} diff --git a/framework/src/onos/providers/ovsdb/pom.xml b/framework/src/onos/providers/ovsdb/pom.xml new file mode 100644 index 00000000..bf215bc0 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/pom.xml @@ -0,0 +1,41 @@ +<?xml version="1.0"?> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + </parent> + + <artifactId>onos-ovsdb-providers</artifactId> + <packaging>pom</packaging> + + <modules> + <module>device</module> + <module>host</module> + <module>tunnel</module> + <module>app</module> + </modules> + + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/ovsdb/tunnel/pom.xml b/framework/src/onos/providers/ovsdb/tunnel/pom.xml new file mode 100644 index 00000000..46ea585b --- /dev/null +++ b/framework/src/onos/providers/ovsdb/tunnel/pom.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-ovsdb-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + </parent> + + <artifactId>onos-ovsdb-provider-tunnel</artifactId> + <packaging>bundle</packaging> + + <dependencies> + <dependency> + <groupId>org.easymock</groupId> + <artifactId>easymock</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + +</project> diff --git a/framework/src/onos/providers/ovsdb/tunnel/src/main/java/org/onosproject/ovsdb/provider/tunnel/OvsdbTunnelProvider.java b/framework/src/onos/providers/ovsdb/tunnel/src/main/java/org/onosproject/ovsdb/provider/tunnel/OvsdbTunnelProvider.java new file mode 100644 index 00000000..244e6fe0 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/tunnel/src/main/java/org/onosproject/ovsdb/provider/tunnel/OvsdbTunnelProvider.java @@ -0,0 +1,123 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.ovsdb.provider.tunnel; + +import static org.slf4j.LoggerFactory.getLogger; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelDescription; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelProvider; +import org.onosproject.incubator.net.tunnel.TunnelProviderRegistry; +import org.onosproject.incubator.net.tunnel.TunnelProviderService; +import org.onosproject.incubator.net.tunnel.TunnelService; +import org.onosproject.net.ElementId; +import org.onosproject.net.Path; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.slf4j.Logger; + +/** + * Provider which uses when tunnel added/removed. + */ +@Component(immediate = true) +@Service +public class OvsdbTunnelProvider extends AbstractProvider + implements TunnelProvider { + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TunnelProviderRegistry providerRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TunnelService tunnelService; + + private TunnelProviderService providerService; + + @Activate + public void activate() { + providerService = providerRegistry.register(this); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + providerRegistry.unregister(this); + providerService = null; + log.info("Stopped"); + } + + public OvsdbTunnelProvider() { + super(new ProviderId("ovsdb", "org.onosproject.ovsdb.provider.tunnel")); + } + + @Override + public void setupTunnel(Tunnel tunnel, Path path) { + // TODO: This will be implemented later. + } + + @Override + public void setupTunnel(ElementId srcElement, Tunnel tunnel, Path path) { + // TODO: This will be implemented later. + } + + @Override + public void releaseTunnel(Tunnel tunnel) { + // TODO: This will be implemented later. + } + + @Override + public void releaseTunnel(ElementId srcElement, Tunnel tunnel) { + // TODO: This will be implemented later. + } + + @Override + public void updateTunnel(Tunnel tunnel, Path path) { + // TODO: This will be implemented later. + } + + @Override + public void updateTunnel(ElementId srcElement, Tunnel tunnel, Path path) { + // TODO: This will be implemented later. + } + + @Override + public TunnelId tunnelAdded(TunnelDescription tunnel) { + return providerService.tunnelAdded(tunnel); + } + + @Override + public void tunnelRemoved(TunnelDescription tunnel) { + providerService.tunnelRemoved(tunnel); + } + + @Override + public void tunnelUpdated(TunnelDescription tunnel) { + providerService.tunnelUpdated(tunnel); + } + + @Override + public Tunnel tunnelQueryById(TunnelId tunnelId) { + // TODO: This will be implemented later. + return null; + } +} diff --git a/framework/src/onos/providers/ovsdb/tunnel/src/main/java/org/onosproject/ovsdb/provider/tunnel/package-info.java b/framework/src/onos/providers/ovsdb/tunnel/src/main/java/org/onosproject/ovsdb/provider/tunnel/package-info.java new file mode 100644 index 00000000..4f8a1095 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/tunnel/src/main/java/org/onosproject/ovsdb/provider/tunnel/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + *Provider that uses ovsdb controller as a means of infrastructure tunnel discovery. + * + */ +package org.onosproject.ovsdb.provider.tunnel;
\ No newline at end of file diff --git a/framework/src/onos/providers/ovsdb/tunnel/src/test/java/org/onosproject/ovsdb/provider/tunnel/OvsdbTunnelProviderTest.java b/framework/src/onos/providers/ovsdb/tunnel/src/test/java/org/onosproject/ovsdb/provider/tunnel/OvsdbTunnelProviderTest.java new file mode 100644 index 00000000..3d1549e5 --- /dev/null +++ b/framework/src/onos/providers/ovsdb/tunnel/src/test/java/org/onosproject/ovsdb/provider/tunnel/OvsdbTunnelProviderTest.java @@ -0,0 +1,185 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.ovsdb.provider.tunnel; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.incubator.net.tunnel.DefaultTunnelDescription; +import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelDescription; +import org.onosproject.incubator.net.tunnel.TunnelEndPoint; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.incubator.net.tunnel.TunnelProvider; +import org.onosproject.incubator.net.tunnel.TunnelProviderRegistry; +import org.onosproject.incubator.net.tunnel.TunnelProviderService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.Link; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.provider.AbstractProviderService; +import org.onosproject.net.provider.ProviderId; + +/** + * Test for ovsdb tunnel provider. + */ +public class OvsdbTunnelProviderTest { + private final OvsdbTunnelProvider provider = new OvsdbTunnelProvider(); + private final TestTunnelRegistry tunnelRegistry = new TestTunnelRegistry(); + private TestTunnelProviderService providerService; + + @Before + public void setUp() { + provider.providerRegistry = tunnelRegistry; + provider.activate(); + } + + @Test + public void basics() { + assertNotNull("registration expected", providerService); + assertEquals("incorrect provider", provider, providerService.provider()); + } + + @Test + public void testTunnelAdded() { + TunnelEndPoint src = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf("192.168.1.1")); + TunnelEndPoint dst = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf("192.168.1.3")); + SparseAnnotations annotations = DefaultAnnotations.builder() + .set("bandwidth", "1024").build(); + Link link = new DefaultLink( + this.provider.id(), + ConnectPoint.deviceConnectPoint("192.168.2.3/20"), + ConnectPoint.deviceConnectPoint("192.168.2.4/30"), + Link.Type.DIRECT); + List<Link> links = new ArrayList<Link>(); + links.add(link); + TunnelDescription tunnel = new DefaultTunnelDescription( + TunnelId.valueOf("1234"), + src, + dst, + Tunnel.Type.VXLAN, + new DefaultGroupId(0), + this.provider.id(), + TunnelName.tunnelName("tunnel12"), + new DefaultPath(this.provider.id(), links, 0.3), + annotations); + provider.tunnelAdded(tunnel); + assertEquals(1, providerService.tunnelSet.size()); + } + + @Test + public void testTunnelRemoved() { + TunnelEndPoint src = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf("192.168.1.1")); + TunnelEndPoint dst = IpTunnelEndPoint.ipTunnelPoint(IpAddress + .valueOf("192.168.1.3")); + SparseAnnotations annotations = DefaultAnnotations.builder() + .set("bandwidth", "1024").build(); + Link link = new DefaultLink( + this.provider.id(), + ConnectPoint.deviceConnectPoint("192.168.2.3/20"), + ConnectPoint.deviceConnectPoint("192.168.2.4/30"), + Link.Type.DIRECT); + List<Link> links = new ArrayList<Link>(); + links.add(link); + TunnelDescription tunnel = new DefaultTunnelDescription( + TunnelId.valueOf("1234"), + src, + dst, + Tunnel.Type.VXLAN, + new DefaultGroupId(0), + this.provider.id(), + TunnelName.tunnelName("tunnel1"), + new DefaultPath(this.provider.id(), links, 0.3), + annotations); + provider.tunnelRemoved(tunnel); + assertEquals(0, providerService.tunnelSet.size()); + } + + @After + public void tearDown() { + provider.deactivate(); + provider.providerRegistry = null; + } + + private class TestTunnelRegistry implements TunnelProviderRegistry { + + @Override + public TunnelProviderService register(TunnelProvider provider) { + providerService = new TestTunnelProviderService(provider); + return providerService; + } + + @Override + public void unregister(TunnelProvider provider) { + + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + } + + private class TestTunnelProviderService + extends AbstractProviderService<TunnelProvider> + implements TunnelProviderService { + Set<TunnelDescription> tunnelSet = new HashSet<TunnelDescription>(); + + protected TestTunnelProviderService(TunnelProvider provider) { + super(provider); + } + + @Override + public TunnelId tunnelAdded(TunnelDescription tunnel) { + tunnelSet.add(tunnel); + return null; + } + + @Override + public void tunnelRemoved(TunnelDescription tunnel) { + tunnelSet.remove(tunnel); + } + + @Override + public void tunnelUpdated(TunnelDescription tunnel) { + + } + + @Override + public Tunnel tunnelQueryById(TunnelId tunnelId) { + return null; + } + + } +} diff --git a/framework/src/onos/providers/pcep/app/app.xml b/framework/src/onos/providers/pcep/app/app.xml new file mode 100644 index 00000000..ea3d1d17 --- /dev/null +++ b/framework/src/onos/providers/pcep/app/app.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<app name="org.onosproject.pcep" origin="ON.Lab" version="${project.version}" + featuresRepo="mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features" + features="${project.artifactId}"> + <description>${project.description}</description> + <artifact>mvn:${project.groupId}/onos-app-pcep-api/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-pcepio/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-pcep-controller-api/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-pcep-controller-impl/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-pcep-provider-topology/${project.version}</artifact> + <artifact>mvn:${project.groupId}/onos-pcep-provider-tunnel/${project.version}</artifact> +</app> diff --git a/framework/src/onos/providers/pcep/app/features.xml b/framework/src/onos/providers/pcep/app/features.xml new file mode 100644 index 00000000..65a221bb --- /dev/null +++ b/framework/src/onos/providers/pcep/app/features.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + ~ Copyright 2015 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> + <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> + <feature name="${project.artifactId}" version="${project.version}" + description="${project.description}"> + <feature>onos-api</feature> + <bundle>mvn:${project.groupId}/onos-app-pcep-api/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-pcepio/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-pcep-controller-api/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-pcep-controller-impl/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-pcep-provider-topology/${project.version}</bundle> + <bundle>mvn:${project.groupId}/onos-pcep-provider-tunnel/${project.version}</bundle> + </feature> +</features> diff --git a/framework/src/onos/providers/pcep/app/pom.xml b/framework/src/onos/providers/pcep/app/pom.xml new file mode 100644 index 00000000..bce88d99 --- /dev/null +++ b/framework/src/onos/providers/pcep/app/pom.xml @@ -0,0 +1,47 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-pcep</artifactId> + <packaging>pom</packaging> + <description>PCEP protocol southbound providers</description> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-pcep-api</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcepio</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-controller-api</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-controller-impl</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-provider-topology</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-provider-tunnel</artifactId> + <version>${project.version}</version> + </dependency> + + </dependencies> +</project> diff --git a/framework/src/onos/providers/pcep/pom.xml b/framework/src/onos/providers/pcep/pom.xml new file mode 100644 index 00000000..66a71141 --- /dev/null +++ b/framework/src/onos/providers/pcep/pom.xml @@ -0,0 +1,17 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + <artifactId>onos-pcep-providers</artifactId> + <packaging>pom</packaging> + <description>PCEP protocol providers root</description> + <modules> + <module>topology</module> + <module>tunnel</module> + <module>app</module> + </modules> +</project>
\ No newline at end of file diff --git a/framework/src/onos/providers/pcep/topology/pom.xml b/framework/src/onos/providers/pcep/topology/pom.xml new file mode 100644 index 00000000..cd8589b9 --- /dev/null +++ b/framework/src/onos/providers/pcep/topology/pom.xml @@ -0,0 +1,18 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + <artifactId>onos-pcep-provider-topology</artifactId> + <packaging>bundle</packaging> + <description>PCEP topology provider</description> + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-pcep-api</artifactId> + </dependency> + </dependencies> +</project>
\ No newline at end of file diff --git a/framework/src/onos/providers/pcep/topology/src/main/java/org/onosproject/provider/pcep/topology/impl/PcepTopologyProvider.java b/framework/src/onos/providers/pcep/topology/src/main/java/org/onosproject/provider/pcep/topology/impl/PcepTopologyProvider.java new file mode 100644 index 00000000..38eaf1fe --- /dev/null +++ b/framework/src/onos/providers/pcep/topology/src/main/java/org/onosproject/provider/pcep/topology/impl/PcepTopologyProvider.java @@ -0,0 +1,321 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.topology.impl; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onlab.packet.ChassisId; +import org.onosproject.cluster.ClusterService; +import org.onosproject.mastership.MastershipAdminService; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.Link.Type; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.OchPort; +import org.onosproject.net.OduCltPort; +import org.onosproject.net.OmsPort; +import org.onosproject.net.Port; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DefaultPortDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceProviderService; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.device.OchPortDescription; +import org.onosproject.net.device.OduCltPortDescription; +import org.onosproject.net.device.OmsPortDescription; +import org.onosproject.net.device.PortDescription; +import org.onosproject.net.link.DefaultLinkDescription; +import org.onosproject.net.link.LinkDescription; +import org.onosproject.net.link.LinkProvider; +import org.onosproject.net.link.LinkProviderRegistry; +import org.onosproject.net.link.LinkProviderService; +import org.onosproject.net.link.LinkService; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.pcep.api.PcepController; +import org.onosproject.pcep.api.PcepDpid; +import org.onosproject.pcep.api.PcepLink; +import org.onosproject.pcep.api.PcepLink.PortType; +import org.onosproject.pcep.api.PcepLinkListener; +import org.onosproject.pcep.api.PcepOperator.OperationType; +import org.onosproject.pcep.api.PcepSwitch; +import org.onosproject.pcep.api.PcepSwitchListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.pcep.api.PcepDpid.uri; + +/** + * Provider which uses an PCEP controller to detect network infrastructure + * topology. + */ +@Component(immediate = true) +public class PcepTopologyProvider extends AbstractProvider + implements LinkProvider, DeviceProvider { + + public PcepTopologyProvider() { + super(new ProviderId("pcep", "org.onosproject.provider.pcep")); + } + + private static final Logger log = LoggerFactory + .getLogger(PcepTopologyProvider.class); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkProviderRegistry linkProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceProviderRegistry deviceProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PcepController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LinkService linkService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipAdminService mastershipAdminService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + private DeviceProviderService deviceProviderService; + private LinkProviderService linkProviderService; + + private HashMap<Long, List<PortDescription>> portMap = new HashMap<>(); + private InternalLinkProvider listener = new InternalLinkProvider(); + + @Activate + public void activate() { + linkProviderService = linkProviderRegistry.register(this); + deviceProviderService = deviceProviderRegistry.register(this); + controller.addListener(listener); + controller.addLinkListener(listener); + } + + @Deactivate + public void deactivate() { + linkProviderRegistry.unregister(this); + linkProviderService = null; + controller.removeListener(listener); + controller.removeLinkListener(listener); + } + + private List<PortDescription> buildPortDescriptions(PcepDpid dpid, + Port port, + PortType portType) { + + List<PortDescription> portList; + + if (portMap.containsKey(dpid.value())) { + portList = portMap.get(dpid.value()); + } else { + portList = new ArrayList<>(); + } + if (port != null && portType != null) { + portList.add(buildPortDescription(port, portType)); + } + + portMap.put(dpid.value(), portList); + return portList; + } + + private PortDescription buildPortDescription(Port port, PortType portType) { + PortDescription portDescription; + + switch (portType) { + case OCH_PORT: + OchPort ochp = (OchPort) port; + portDescription = new OchPortDescription(ochp.number(), ochp.isEnabled(), + ochp.signalType(), ochp.isTunable(), + ochp.lambda()); + break; + case ODU_PORT: + OduCltPort odup = (OduCltPort) port; + portDescription = new OduCltPortDescription(odup.number(), odup.isEnabled(), + odup.signalType()); + break; + case OMS_PORT: + OmsPort op = (OmsPort) port; + portDescription = new OmsPortDescription(op.number(), op.isEnabled(), op.minFrequency(), + op.maxFrequency(), op.grid()); + break; + default: + portDescription = new DefaultPortDescription(port.number(), port.isEnabled()); + break; + } + return portDescription; + } + + /** + * Build a link description from a pcep link. + * + * @param pceLink pcep link + * @return LinkDescription onos link description + */ + private LinkDescription buildLinkDescription(PcepLink pceLink) { + LinkDescription ld; + checkNotNull(pceLink); + DeviceId srcDeviceID = deviceId(uri(pceLink.linkSrcDeviceID())); + DeviceId dstDeviceID = deviceId(uri(pceLink.linkDstDeviceId())); + + deviceProviderService + .updatePorts(srcDeviceID, + buildPortDescriptions(pceLink.linkSrcDeviceID(), + pceLink.linkSrcPort(), pceLink.portType())); + + deviceProviderService + .updatePorts(dstDeviceID, + buildPortDescriptions(pceLink.linkDstDeviceId(), + pceLink.linkDstPort(), pceLink.portType())); + + ConnectPoint src = new ConnectPoint(srcDeviceID, pceLink.linkSrcPort().number()); + + ConnectPoint dst = new ConnectPoint(dstDeviceID, pceLink.linkDstPort().number()); + + DefaultAnnotations extendedAttributes = DefaultAnnotations + .builder() + .set("subType", String.valueOf(pceLink.linkSubType())) + .set("workState", pceLink.linkState()) + .set("distance", String.valueOf(pceLink.linkDistance())) + .set("capType", pceLink.linkCapacityType().toLowerCase()) + .set("avail_" + pceLink.linkCapacityType().toLowerCase(), + String.valueOf(pceLink.linkAvailValue())) + .set("max_" + pceLink.linkCapacityType().toLowerCase(), + String.valueOf(pceLink.linkMaxValue())).build(); + // construct the link + ld = new DefaultLinkDescription(src, dst, Type.OPTICAL, extendedAttributes); + return ld; + } + + private class InternalLinkProvider + implements PcepSwitchListener, PcepLinkListener { + + @Override + public void switchAdded(PcepDpid dpid) { + if (deviceProviderService == null) { + return; + } + DeviceId deviceId = deviceId(uri(dpid)); + PcepSwitch sw = controller.getSwitch(dpid); + checkNotNull(sw, "device should not null."); + // The default device type is switch. + ChassisId cId = new ChassisId(dpid.value()); + Device.Type deviceType; + + switch (sw.getDeviceType()) { + case ROADM: + deviceType = Device.Type.ROADM; + break; + case OTN: + deviceType = Device.Type.SWITCH; + break; + case ROUTER: + deviceType = Device.Type.ROUTER; + break; + default: + deviceType = Device.Type.OTHER; + } + + DeviceDescription description = new DefaultDeviceDescription( + deviceId.uri(), + deviceType, + sw.manufacturerDescription(), + sw.hardwareDescription(), + sw.softwareDescription(), + sw.serialNumber(), + cId); + deviceProviderService.deviceConnected(deviceId, description); + + } + + @Override + public void switchRemoved(PcepDpid dpid) { + if (deviceProviderService == null || linkProviderService == null) { + return; + } + deviceProviderService.deviceDisconnected(deviceId(uri(dpid))); + + linkProviderService.linksVanished(DeviceId.deviceId(uri(dpid))); + } + + @Override + public void switchChanged(PcepDpid dpid) { + // TODO Auto-generated method stub + + } + + @Override + public void handlePCEPlink(PcepLink link) { + + OperationType operType = link.getOperationType(); + LinkDescription ld = buildLinkDescription(link); + if (ld == null) { + log.error("Invalid link info."); + return; + } + switch (operType) { + case ADD: + case UPDATE: + linkProviderService.linkDetected(ld); + break; + + case DELETE: + linkProviderService.linkVanished(ld); + break; + + default: + break; + + } + } + + } + + @Override + public void triggerProbe(DeviceId deviceId) { + // TODO Auto-generated method stub + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + } + + @Override + public boolean isReachable(DeviceId deviceId) { + // TODO Auto-generated method stub + return true; + } +} diff --git a/framework/src/onos/providers/pcep/topology/src/main/java/org/onosproject/provider/pcep/topology/impl/package-info.java b/framework/src/onos/providers/pcep/topology/src/main/java/org/onosproject/provider/pcep/topology/impl/package-info.java new file mode 100644 index 00000000..c119266d --- /dev/null +++ b/framework/src/onos/providers/pcep/topology/src/main/java/org/onosproject/provider/pcep/topology/impl/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *Provider that uses PCEP controller as a means of infrastructure topology discovery. + */ +package org.onosproject.provider.pcep.topology.impl;
\ No newline at end of file diff --git a/framework/src/onos/providers/pcep/tunnel/pom.xml b/framework/src/onos/providers/pcep/tunnel/pom.xml new file mode 100644 index 00000000..09efb8ae --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/pom.xml @@ -0,0 +1,39 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-providers</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + <artifactId>onos-pcep-provider-tunnel</artifactId> + <packaging>bundle</packaging> + <description>PCEP-based tunnel provider</description> + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-app-pcep-api</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-pcep-controller-api</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + <classifier>tests</classifier> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-incubator-net</artifactId> + <version>${project.version} </version> + <scope>test</scope> + </dependency> + </dependencies> +</project>
\ No newline at end of file diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelApiMapper.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelApiMapper.java new file mode 100644 index 00000000..b7aa3bda --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelApiMapper.java @@ -0,0 +1,206 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.commons.collections.map.MultiKeyMap; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelProviderService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Entity to provide tunnel DB and mapping for request/response between CORE to PCEP + * and PCEP to PCC. + */ +public class PcepTunnelApiMapper { + protected static final Logger log = LoggerFactory.getLogger(PcepTunnelApiMapper.class); + + static final String PROVIDER_ID = "org.onosproject.provider.tunnel.pcep"; + // Map to store all the tunnel requests. + private Map<Integer, PcepTunnelData> tunnelRequestQueue; + //Map to store all core related tunnel requests. + private Map<TunnelId, PcepTunnelData> coreTunnelRequestQueue; + //Map to store all the created tunnels. + private Map<Integer, PcepTunnelData> tunnelDB; + // Map to store the tunnel ids, given by core and given by pcc. + private Map<TunnelId, Integer> tunnelIdMap; + //Map to store all the learnt tunnels. + private MultiKeyMap pccTunnelDB = new MultiKeyMap(); + + TunnelProviderService tunnelApiMapperservice; + + /** + * Default constructor. + */ + public PcepTunnelApiMapper() { + //TODO check if the map need to initialize + tunnelRequestQueue = new HashMap<Integer, PcepTunnelData>(); + coreTunnelRequestQueue = new HashMap<TunnelId, PcepTunnelData>(); + tunnelDB = new HashMap<Integer, PcepTunnelData>(); + tunnelIdMap = new HashMap<TunnelId, Integer>(); + } + + /** + * Add tunnels to tunnel Request queues. + * + * @param srpId srp id + * @param pcepTunnelData pcep tunnel data + */ + public void addToTunnelRequestQueue(int srpId, PcepTunnelData pcepTunnelData) { + tunnelRequestQueue.put(new Integer(srpId), pcepTunnelData); + log.debug("Tunnel Added to TunnelRequestQueue"); + } + + /** + * Map between Tunnel ID and pcc provided Tunnel ID. + * + * @param pcepTunnelData pcep tunnel data + */ + public void addToTunnelIdMap(PcepTunnelData pcepTunnelData) { + int value = pcepTunnelData.statefulIpv4IndentifierTlv().getTunnelId() & 0xFFFF; + tunnelIdMap.put(pcepTunnelData.tunnel().tunnelId(), (new Integer(value))); + log.debug("Tunnel ID Added to tunnelIdMap"); + } + + /** + * Add tunnels to core tunnel request queue. + * + * @param pcepTunnelData pcep tunnel data + */ + public void addToCoreTunnelRequestQueue(PcepTunnelData pcepTunnelData) { + coreTunnelRequestQueue.put(pcepTunnelData.tunnel().tunnelId(), pcepTunnelData); + log.debug("Tunnel Added to CoreTunnelRequestQueue"); + } + + /** + * Removes tunnels from the core tunnel request queue. + * + * @param tunnelId tunnel id + */ + public void removeFromCoreTunnelRequestQueue(TunnelId tunnelId) { + coreTunnelRequestQueue.remove(tunnelId); + log.debug("Tunnnel create response sent to core and removed from CoreTunnelRequestQueue"); + } + + /** + * Handle the report which comes after initiate message. + * + * @param srpId srp id + * @param pcepTunnelData pcep tunnel data + */ + public void handleCreateTunnelRequestQueue(int srpId, PcepTunnelData pcepTunnelData) { + + int value = tunnelIdMap.get(pcepTunnelData.tunnel().tunnelId()); + tunnelDB.put(new Integer(value), pcepTunnelData); + tunnelRequestQueue.remove(new Integer(srpId), pcepTunnelData); + log.debug("Tunnel Added to TunnelDBQueue and removed from TunnelRequestQueue. tunnel id {}" + + (new Integer(value)).toString()); + } + + /** + * Handle report which comes for update message. + * + * @param srpId srp id + * @param pcepTunnelData pcep tunnel data + */ + public void handleUpdateTunnelRequestQueue(int srpId, PcepTunnelData pcepTunnelData) { + if (pcepTunnelData.rptFlag()) { + pcepTunnelData.setRptFlag(false); + int value = tunnelIdMap.get(pcepTunnelData.tunnel().tunnelId()); + tunnelDB.put(new Integer(value), pcepTunnelData); + tunnelRequestQueue.remove(new Integer(srpId), pcepTunnelData); + log.debug("Tunnel Added to TunnelDBQueue and removed from TunnelRequestQueue. tunnel id {}" , + (new Integer(value)).toString()); + } else { + pcepTunnelData.setRptFlag(true); + tunnelRequestQueue.put(new Integer(srpId), pcepTunnelData); + log.debug("Tunnel updated in TunnelRequestQueue"); + } + } + + /** + * Handle report for tunnel Release request. + * + * @param srpId srp id + * @param pcepTunnelData pcep tunnel data + */ + public void handleRemoveFromTunnelRequestQueue(int srpId, PcepTunnelData pcepTunnelData) { + + int value = tunnelIdMap.get(pcepTunnelData.tunnel().tunnelId()); + tunnelIdMap.remove(pcepTunnelData.tunnel().tunnelId()); + tunnelDB.remove(new Integer(value)); + tunnelRequestQueue.remove(srpId); + log.debug("Tunnel removed from TunnelDBQueue and TunnelRequestQueue"); + } + + /** + * Returns PcepTunnelData from the tunnel request queue. + * + * @param srpId srp id + * @return PcepTunnelData pcep tunnel data + */ + public PcepTunnelData getDataFromTunnelRequestQueue(int srpId) { + return tunnelRequestQueue.get(new Integer(srpId)); + + } + + /** + * Returns PcepTunnelData from the tunnel DB. + * + * @param tunnelId tunnel id + * @return PcepTunnelData pcep tunnel data + */ + public PcepTunnelData getDataFromTunnelDBQueue(TunnelId tunnelId) { + int value = tunnelIdMap.get(tunnelId); + return tunnelDB.get((new Integer(value))); + } + + /** + * Checks whether the tunnel exist in tunnel request queue. + * + * @param srpId srp id + * @return true if tunnel exist in reuest queue, false otherwise + */ + public boolean checkFromTunnelRequestQueue(int srpId) { + boolean retValue = tunnelRequestQueue.containsKey(srpId); + return retValue; + } + + /** + * Returns whether tunnel exist in tunnel db. + * + * @param tunnelId tunnel id + * @return true/false if the tunnel exists in the tunnel db + */ + public boolean checkFromTunnelDBQueue(TunnelId tunnelId) { + int value = tunnelIdMap.get(tunnelId); + boolean retValue = tunnelDB.containsKey((new Integer(value))); + return retValue; + } + + /** + * Add Learnt tunnels to pcc tunnel DB. + * + * @param pcepTunnelData pcep tunnel data + */ + public void addPccTunnelDB(PcepTunnelData pcepTunnelData) { + pccTunnelDB.put(pcepTunnelData.statefulIpv4IndentifierTlv().getTunnelId() & 0xFFFFL, + pcepTunnelData.statefulIpv4IndentifierTlv().getIpv4IngressAddress(), pcepTunnelData); + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelData.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelData.java new file mode 100644 index 00000000..f796a2de --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelData.java @@ -0,0 +1,386 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import java.util.Objects; + +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.net.ElementId; +import org.onosproject.net.Path; +import org.onosproject.pcepio.types.StatefulIPv4LspIdentidiersTlv; + +import com.google.common.base.MoreObjects; + +/** + * To store all tunnel related information from Core and Path computation client. + */ +public class PcepTunnelData { + + private Tunnel tunnel; + private Path path; + private int plspId; + private ElementId elementId; + private RequestType requestType; + private boolean rptFlag; + + // data need to store from LSP object + private boolean lspAFlag; + private boolean lspDFlag; + private byte lspOFlag; + private short tunnelId; + private int extTunnelId; + private short lspId; + private StatefulIPv4LspIdentidiersTlv statefulIpv4IndentifierTlv; + + /** + * Default constructor. + */ + public PcepTunnelData() { + this.elementId = null; + this.tunnel = null; + this.path = null; + this.requestType = null; + this.rptFlag = false; + this.plspId = 0; + } + + /** + * Constructor to initialize Tunnel, Path and Request type. + * + * @param tunnel mpls tunnel + * @param path Path in network + * @param requestType request type for tunnel + */ + public PcepTunnelData(Tunnel tunnel, Path path, RequestType requestType) { + this.tunnel = tunnel; + this.path = path; + this.requestType = requestType; + } + + /** + * Constructor to initialize ElemendId, Tunnel, Path and Request type. + * + * @param elementId Ip element id + * @param tunnel mpls tunnel + * @param path Path in network + * @param requestType request type for tunnel + */ + public PcepTunnelData(ElementId elementId, Tunnel tunnel, Path path, RequestType requestType) { + this.elementId = elementId; + this.tunnel = tunnel; + this.path = path; + this.requestType = requestType; + } + + /** + * Constructor to initialize Tunnel and Request type. + * + * @param tunnel Tunnel from core + * @param requestType request type for tunnel + */ + public PcepTunnelData(Tunnel tunnel, RequestType requestType) { + this.tunnel = tunnel; + this.requestType = requestType; + } + + /** + * Constructor to initialize ElementId, Tunnel and Request type. + * + * @param elementId Ip element id + * @param tunnel mpls tunnel + * @param requestType request type for tunnel + */ + public PcepTunnelData(ElementId elementId, Tunnel tunnel, RequestType requestType) { + this.elementId = elementId; + this.tunnel = tunnel; + this.requestType = requestType; + } + + /** + * Sets ip element id. + * + * @param elementId Ip element id + */ + public void setElementId(ElementId elementId) { + this.elementId = elementId; + } + + /** + * Sets tunnel. + * + * @param tunnel mpls tunnel + */ + public void setTunnel(Tunnel tunnel) { + this.tunnel = tunnel; + } + + /** + * Sets Path. + * + * @param path Path in network + */ + public void setPath(Path path) { + this.path = path; + } + + /** + * Request type for tunnel. + * + * @param requestType request type for tunnel + */ + public void setRequestType(RequestType requestType) { + this.requestType = requestType; + } + + /** + * Sets plspid generated from pcc. + * + * @param plspId plsp identifier + */ + public void setPlspId(int plspId) { + this.plspId = plspId; + } + + /** + * Sets A flag from lsp object. + * + * @param value A flag value + */ + public void setLspAFlag(boolean value) { + this.lspAFlag = value; + } + + /** + * Sets OF flag from lsp object. + * + * @param value OF flag value + */ + public void setLspOFlag(byte value) { + this.lspOFlag = value; + } + + /** + * Sets tunnel id from PCC. + * + * @param value tunnel id value + */ + public void setTunnelId(short value) { + this.tunnelId = value; + } + + /** + * Sets extended tunnel id from PCC. + * + * @param value extended tunnel id value + */ + public void setExtTunnelId(int value) { + this.extTunnelId = value; + } + + /** + * Sets lsp id from pcc. + * + * @param value lsp id + */ + public void setLspId(short value) { + this.lspId = value; + } + + /** + * Sets statefulIpv4Identifiers tlv. + * @param value statefulIpv4Identifiers tlv + */ + public void setStatefulIpv4IndentifierTlv(StatefulIPv4LspIdentidiersTlv value) { + this.statefulIpv4IndentifierTlv = value; + } + + /** + * Sets report flag. + * + * @param rptFlag report flag + */ + public void setRptFlag(boolean rptFlag) { + this.rptFlag = rptFlag; + } + + /** + * Sets D flag from lsp object. + * + * @param value D flag value + */ + public void setLspDFlag(boolean value) { + this.lspDFlag = value; + } + + /** + * To get Ip element id. + * + * @return Ip elemend id + */ + public ElementId elementId() { + return this.elementId; + } + + /** + * To get Tunnel. + * + * @return tunnel + */ + public Tunnel tunnel() { + return this.tunnel; + } + + /** + * To get Path. + * + * @return path + */ + public Path path() { + return this.path; + } + + /** + * To get request type. + * + * @return request type + */ + public RequestType requestType() { + return this.requestType; + } + + /** + * To get pLspId. + * + * @return pLspId + */ + public int plspId() { + return this.plspId; + } + + /** + * To get A flag. + * + * @return A flag + */ + public boolean lspAFlag() { + return this.lspAFlag; + } + + /** + * To get OF flag. + * + * @return OF flag + */ + public byte lspOFlag() { + return this.lspOFlag; + } + + /** + * To get tunnel id. + * + * @return tunnel id + */ + public short tunnelId() { + return this.tunnelId; + } + + /** + * To get extended tunnel id. + * + * @return extended tunnel id + */ + public int extTunnelId() { + return this.extTunnelId; + } + + /** + * To get pLspId. + * + * @return pLspId + */ + public short lspId() { + return this.lspId; + } + + /** + * To get D Flag. + * + * @return d flag + */ + public boolean lspDFlag() { + return this.lspDFlag; + } + + /** + * To get statefulIpv4Indentifier tlv. + * + * @return statefulIpv4Indentifier tlv + */ + public StatefulIPv4LspIdentidiersTlv statefulIpv4IndentifierTlv() { + return this.statefulIpv4IndentifierTlv; + } + + /** + * To get report flag. + * + * @return report flag + */ + public boolean rptFlag() { + return this.rptFlag; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + + if (obj instanceof PcepTunnelData) { + PcepTunnelData other = (PcepTunnelData) obj; + return Objects.equals(tunnel, other.tunnel) + && Objects.equals(path, other.path) + && Objects.equals(plspId, other.plspId) + && Objects.equals(elementId, other.elementId) + && Objects.equals(requestType, other.requestType) + && Objects.equals(rptFlag, other.rptFlag) + && Objects.equals(lspAFlag, other.lspAFlag) + && Objects.equals(lspDFlag, other.lspDFlag) + && Objects.equals(lspOFlag, other.lspOFlag) + && Objects.equals(tunnelId, other.tunnelId) + && Objects.equals(extTunnelId, other.extTunnelId) + && Objects.equals(lspId, other.lspId) + && Objects.equals(statefulIpv4IndentifierTlv, other.statefulIpv4IndentifierTlv); + } + + return false; + } + + @Override + public int hashCode() { + return Objects.hash(tunnel, path, plspId, elementId, requestType, rptFlag, lspAFlag, + lspDFlag, lspOFlag, tunnelId, extTunnelId, lspId, statefulIpv4IndentifierTlv); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()).add("Tunnel", tunnel) + .add("Path", path).add("PlspId", plspId).add("ElementId", elementId) + .add("RequestType", requestType).add("RptFlag", rptFlag).add("LspAFlag", lspAFlag) + .add("LspDFlag", lspDFlag).add("LspOFlag", lspOFlag).add("TunnelId", tunnelId) + .add("ExtTunnelid", extTunnelId).add("LspId", lspId) + .add("StatefulIpv4IndentifierTlv", statefulIpv4IndentifierTlv).toString(); + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelProvider.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelProvider.java new file mode 100644 index 00000000..648e500b --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelProvider.java @@ -0,0 +1,1230 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.onosproject.net.DefaultAnnotations.EMPTY; +import static org.onlab.util.Tools.get; +import static org.onosproject.net.DeviceId.deviceId; +import static org.onosproject.net.PortNumber.portNumber; +import static org.onosproject.pcep.api.PcepDpid.uri; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; + +import com.google.common.collect.Maps; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.IpAddress; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.incubator.net.tunnel.DefaultOpticalTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.DefaultTunnel; +import org.onosproject.incubator.net.tunnel.DefaultTunnelDescription; +import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.DefaultTunnelStatistics; +import org.onosproject.incubator.net.tunnel.OpticalLogicId; +import org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelDescription; +import org.onosproject.incubator.net.tunnel.TunnelEndPoint; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.incubator.net.tunnel.TunnelProvider; +import org.onosproject.incubator.net.tunnel.TunnelProviderRegistry; +import org.onosproject.incubator.net.tunnel.TunnelProviderService; +import org.onosproject.incubator.net.tunnel.TunnelService; +import org.onosproject.incubator.net.tunnel.TunnelStatistics; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.DeviceId; +import org.onosproject.net.ElementId; +import org.onosproject.net.IpElementId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.SparseAnnotations; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.pcep.api.PcepController; +import org.onosproject.pcep.api.PcepDpid; +import org.onosproject.pcep.api.PcepHopNodeDescription; +import org.onosproject.pcep.api.PcepOperator.OperationType; +import org.onosproject.pcep.api.PcepTunnel; +import org.onosproject.pcep.api.PcepTunnel.PATHTYPE; +import org.onosproject.pcep.api.PcepTunnel.PathState; +import org.onosproject.pcep.api.PcepTunnelListener; +import org.onosproject.pcep.api.PcepTunnelStatistics; +import org.osgi.service.component.annotations.Modified; +import org.onosproject.pcep.controller.PccId; +import org.onosproject.pcep.controller.PcepClient; +import org.onosproject.pcep.controller.PcepClientController; +import org.onosproject.pcep.controller.PcepClientListener; +import org.onosproject.pcep.controller.PcepEventListener; +import org.onosproject.pcepio.exceptions.PcepParseException; +import org.onosproject.pcepio.protocol.PcInitiatedLspRequest; +import org.onosproject.pcepio.protocol.PcepAttribute; +import org.onosproject.pcepio.protocol.PcepBandwidthObject; +import org.onosproject.pcepio.protocol.PcepEndPointsObject; +import org.onosproject.pcepio.protocol.PcepEroObject; +import org.onosproject.pcepio.protocol.PcepInitiateMsg; +import org.onosproject.pcepio.protocol.PcepLspObject; +import org.onosproject.pcepio.protocol.PcepMessage; +import org.onosproject.pcepio.protocol.PcepMsgPath; +import org.onosproject.pcepio.protocol.PcepReportMsg; +import org.onosproject.pcepio.protocol.PcepRroObject; +import org.onosproject.pcepio.protocol.PcepSrpObject; +import org.onosproject.pcepio.protocol.PcepStateReport; +import org.onosproject.pcepio.protocol.PcepUpdateMsg; +import org.onosproject.pcepio.protocol.PcepUpdateRequest; +import org.onosproject.pcepio.types.IPv4SubObject; +import org.onosproject.pcepio.types.PcepValueType; +import org.onosproject.pcepio.types.StatefulIPv4LspIdentidiersTlv; +import org.onosproject.pcepio.types.SymbolicPathNameTlv; +import org.slf4j.Logger; +import org.osgi.service.component.ComponentContext; + +/** + * Provider which uses an PCEP controller to detect, update, create network + * tunnels. + */ +@Component(immediate = true) +@Service +public class PcepTunnelProvider extends AbstractProvider implements TunnelProvider { + + private static final Logger log = getLogger(PcepTunnelProvider.class); + private static final long MAX_BANDWIDTH = 99999744; + private static final long MIN_BANDWIDTH = 64; + private static final String BANDWIDTH_UINT = "kbps"; + static final String PROVIDER_ID = "org.onosproject.provider.tunnel.pcep"; + + static final int POLL_INTERVAL = 10; + @Property(name = "tunnelStatsPollFrequency", intValue = POLL_INTERVAL, + label = "Frequency (in seconds) for polling tunnel statistics") + private int tunnelStatsPollFrequency = POLL_INTERVAL; + + private static final String TUNNLE_NOT_NULL = "Create failed,The given port may be wrong or has been occupied."; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TunnelProviderRegistry tunnelProviderRegistry; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PcepController controller; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PcepClientController pcepClientController; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected TunnelService tunnelService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService cfgService; + + TunnelProviderService service; + + HashMap<String, TunnelId> tunnelMap = new HashMap<String, TunnelId>(); + HashMap<TunnelId, TunnelStatistics> tunnelStatisticsMap = new HashMap<>(); + private HashMap<Long, TunnelStatsCollector> collectors = Maps.newHashMap(); + + private InnerTunnelProvider listener = new InnerTunnelProvider(); + + protected PcepTunnelApiMapper pcepTunnelAPIMapper = new PcepTunnelApiMapper(); + private static final int DEFAULT_BANDWIDTH_VALUE = 10; + + /** + * Creates a Tunnel provider. + */ + public PcepTunnelProvider() { + super(new ProviderId("pcep", PROVIDER_ID)); + } + + @Activate + public void activate() { + cfgService.registerProperties(getClass()); + service = tunnelProviderRegistry.register(this); + controller.addTunnelListener(listener); + pcepClientController.addListener(listener); + pcepClientController.addEventListener(listener); + tunnelService.queryAllTunnels().forEach(tunnel -> { + String pcepTunnelId = getPCEPTunnelKey(tunnel.tunnelId()); + TunnelStatsCollector tsc = new TunnelStatsCollector(pcepTunnelId, tunnelStatsPollFrequency); + tsc.start(); + collectors.put(tunnel.tunnelId().id(), tsc); + + }); + + log.info("Started"); + } + + @Deactivate + public void deactivate() { + tunnelProviderRegistry.unregister(this); + controller.removeTunnelListener(listener); + collectors.values().forEach(TunnelStatsCollector::stop); + pcepClientController.removeListener(listener); + log.info("Stopped"); + } + + @Modified + public void modified(ComponentContext context) { + Dictionary<?, ?> properties = context.getProperties(); + int newTunnelStatsPollFrequency; + try { + String s = get(properties, "tunnelStatsPollFrequency"); + newTunnelStatsPollFrequency = isNullOrEmpty(s) ? tunnelStatsPollFrequency : Integer.parseInt(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + newTunnelStatsPollFrequency = tunnelStatsPollFrequency; + } + + if (newTunnelStatsPollFrequency != tunnelStatsPollFrequency) { + tunnelStatsPollFrequency = newTunnelStatsPollFrequency; + collectors.values().forEach(tsc -> tsc.adjustPollInterval(tunnelStatsPollFrequency)); + log.info("New setting: tunnelStatsPollFrequency={}", tunnelStatsPollFrequency); + } + + } + + @Override + public void setupTunnel(Tunnel tunnel, Path path) { + if (tunnel.type() != Tunnel.Type.MPLS) { + log.error("Tunnel Type MPLS is only supported"); + return; + } + + // check for tunnel end points + if (!(tunnel.src() instanceof IpTunnelEndPoint) || !(tunnel.dst() instanceof IpTunnelEndPoint)) { + log.error("Tunnel source or destination is not valid"); + return; + } + + // Get the pcc client + PcepClient pc = pcepClientController.getClient(PccId.pccId(((IpTunnelEndPoint) tunnel.src()).ip())); + + if (!(pc instanceof PcepClient)) { + log.error("There is no PCC connected with ip addresss {}" + + ((IpTunnelEndPoint) tunnel.src()).ip().toString()); + return; + } + pcepSetupTunnel(tunnel, path, pc); + } + + @Override + public void setupTunnel(ElementId srcElement, Tunnel tunnel, Path path) { + + if (tunnel.type() != Tunnel.Type.MPLS) { + log.error("Tunnel Type MPLS is only supported"); + return; + } + + if (!(srcElement instanceof IpElementId)) { + log.error("Element id is not valid"); + return; + } + + // check for tunnel end points + if (!(tunnel.src() instanceof IpTunnelEndPoint) || !(tunnel.dst() instanceof IpTunnelEndPoint)) { + log.error("Tunnel source or destination is not valid"); + return; + } + + PcepClient pc = pcepClientController.getClient(PccId.pccId(((IpElementId) srcElement).ipAddress())); + + if (!(pc instanceof PcepClient)) { + log.error("There is no PCC connected with ip addresss {}" + + ((IpElementId) srcElement).ipAddress().toString()); + return; + } + pcepSetupTunnel(tunnel, path, pc); + } + + @Override + public void releaseTunnel(Tunnel tunnel) { + + if (tunnel.type() != Tunnel.Type.MPLS) { + log.error("Tunnel Type MPLS is only supported"); + return; + } + + // check for tunnel end points + if (!(tunnel.src() instanceof IpTunnelEndPoint) || !(tunnel.dst() instanceof IpTunnelEndPoint)) { + log.error("Tunnel source or destination is not valid"); + return; + } + + PcepClient pc = pcepClientController.getClient(PccId.pccId(((IpTunnelEndPoint) tunnel.src()).ip())); + + if (!(pc instanceof PcepClient)) { + log.error("There is no PCC connected with ip addresss {}" + + ((IpTunnelEndPoint) tunnel.src()).ip().toString()); + return; + } + pcepReleaseTunnel(tunnel, pc); + } + + @Override + public void releaseTunnel(ElementId srcElement, Tunnel tunnel) { + if (tunnel.type() != Tunnel.Type.MPLS) { + log.error("Tunnel Type MPLS is only supported"); + return; + } + + if (!(srcElement instanceof IpElementId)) { + log.error("Element id is not valid"); + return; + } + + // check for tunnel end points + if (!(tunnel.src() instanceof IpTunnelEndPoint) || !(tunnel.dst() instanceof IpTunnelEndPoint)) { + log.error("Tunnel source or destination is not valid"); + return; + } + + PcepClient pc = pcepClientController.getClient(PccId.pccId(((IpElementId) srcElement).ipAddress())); + + if (!(pc instanceof PcepClient)) { + log.error("There is no PCC connected with ip addresss {}" + + ((IpElementId) srcElement).ipAddress().toString()); + return; + } + pcepReleaseTunnel(tunnel, pc); + } + + @Override + public void updateTunnel(Tunnel tunnel, Path path) { + if (tunnel.type() != Tunnel.Type.MPLS) { + log.error("Tunnel Type MPLS is only supported"); + return; + } + + // check for tunnel end points + if (!(tunnel.src() instanceof IpTunnelEndPoint) || !(tunnel.dst() instanceof IpTunnelEndPoint)) { + log.error("Tunnel source or destination is not valid"); + return; + } + + PcepClient pc = pcepClientController.getClient(PccId.pccId(((IpTunnelEndPoint) tunnel.src()).ip())); + + if (!(pc instanceof PcepClient)) { + log.error("There is no PCC connected with ip addresss {}" + + ((IpTunnelEndPoint) tunnel.src()).ip().toString()); + return; + } + pcepUpdateTunnel(tunnel, path, pc); + } + + @Override + public void updateTunnel(ElementId srcElement, Tunnel tunnel, Path path) { + + if (tunnel.type() != Tunnel.Type.MPLS) { + log.error("Tunnel Type MPLS is only supported"); + return; + } + + if (!(srcElement instanceof IpElementId)) { + log.error("Element id is not valid"); + return; + } + + // check for tunnel end points + if (!(tunnel.src() instanceof IpTunnelEndPoint) || !(tunnel.dst() instanceof IpTunnelEndPoint)) { + log.error("Tunnel source or destination is not valid"); + return; + } + + PcepClient pc = pcepClientController.getClient(PccId.pccId(((IpElementId) srcElement).ipAddress())); + + if (!(pc instanceof PcepClient)) { + log.error("There is no PCC connected with ip addresss {}" + + ((IpElementId) srcElement).ipAddress().toString()); + return; + } + pcepUpdateTunnel(tunnel, path, pc); + } + + @Override + public TunnelId tunnelAdded(TunnelDescription tunnel) { + if (tunnel.type() == Tunnel.Type.MPLS) { + pcepTunnelAPIMapper.removeFromCoreTunnelRequestQueue(tunnel.id()); + return service.tunnelAdded(tunnel); + } + + long bandwidth = Long + .parseLong(tunnel.annotations().value("bandwidth")); + + if (bandwidth < MIN_BANDWIDTH || bandwidth > MAX_BANDWIDTH) { + error("Update failed, invalid bandwidth."); + return null; + } + + // endpoints + OpticalTunnelEndPoint src = (org.onosproject.incubator.net.tunnel.OpticalTunnelEndPoint) tunnel + .src(); + OpticalTunnelEndPoint dst = (OpticalTunnelEndPoint) tunnel.dst(); + // devices + DeviceId srcId = (DeviceId) src.elementId().get(); + DeviceId dstId = (DeviceId) dst.elementId().get(); + + // ports + long srcPort = src.portNumber().get().toLong(); + long dstPort = dst.portNumber().get().toLong(); + + // type + if (tunnel.type() != Tunnel.Type.VLAN) { + error("Illegal tunnel type. Only support VLAN tunnel creation."); + return null; + } + + PcepTunnel pcepTunnel = controller.applyTunnel(srcId, dstId, srcPort, + dstPort, bandwidth, + tunnel.tunnelName() + .value()); + + checkNotNull(pcepTunnel, TUNNLE_NOT_NULL); + TunnelDescription tunnelAdded = buildOpticalTunnel(pcepTunnel, null); + TunnelId tunnelId = service.tunnelAdded(tunnelAdded); + + tunnelMap.put(String.valueOf(pcepTunnel.id()), tunnelId); + return tunnelId; + } + + @Override + public void tunnelRemoved(TunnelDescription tunnel) { + if (tunnel.type() == Tunnel.Type.MPLS) { + pcepTunnelAPIMapper.removeFromCoreTunnelRequestQueue(tunnel.id()); + service.tunnelRemoved(tunnel); + } + + Tunnel tunnelOld = tunnelQueryById(tunnel.id()); + checkNotNull(tunnelOld, "The tunnel id is not exsited."); + if (tunnelOld.type() != Tunnel.Type.VLAN) { + error("Illegal tunnel type. Only support VLAN tunnel deletion."); + return; + } + String pcepTunnelId = getPCEPTunnelKey(tunnel.id()); + checkNotNull(pcepTunnelId, "The tunnel id is not exsited."); + if (!controller.deleteTunnel(pcepTunnelId)) { + error("Delete tunnel failed, Maybe some devices have been disconnected."); + return; + } + tunnelMap.remove(pcepTunnelId); + service.tunnelRemoved(tunnel); + } + + @Override + public void tunnelUpdated(TunnelDescription tunnel) { + if (tunnel.type() == Tunnel.Type.MPLS) { + pcepTunnelAPIMapper.removeFromCoreTunnelRequestQueue(tunnel.id()); + service.tunnelUpdated(tunnel); + } + + Tunnel tunnelOld = tunnelQueryById(tunnel.id()); + if (tunnelOld.type() != Tunnel.Type.VLAN) { + error("Illegal tunnel type. Only support VLAN tunnel update."); + return; + } + long bandwidth = Long + .parseLong(tunnel.annotations().value("bandwidth")); + if (bandwidth < MIN_BANDWIDTH || bandwidth > MAX_BANDWIDTH) { + error("Update failed, invalid bandwidth."); + return; + } + String pcepTunnelId = getPCEPTunnelKey(tunnel.id()); + + checkNotNull(pcepTunnelId, "Invalid tunnel id"); + if (!controller.updateTunnelBandwidth(pcepTunnelId, bandwidth)) { + + error("Update failed,maybe invalid bandwidth."); + return; + + } + service.tunnelUpdated(tunnel); + } + + private void error(String info) { + System.err.println(info); + } + + // Short-hand for creating a connection point. + private ConnectPoint connectPoint(PcepDpid id, long port) { + return new ConnectPoint(deviceId(uri(id)), portNumber(port)); + } + + // Short-hand for creating a link. + private Link link(PcepDpid src, long sp, PcepDpid dst, long dp) { + return new DefaultLink(id(), connectPoint(src, sp), connectPoint(dst, + dp), + Link.Type.TUNNEL); + } + + // Creates a path that leads through the given devices. + private Path createPath(List<PcepHopNodeDescription> hopList, + PATHTYPE pathtype, PathState pathState) { + if (hopList == null || hopList.size() == 0) { + return null; + } + List<Link> links = new ArrayList<>(); + for (int i = 1; i < hopList.size() - 1; i = i + 2) { + links.add(link(hopList.get(i).getDeviceId(), hopList.get(i) + .getPortNum(), hopList.get(i + 1).getDeviceId(), hopList + .get(i + 1).getPortNum())); + } + + int hopNum = hopList.size() - 2; + DefaultAnnotations extendAnnotations = DefaultAnnotations.builder() + .set("pathNum", String.valueOf(hopNum)) + .set("pathState", String.valueOf(pathState)) + .set("pathType", String.valueOf(pathtype)).build(); + return new DefaultPath(id(), links, hopNum, extendAnnotations); + } + + // convert the path description to a string. + public String pathToString(List<Link> links) { + StringBuilder builder = new StringBuilder(); + builder.append("{"); + for (Link link : links) { + builder.append("(Device:" + link.src().deviceId() + " Port:" + + link.src().port().toLong()); + builder.append(" Device:" + link.dst().deviceId() + " Port:" + + link.dst().port().toLong()); + builder.append(")"); + } + builder.append("}"); + return builder.toString(); + } + + // build a TunnelDescription. + private TunnelDescription buildOpticalTunnel(PcepTunnel pcepTunnel, + TunnelId tunnelId) { + TunnelEndPoint srcPoint = null; + TunnelEndPoint dstPoint = null; + Tunnel.Type tunnelType = null; + TunnelName name = TunnelName.tunnelName(pcepTunnel.name()); + + // add path after codes of tunnel's path merged + Path path = createPath(pcepTunnel.getHopList(), + pcepTunnel.getPathType(), + pcepTunnel.getPathState()); + + OpticalTunnelEndPoint.Type endPointType = null; + switch (pcepTunnel.type()) { + case OCH: + tunnelType = Tunnel.Type.OCH; + endPointType = OpticalTunnelEndPoint.Type.LAMBDA; + break; + + case OTN: + tunnelType = Tunnel.Type.ODUK; + endPointType = OpticalTunnelEndPoint.Type.TIMESLOT; + break; + + case UNI: + tunnelType = Tunnel.Type.VLAN; + endPointType = null; + break; + + default: + break; + } + DeviceId srcDid = deviceId(uri(pcepTunnel.srcDeviceID())); + DeviceId dstDid = deviceId(uri(pcepTunnel.dstDeviceId())); + PortNumber srcPort = PortNumber.portNumber(pcepTunnel.srcPort()); + PortNumber dstPort = PortNumber.portNumber(pcepTunnel.dstPort()); + + srcPoint = new DefaultOpticalTunnelEndPoint(id(), Optional.of(srcDid), + Optional.of(srcPort), null, + endPointType, + OpticalLogicId.logicId(0), + true); + dstPoint = new DefaultOpticalTunnelEndPoint(id(), Optional.of(dstDid), + Optional.of(dstPort), null, + endPointType, + OpticalLogicId.logicId(0), + true); + + // basic annotations + DefaultAnnotations annotations = DefaultAnnotations + .builder() + .set("SLA", String.valueOf(pcepTunnel.getSla())) + .set("bandwidth", + String.valueOf(pcepTunnel.bandWidth()) + BANDWIDTH_UINT) + .set("index", String.valueOf(pcepTunnel.id())).build(); + + // a VLAN tunnel always carry OCH tunnel, this annotation is the index + // of a OCH tunnel. + if (pcepTunnel.underlayTunnelId() != 0) { + DefaultAnnotations extendAnnotations = DefaultAnnotations + .builder() + .set("underLayTunnelIndex", + String.valueOf(pcepTunnel.underlayTunnelId())).build(); + annotations = DefaultAnnotations.merge(annotations, + extendAnnotations); + + } + TunnelDescription tunnel = new DefaultTunnelDescription( + tunnelId, + srcPoint, + dstPoint, + tunnelType, + new DefaultGroupId( + 0), + id(), name, + path, + annotations); + return tunnel; + + } + + /** + * Get the tunnelID according to the tunnel key. + * + * @param tunnelKey tunnel key + * @return corresponding tunnel id of the a tunnel key. + */ + private TunnelId getTunnelId(String tunnelKey) { + for (String key : tunnelMap.keySet()) { + if (key.equals(tunnelKey)) { + return tunnelMap.get(key); + } + } + return null; + } + + /** + * Get the tunnel key according to the tunnelID. + * + * @param tunnelId tunnel id + * @return corresponding a tunnel key of the tunnel id. + */ + private String getPCEPTunnelKey(TunnelId tunnelId) { + for (String key : tunnelMap.keySet()) { + if (tunnelMap.get(key).id() == tunnelId.id()) { + return key; + } + } + return null; + + } + + /** + * Build a DefaultTunnelStatistics from a PcepTunnelStatistics. + * + * @param statistics statistics data from a PCEP tunnel + * @return TunnelStatistics + */ + private TunnelStatistics buildTunnelStatistics(PcepTunnelStatistics statistics) { + DefaultTunnelStatistics.Builder builder = new DefaultTunnelStatistics.Builder(); + DefaultTunnelStatistics tunnelStatistics = builder.setBwUtilization(statistics.bandwidthUtilization()) + .setPacketLossRatio(statistics.packetLossRate()) + .setFlowDelay(statistics.flowDelay()) + .setAlarms(statistics.alarms()) + .build(); + return tunnelStatistics; + } + /** + * Creates list of hops for ERO object from Path. + * + * @param path network path + * @return list of ipv4 subobjects + */ + private LinkedList<PcepValueType> createPcepPath(Path path) { + LinkedList<PcepValueType> llSubObjects = new LinkedList<PcepValueType>(); + List<Link> listLink = path.links(); + ConnectPoint source = null; + ConnectPoint destination = null; + IpAddress ipDstAddress = null; + IpAddress ipSrcAddress = null; + PcepValueType subObj = null; + + for (Link link : listLink) { + source = link.src(); + if (!(source.equals(destination))) { + //set IPv4SubObject for ERO object + ipSrcAddress = source.ipElementId().ipAddress(); + subObj = new IPv4SubObject(ipSrcAddress.getIp4Address().toInt()); + llSubObjects.add(subObj); + } + + destination = link.dst(); + ipDstAddress = destination.ipElementId().ipAddress(); + subObj = new IPv4SubObject(ipDstAddress.getIp4Address().toInt()); + llSubObjects.add(subObj); + } + return llSubObjects; + } + + /** + * Creates PcInitiated lsp request list for setup tunnel. + * + * @param tunnel mpls tunnel + * @param path network path + * @param pc pcep client + * @param srpId unique id for pcep message + * @return list of PcInitiatedLspRequest + * @throws PcepParseException while building pcep objects fails + */ + LinkedList<PcInitiatedLspRequest> createPcInitiatedLspReqList(Tunnel tunnel, Path path, + PcepClient pc, int srpId) + throws PcepParseException { + PcepValueType tlv; + LinkedList<PcepValueType> llSubObjects = createPcepPath(path); + + if (llSubObjects == null || llSubObjects.size() == 0) { + log.error("There is no link information to create tunnel"); + return null; + } + + //build SRP object + PcepSrpObject srpobj = pc.factory().buildSrpObject().setSrpID(srpId).setRFlag(false).build(); + + LinkedList<PcepValueType> llOptionalTlv = new LinkedList<PcepValueType>(); + LinkedList<PcInitiatedLspRequest> llPcInitiatedLspRequestList = new LinkedList<PcInitiatedLspRequest>(); + // set LSP identifiers TLV + tlv = new StatefulIPv4LspIdentidiersTlv((((IpTunnelEndPoint) tunnel.src()).ip().getIp4Address().toInt()), + (short) 0, (short) 0, 0, + (((IpTunnelEndPoint) tunnel.dst()).ip().getIp4Address().toInt())); + llOptionalTlv.add(tlv); + //set SymbolicPathNameTlv of LSP object + tlv = new SymbolicPathNameTlv(tunnel.tunnelName().value().getBytes()); + llOptionalTlv.add(tlv); + + //build LSP object + PcepLspObject lspobj = pc.factory().buildLspObject().setAFlag(true).setOFlag((byte) 0).setPlspId(0) + .setOptionalTlv(llOptionalTlv).build(); + + //build ENDPOINTS object + PcepEndPointsObject endpointsobj = pc.factory().buildEndPointsObject() + .setSourceIpAddress(((IpTunnelEndPoint) tunnel.src()).ip().getIp4Address().toInt()) + .setDestIpAddress(((IpTunnelEndPoint) tunnel.dst()).ip().getIp4Address().toInt()) + .setPFlag(true).build(); + + //build ERO object + PcepEroObject eroobj = pc.factory().buildEroObject().setSubObjects(llSubObjects).build(); + + int iBandwidth = DEFAULT_BANDWIDTH_VALUE; + if (tunnel.annotations().value("bandwidth") != null) { + iBandwidth = Integer.parseInt(tunnel.annotations().value("bandwidth")); + } + // build bandwidth object + PcepBandwidthObject bandwidthObject = pc.factory().buildBandwidthObject().setBandwidth(iBandwidth).build(); + // build pcep attribute + PcepAttribute pcepAttribute = pc.factory().buildPcepAttribute().setBandwidthObject(bandwidthObject).build(); + + PcInitiatedLspRequest initiateLspRequest = pc.factory().buildPcInitiatedLspRequest().setSrpObject(srpobj) + .setLspObject(lspobj).setEndPointsObject(endpointsobj).setEroObject(eroobj) + .setPcepAttribute(pcepAttribute).build(); + llPcInitiatedLspRequestList.add(initiateLspRequest); + return llPcInitiatedLspRequestList; + } + + /** + * To send initiate tunnel message to pcc. + * + * @param tunnel mpls tunnel info + * @param path explicit route for the tunnel + * @param pc pcep client to send message + */ + private void pcepSetupTunnel(Tunnel tunnel, Path path, PcepClient pc) { + try { + int srpId = SrpIdGenerators.create(); + PcepTunnelData pcepTunnelData = new PcepTunnelData(tunnel, path, RequestType.CREATE); + + pcepTunnelAPIMapper.addToCoreTunnelRequestQueue(pcepTunnelData); + + LinkedList<PcInitiatedLspRequest> llPcInitiatedLspRequestList = createPcInitiatedLspReqList(tunnel, path, + pc, srpId); + if (llPcInitiatedLspRequestList == null || llPcInitiatedLspRequestList.size() == 0) { + log.error("Failed to create PcInitiatedLspRequestList"); + return; + } + + //build PCInitiate message + PcepInitiateMsg pcInitiateMsg = pc.factory().buildPcepInitiateMsg() + .setPcInitiatedLspRequestList(llPcInitiatedLspRequestList) + .build(); + + pc.sendMessage(Collections.singletonList(pcInitiateMsg)); + + pcepTunnelAPIMapper.addToTunnelRequestQueue(srpId, pcepTunnelData); + } catch (PcepParseException e) { + log.error("PcepParseException occurred while processing setup tunnel {}", e.getMessage()); + } + } + + /** + * To send Release tunnel message to pcc. + * + * @param tunnel mpls tunnel info + * @param pc pcep client to send message + */ + private void pcepReleaseTunnel(Tunnel tunnel, PcepClient pc) { + try { + PcepTunnelData pcepTunnelData = new PcepTunnelData(tunnel, RequestType.DELETE); + pcepTunnelAPIMapper.addToCoreTunnelRequestQueue(pcepTunnelData); + int srpId = SrpIdGenerators.create(); + TunnelId tunnelId = tunnel.tunnelId(); + int plspId = 0; + StatefulIPv4LspIdentidiersTlv statefulIpv4IndentifierTlv = null; + + if (!(pcepTunnelAPIMapper.checkFromTunnelDBQueue(tunnelId))) { + log.error("Tunnel doesnot exists. Tunnel id {}" + tunnelId.toString()); + return; + } else { + PcepTunnelData pcepTunnelDbData = pcepTunnelAPIMapper.getDataFromTunnelDBQueue(tunnelId); + plspId = pcepTunnelDbData.plspId(); + statefulIpv4IndentifierTlv = pcepTunnelDbData.statefulIpv4IndentifierTlv(); + } + // build srp object + PcepSrpObject srpobj = pc.factory().buildSrpObject().setSrpID(srpId).setRFlag(true).build(); + + PcepValueType tlv; + LinkedList<PcepValueType> llOptionalTlv = new LinkedList<PcepValueType>(); + LinkedList<PcInitiatedLspRequest> llPcInitiatedLspRequestList = new LinkedList<PcInitiatedLspRequest>(); + + if (statefulIpv4IndentifierTlv != null) { + tlv = statefulIpv4IndentifierTlv; + } else { + tlv = new StatefulIPv4LspIdentidiersTlv(( + ((IpTunnelEndPoint) tunnel.src()).ip().getIp4Address().toInt()), + (short) 0, (short) 0, 0, + (((IpTunnelEndPoint) tunnel.dst()).ip().getIp4Address().toInt())); + } + llOptionalTlv.add(tlv); + tlv = new SymbolicPathNameTlv(tunnel.tunnelName().value().getBytes()); + llOptionalTlv.add(tlv); + // build lsp object, set r flag as false to delete the tunnel + PcepLspObject lspobj = pc.factory().buildLspObject().setRFlag(false).setPlspId(plspId) + .setOptionalTlv(llOptionalTlv).build(); + + PcInitiatedLspRequest releaseLspRequest = pc.factory().buildPcInitiatedLspRequest().setSrpObject(srpobj) + .setLspObject(lspobj).build(); + + llPcInitiatedLspRequestList.add(releaseLspRequest); + + PcepInitiateMsg pcInitiateMsg = pc.factory().buildPcepInitiateMsg() + .setPcInitiatedLspRequestList(llPcInitiatedLspRequestList).build(); + + pc.sendMessage(Collections.singletonList(pcInitiateMsg)); + + pcepTunnelAPIMapper.addToTunnelRequestQueue(srpId, pcepTunnelData); + } catch (PcepParseException e) { + log.error("PcepParseException occurred while processing release tunnel {}", e.getMessage()); + } + } + + /** + * To send Update tunnel request message to pcc. + * + * @param tunnel mpls tunnel info + * @param path explicit route for the tunnel + * @param pc pcep client to send message + */ + private void pcepUpdateTunnel(Tunnel tunnel, Path path, PcepClient pc) { + try { + PcepTunnelData pcepTunnelData = new PcepTunnelData(tunnel, path, RequestType.UPDATE); + pcepTunnelAPIMapper.addToCoreTunnelRequestQueue(pcepTunnelData); + int srpId = SrpIdGenerators.create(); + TunnelId tunnelId = tunnel.tunnelId(); + PcepValueType tlv; + int plspId = 0; + + LinkedList<PcepValueType> llSubObjects = createPcepPath(path); + LinkedList<PcepValueType> llOptionalTlv = new LinkedList<PcepValueType>(); + LinkedList<PcepUpdateRequest> llUpdateRequestList = new LinkedList<PcepUpdateRequest>(); + + //build SRP object + PcepSrpObject srpobj = pc.factory().buildSrpObject().setSrpID(srpId).setRFlag(false).build(); + + if (!(pcepTunnelAPIMapper.checkFromTunnelDBQueue(tunnelId))) { + log.error("Tunnel doesnot exists in DB"); + return; + } else { + PcepTunnelData pcepTunnelDBData = pcepTunnelAPIMapper.getDataFromTunnelDBQueue(tunnelId); + plspId = pcepTunnelDBData.plspId(); + } + + tlv = new StatefulIPv4LspIdentidiersTlv((((IpTunnelEndPoint) tunnel.src()).ip().getIp4Address().toInt()), + (short) 0, (short) 0, 0, + (((IpTunnelEndPoint) tunnel.dst()).ip().getIp4Address().toInt())); + llOptionalTlv.add(tlv); + + if (tunnel.tunnelName().value() != null) { + tlv = new SymbolicPathNameTlv(tunnel.tunnelName().value().getBytes()); + llOptionalTlv.add(tlv); + } + + // build lsp object + PcepLspObject lspobj = pc.factory().buildLspObject().setAFlag(true).setPlspId(plspId) + .setOptionalTlv(llOptionalTlv).build(); + // build ero object + PcepEroObject eroobj = pc.factory().buildEroObject().setSubObjects(llSubObjects).build(); + + int iBandwidth = DEFAULT_BANDWIDTH_VALUE; + if (tunnel.annotations().value("bandwidth") != null) { + iBandwidth = Integer.parseInt(tunnel.annotations().value("bandwidth")); + } + // build bandwidth object + PcepBandwidthObject bandwidthObject = pc.factory().buildBandwidthObject().setBandwidth(iBandwidth).build(); + // build pcep attribute + PcepAttribute pcepAttribute = pc.factory().buildPcepAttribute().setBandwidthObject(bandwidthObject).build(); + // build pcep msg path + PcepMsgPath msgPath = pc.factory().buildPcepMsgPath().setEroObject(eroobj).setPcepAttribute(pcepAttribute) + .build(); + + PcepUpdateRequest updateRequest = pc.factory().buildPcepUpdateRequest().setSrpObject(srpobj) + .setLspObject(lspobj).setMsgPath(msgPath).build(); + + llUpdateRequestList.add(updateRequest); + + PcepUpdateMsg pcUpdateMsg = pc.factory().buildUpdateMsg().setUpdateRequestList(llUpdateRequestList).build(); + + pc.sendMessage(Collections.singletonList(pcUpdateMsg)); + pcepTunnelAPIMapper.addToTunnelRequestQueue(srpId, pcepTunnelData); + } catch (PcepParseException e) { + log.error("PcepParseException occurred while processing release tunnel {}", e.getMessage()); + } + } + + + + private class InnerTunnelProvider implements PcepTunnelListener, PcepEventListener, PcepClientListener { + + @Override + public void handlePCEPTunnel(PcepTunnel pcepTunnel) { + TunnelDescription tunnel = null; + // instance and id identify a tunnel together + String tunnelKey = String.valueOf(pcepTunnel.getInstance()) + + String.valueOf(pcepTunnel.id()); + + if (tunnelKey == null || "".equals(tunnelKey)) { + log.error("Invalid PCEP tunnel"); + return; + } + + TunnelId tunnelId = getTunnelId(tunnelKey); + + tunnel = buildOpticalTunnel(pcepTunnel, tunnelId); + + OperationType operType = pcepTunnel.getOperationType(); + switch (operType) { + case ADD: + tunnelId = service.tunnelAdded(tunnel); + tunnelMap.put(tunnelKey, tunnelId); + break; + + case UPDATE: + service.tunnelUpdated(tunnel); + break; + + case DELETE: + service.tunnelRemoved(tunnel); + tunnelMap.remove(tunnelKey); + break; + + default: + log.error("Invalid tunnel operation"); + } + } + + @Override + public void handleMessage(PccId pccId, PcepMessage msg) { + try { + log.debug("tunnel provider handle message {}", msg.getType().toString()); + switch (msg.getType()) { + case REPORT: + int srpId = 0; + LinkedList<PcepStateReport> llStateReportList = null; + llStateReportList = ((PcepReportMsg) msg).getStateReportList(); + ListIterator<PcepStateReport> listIterator = llStateReportList.listIterator(); + PcepSrpObject srpObj = null; + PcepLspObject lspObj = null; + while (listIterator.hasNext()) { + PcepStateReport stateRpt = listIterator.next(); + srpObj = stateRpt.getSrpObject(); + lspObj = stateRpt.getLspObject(); + + if (srpObj instanceof PcepSrpObject) { + srpId = srpObj.getSrpID(); + } + + log.debug("Plsp ID in handle message " + lspObj.getPlspId()); + log.debug("SRP ID in handle message " + srpId); + + if (!(pcepTunnelAPIMapper.checkFromTunnelRequestQueue(srpId))) { + + // Check the sync status + if (lspObj.getSFlag()) { + handleSyncReport(stateRpt); + } else if (!pcepClientController.getClient(pccId).isSyncComplete()) { + // sync is done + pcepClientController.getClient(pccId).setIsSyncComplete(true); + } + continue; + } + + handleReportMessage(srpId, lspObj); + } + break; + + default: + log.debug("Received unsupported message type {}", msg.getType().toString()); + } + } catch (Exception e) { + log.error("Exception occured while processing report message {}", e.getMessage()); + } + } + + /** + * Handles report message for setup/update/delete tunnel request. + * + * @param srpId unique identifier for pcep message + * @param lspObj lsp object + */ + private void handleReportMessage(int srpId, PcepLspObject lspObj) { + ProviderId providerId = new ProviderId("pcep", PROVIDER_ID); + PcepTunnelData pcepTunnelData = pcepTunnelAPIMapper.getDataFromTunnelRequestQueue(srpId); + SparseAnnotations annotations = (SparseAnnotations) pcepTunnelData.tunnel().annotations(); + + // store the values required from report message + pcepTunnelData.setPlspId(lspObj.getPlspId()); + pcepTunnelData.setLspAFlag(lspObj.getAFlag()); + pcepTunnelData.setLspOFlag(lspObj.getOFlag()); + pcepTunnelData.setLspDFlag(lspObj.getDFlag()); + + StatefulIPv4LspIdentidiersTlv ipv4LspTlv = null; + ListIterator<PcepValueType> listTlvIterator = lspObj.getOptionalTlv().listIterator(); + while (listTlvIterator.hasNext()) { + PcepValueType tlv = listTlvIterator.next(); + if (tlv.getType() == StatefulIPv4LspIdentidiersTlv.TYPE) { + ipv4LspTlv = (StatefulIPv4LspIdentidiersTlv) tlv; + break; + } + } + if (ipv4LspTlv != null) { + pcepTunnelData.setStatefulIpv4IndentifierTlv(ipv4LspTlv); + } + + Path path = pcepTunnelData.path(); + Tunnel tunnel = pcepTunnelData.tunnel(); + DefaultTunnelDescription td = new DefaultTunnelDescription(tunnel.tunnelId(), tunnel.src(), + tunnel.dst(), tunnel.type(), tunnel.groupId(), + providerId, tunnel.tunnelName(), path, + annotations); + + if (RequestType.CREATE == pcepTunnelData.requestType()) { + log.debug("Report received for create request"); + + pcepTunnelAPIMapper.handleCreateTunnelRequestQueue(srpId, pcepTunnelData); + if (0 == lspObj.getOFlag()) { + log.warn("The tunnel is in down state"); + } + tunnelAdded(td); + } + if (RequestType.DELETE == pcepTunnelData.requestType()) { + log.debug("Report received for delete request"); + pcepTunnelAPIMapper.handleRemoveFromTunnelRequestQueue(srpId, pcepTunnelData); + tunnelRemoved(td); + } + + if (RequestType.UPDATE == pcepTunnelData.requestType()) { + log.debug("Report received for update request"); + pcepTunnelData.setRptFlag(true); + pcepTunnelAPIMapper.addToTunnelIdMap(pcepTunnelData); + pcepTunnelAPIMapper.handleUpdateTunnelRequestQueue(srpId, pcepTunnelData); + + if (0 == lspObj.getOFlag()) { + log.warn("The tunnel is in down state"); + } + if (!(pcepTunnelAPIMapper.checkFromTunnelRequestQueue(srpId))) { + tunnelUpdated(td); + } + } + } + + /** + * Handles sync report received from pcc. + * + * @param stateRpt pcep state report + */ + private void handleSyncReport(PcepStateReport stateRpt) { + PcepLspObject lspObj = stateRpt.getLspObject(); + PcepStateReport.PcepMsgPath msgPath = stateRpt.getMsgPath(); + checkNotNull(msgPath); + PcepRroObject rroObj = msgPath.getRroObject(); + if (rroObj == null) { + log.debug("RRO object is null in sate report"); + return; + } + int bandwidth = 0; + + log.debug("Handle Sync report received from PCC."); + + if (0 == lspObj.getOFlag()) { + log.warn("The PCC reported tunnel is in down state"); + } + log.debug("Sync report received"); + + if (msgPath.getBandwidthObject() != null) { + bandwidth = msgPath.getBandwidthObject().getBandwidth(); + } + + buildAndStorePcepTunnelData(lspObj, rroObj, bandwidth); + } + + /** + * To build Path in network from RRO object. + * + * @param rroObj rro object + * @param providerId provider id + * @return path object + */ + private Path buildPathFromRroObj(PcepRroObject rroObj, ProviderId providerId) { + checkNotNull(rroObj); + List<Link> links = new ArrayList<Link>(); + LinkedList<PcepValueType> llSubObj = rroObj.getSubObjects(); + if (0 == llSubObj.size()) { + log.error("RRO in report message does not have hop information"); + } + ListIterator<PcepValueType> tlvIterator = llSubObj.listIterator(); + + ConnectPoint src = null; + ConnectPoint dst = null; + boolean isSrcSet = false; + while (tlvIterator.hasNext()) { + PcepValueType subObj = tlvIterator.next(); + switch (subObj.getType()) { + + case IPv4SubObject.TYPE: + + IPv4SubObject ipv4SubObj = (IPv4SubObject) subObj; + if (!isSrcSet) { + IpAddress srcIp = IpAddress.valueOf(ipv4SubObj.getIpAddress()); + src = new ConnectPoint(IpElementId.ipElement(srcIp), PortNumber.portNumber(0)); + isSrcSet = true; + } else { + IpAddress dstIp = IpAddress.valueOf(ipv4SubObj.getIpAddress()); + dst = new ConnectPoint(IpElementId.ipElement(dstIp), PortNumber.portNumber(0)); + Link link = new DefaultLink(providerId, src, dst, Link.Type.DIRECT, EMPTY); + links.add(link); + src = dst; + } + break; + default: + // the other sub objects are not required + } + } + return new DefaultPath(providerId, links, 0, EMPTY); + } + + /** + * To build pcepTunnelData and informs core about the pcc reported tunnel. + * + * @param lspObj pcep lsp object + * @param rroObj pcep rro object + * @param bandwidth bandwidth of tunnel + */ + private void buildAndStorePcepTunnelData(PcepLspObject lspObj, PcepRroObject rroObj, + int bandwidth) { + + ProviderId providerId = new ProviderId("pcep", PROVIDER_ID); + + // StatefulIPv4LspIdentidiersTlv in LSP object will have the source and destination address. + StatefulIPv4LspIdentidiersTlv lspIdenTlv = null; + SymbolicPathNameTlv pathNameTlv = null; + LinkedList<PcepValueType> llOptionalTlv = lspObj.getOptionalTlv(); + ListIterator<PcepValueType> listIterator = llOptionalTlv.listIterator(); + while (listIterator.hasNext()) { + PcepValueType tlv = listIterator.next(); + switch (tlv.getType()) { + case StatefulIPv4LspIdentidiersTlv.TYPE: + lspIdenTlv = (StatefulIPv4LspIdentidiersTlv) tlv; + break; + case SymbolicPathNameTlv.TYPE: + pathNameTlv = (SymbolicPathNameTlv) tlv; + break; + default: + // currently this tlv is not required + } + } + + IpTunnelEndPoint tunnelEndPointSrc; + tunnelEndPointSrc = IpTunnelEndPoint.ipTunnelPoint(IpAddress.valueOf(lspIdenTlv.getIpv4IngressAddress())); + IpTunnelEndPoint tunnelEndPointDst; + tunnelEndPointDst = IpTunnelEndPoint.ipTunnelPoint(IpAddress.valueOf(lspIdenTlv.getIpv4EgressAddress())); + + Path path = buildPathFromRroObj(rroObj, providerId); + + SparseAnnotations annotations = DefaultAnnotations.builder() + .set("bandwidth", (new Integer(bandwidth)).toString()) + .build(); + + DefaultTunnelDescription td = new DefaultTunnelDescription(null, tunnelEndPointSrc, + tunnelEndPointDst, Tunnel.Type.MPLS, + new DefaultGroupId(0), providerId, + TunnelName.tunnelName(pathNameTlv.toString()), + path, annotations); + TunnelId tId = tunnelAdded(td); + + Tunnel tunnel = new DefaultTunnel(providerId, tunnelEndPointSrc, tunnelEndPointDst, Tunnel.Type.MPLS, + new DefaultGroupId(0), tId, + TunnelName.tunnelName(pathNameTlv.toString()), path, annotations); + + PcepTunnelData pcepTunnelData = new PcepTunnelData(tunnel, path, RequestType.LSP_STATE_RPT); + pcepTunnelData.setStatefulIpv4IndentifierTlv(lspIdenTlv); + pcepTunnelAPIMapper.addPccTunnelDB(pcepTunnelData); + pcepTunnelAPIMapper.addToTunnelIdMap(pcepTunnelData); + } + + @Override + public void clientConnected(PccId pccId) { + // TODO + } + + @Override + public void clientDisconnected(PccId pccId) { + // TODO + } + + + + @Override + public void handlePcepTunnelStatistics(PcepTunnelStatistics pcepTunnelStatistics) { + TunnelId id = getTunnelId(String.valueOf(pcepTunnelStatistics.id())); + TunnelStatistics tunnelStatistics = buildTunnelStatistics(pcepTunnelStatistics); + tunnelStatisticsMap.put(id, tunnelStatistics); + } + } + + @Override + public Tunnel tunnelQueryById(TunnelId tunnelId) { + return service.tunnelQueryById(tunnelId); + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/RequestType.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/RequestType.java new file mode 100644 index 00000000..51854451 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/RequestType.java @@ -0,0 +1,41 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +/** + * Enum of request types between pcc and pcep. + */ +public enum RequestType { + /** + * Specifies the request type for PCC is to create new tunnel. + */ + CREATE, + + /** + * Specifies the request type for PCC is to update existing tunnel. + */ + UPDATE, + + /** + * Specifies the request type for PCC is to delete existing tunnel. + */ + DELETE, + + /** + * Specifies the request type for PCC to report existing tunnel. + */ + LSP_STATE_RPT; +}
\ No newline at end of file diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/SrpIdGenerators.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/SrpIdGenerators.java new file mode 100644 index 00000000..5b5a5fb2 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/SrpIdGenerators.java @@ -0,0 +1,56 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; + +/** + * Unique Srp Id generator for pcep messages. + */ +public final class SrpIdGenerators { + + private static final Logger log = getLogger(SrpIdGenerators.class); + private static final AtomicInteger SRP_ID_GEN = new AtomicInteger(); + private static final int MAX_SRP_ID = 0x7FFFFFFF; + private static int srpId; + + /** + * Default constructor. + */ + private SrpIdGenerators() { + } + + /** + * Get the next srp id. + * + * @return srp id + */ + public static int create() { + do { + if (srpId >= MAX_SRP_ID) { + if (SRP_ID_GEN.get() >= MAX_SRP_ID) { + SRP_ID_GEN.set(0); + } + } + srpId = SRP_ID_GEN.incrementAndGet(); + } while (srpId > MAX_SRP_ID); + return srpId; + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/TunnelStatsCollector.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/TunnelStatsCollector.java new file mode 100644 index 00000000..39249c5c --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/TunnelStatsCollector.java @@ -0,0 +1,104 @@ +/* + * + * * Copyright 2014-2015 Open Networking Laboratory + * * + * * Licensed under the Apache License, Version 2.0 (the "License"); + * * you may not use this file except in compliance with the License. + * * You may obtain a copy of the License at + * * + * * http://www.apache.org/licenses/LICENSE-2.0 + * * + * * Unless required by applicable law or agreed to in writing, software + * * distributed under the License is distributed on an "AS IS" BASIS, + * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * * See the License for the specific language governing permissions and + * * limitations under the License. + * + */ + +package org.onosproject.provider.pcep.tunnel.impl; + + +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.jboss.netty.util.HashedWheelTimer; +import org.jboss.netty.util.Timeout; +import org.jboss.netty.util.TimerTask; +import org.onlab.util.Timer; +import org.onosproject.pcep.api.PcepController; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.TimeUnit; + +/* + * Sends Stats Request and collect the tunnel statistics with a time interval. + */ +public class TunnelStatsCollector implements TimerTask { + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected PcepController controller; + + private int refreshInterval; + private final HashedWheelTimer timer = Timer.getTimer(); + + private String pcepTunnelId; + private Timeout timeout; + private volatile boolean stopped; + + + /** + * Greate a tunnel status collector object. + * + * @param id tunnel whose status data will be collected + * @param refreshInterval time interval for collecting statistic + */ + public TunnelStatsCollector(String id, int refreshInterval) { + this.pcepTunnelId = id; + this.refreshInterval = refreshInterval; + } + + @Override + public void run(Timeout timeout) throws Exception { + if (stopped || timeout.isCancelled()) { + return; + } + log.trace("Collecting stats for {}", pcepTunnelId); + + sendTunnelStatistic(); + if (!stopped && !timeout.isCancelled()) { + log.trace("Scheduling stats collection in {} seconds for {}", + this.refreshInterval, pcepTunnelId); + timeout.getTimer().newTimeout(this, refreshInterval, TimeUnit.SECONDS); + } + + } + + private void sendTunnelStatistic() { + controller.getTunnelStatistics(pcepTunnelId); + + } + + synchronized void adjustPollInterval(int pollInterval) { + this.refreshInterval = pollInterval; + } + + /** + * Starts the collector. + */ + public synchronized void start() { + log.info("Starting Tunnel Stats collection thread for {}", pcepTunnelId); + stopped = false; + timeout = timer.newTimeout(this, 1, TimeUnit.SECONDS); + } + + /** + * Stops the collector. + */ + public synchronized void stop() { + log.info("Stopping Tunnel Stats collection thread for {}", pcepTunnelId); + stopped = true; + timeout.cancel(); + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/package-info.java b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/package-info.java new file mode 100644 index 00000000..5074eecb --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/main/java/org/onosproject/provider/pcep/tunnel/impl/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + *Provider that uses PCEP controller as a means of infrastructure tunnel discovery. + */ +package org.onosproject.provider.pcep.tunnel.impl;
\ No newline at end of file diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepClientAdapter.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepClientAdapter.java new file mode 100644 index 00000000..49e29514 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepClientAdapter.java @@ -0,0 +1,92 @@ +package org.onosproject.provider.pcep.tunnel.impl; + +import static org.junit.Assert.assertNotNull; + +import java.util.List; +import java.util.concurrent.RejectedExecutionException; + +import org.jboss.netty.channel.Channel; +import org.onosproject.pcep.controller.PccId; +import org.onosproject.pcep.controller.PcepClient; +import org.onosproject.pcepio.protocol.PcepFactories; +import org.onosproject.pcepio.protocol.PcepFactory; +import org.onosproject.pcepio.protocol.PcepMessage; +import org.onosproject.pcepio.protocol.PcepVersion; + +public class PcepClientAdapter implements PcepClient { + + private Channel channel; + protected String channelId; + + private boolean connected; + private PccId pccId; + + private PcepVersion pcepVersion; + + public void init(PccId pccId, PcepVersion pcepVersion) { + this.pccId = pccId; + this.pcepVersion = pcepVersion; + } + + @Override + public final void disconnectClient() { + this.channel.close(); + } + + @Override + public final void sendMessage(PcepMessage m) { + } + + @Override + public final void sendMessage(List<PcepMessage> msgs) { + try { + PcepMessage pcepMsg = msgs.get(0); + assertNotNull("PCEP MSG should be created.", pcepMsg); + } catch (RejectedExecutionException e) { + throw e; + } + } + + @Override + public final boolean isConnected() { + return this.connected; + } + + @Override + public String channelId() { + return channelId; + } + + @Override + public final PccId getPccId() { + return this.pccId; + }; + + @Override + public final String getStringId() { + return this.pccId.toString(); + } + + @Override + public final void handleMessage(PcepMessage m) { + } + + @Override + public boolean isOptical() { + return false; + } + + @Override + public PcepFactory factory() { + return PcepFactories.getFactory(pcepVersion); + } + + @Override + public final boolean isSyncComplete() { + return false; + } + + @Override + public final void setIsSyncComplete(boolean value) { + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepClientControllerAdapter.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepClientControllerAdapter.java new file mode 100644 index 00000000..74aa590c --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepClientControllerAdapter.java @@ -0,0 +1,189 @@ +package org.onosproject.provider.pcep.tunnel.impl; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Deactivate; +import org.onlab.packet.IpAddress; +import org.onosproject.pcep.controller.PccId; +import org.onosproject.pcep.controller.PcepClient; +import org.onosproject.pcep.controller.PcepClientController; +import org.onosproject.pcep.controller.PcepClientListener; +import org.onosproject.pcep.controller.PcepEventListener; +import org.onosproject.pcep.controller.driver.PcepAgent; +import org.onosproject.pcepio.protocol.PcepMessage; +import org.onosproject.pcepio.protocol.PcepVersion; + +import com.google.common.collect.Sets; + +public class PcepClientControllerAdapter implements PcepClientController { + + protected ConcurrentHashMap<PccId, PcepClient> connectedClients = + new ConcurrentHashMap<PccId, PcepClient>(); + + protected PcepClientAgent agent = new PcepClientAgent(); + protected Set<PcepClientListener> pcepClientListener = new HashSet<>(); + + protected Set<PcepEventListener> pcepEventListener = Sets.newHashSet(); + + @Activate + public void activate() { + } + + @Deactivate + public void deactivate() { + } + + @Override + public Collection<PcepClient> getClients() { + return connectedClients.values(); + } + + @Override + public PcepClient getClient(PccId pccId) { + //return connectedClients.get(pccIpAddress); + PcepClientAdapter pc = new PcepClientAdapter(); + pc.init(PccId.pccId(IpAddress.valueOf(0xac000001)), PcepVersion.PCEP_1); + return pc; + } + + @Override + public void addListener(PcepClientListener listener) { + if (!pcepClientListener.contains(listener)) { + this.pcepClientListener.add(listener); + } + } + + @Override + public void removeListener(PcepClientListener listener) { + this.pcepClientListener.remove(listener); + } + + @Override + public void addEventListener(PcepEventListener listener) { + pcepEventListener.add(listener); + } + + @Override + public void removeEventListener(PcepEventListener listener) { + pcepEventListener.remove(listener); + } + + @Override + public void writeMessage(PccId pccId, PcepMessage msg) { + this.getClient(pccId).sendMessage(msg); + } + + @Override + public void processClientMessage(PccId pccId, PcepMessage msg) { + + PcepClient pc = getClient(pccId); + + switch (msg.getType()) { + case NONE: + break; + case OPEN: + break; + case KEEP_ALIVE: + //log.debug("Sending Keep Alive Message to {" + pccIpAddress.toString() + "}"); + pc.sendMessage(Collections.singletonList(pc.factory().buildKeepaliveMsg().build())); + break; + case PATH_COMPUTATION_REQUEST: + break; + case PATH_COMPUTATION_REPLY: + break; + case NOTIFICATION: + break; + case ERROR: + break; + case CLOSE: + //log.debug("Sending Close Message to { }", pccIpAddress.toString()); + pc.sendMessage(Collections.singletonList(pc.factory().buildCloseMsg().build())); + break; + case REPORT: + for (PcepEventListener l : pcepEventListener) { + l.handleMessage(pccId, msg); + } + break; + case UPDATE: + for (PcepEventListener l : pcepEventListener) { + l.handleMessage(pccId, msg); + } + break; + case INITIATE: + for (PcepEventListener l : pcepEventListener) { + l.handleMessage(pccId, msg); + } + break; + case LABEL_UPDATE: + break; + case MAX: + break; + case END: + break; + default: + break; + } + } + + @Override + public void closeConnectedClients() { + PcepClient pc; + for (PccId id : connectedClients.keySet()) { + pc = getClient(id); + pc.disconnectClient(); + } + } + + /** + * Implementation of an Pcep Agent which is responsible for + * keeping track of connected clients and the state in which + * they are. + */ + public class PcepClientAgent implements PcepAgent { + + @Override + public boolean addConnectedClient(PccId pccId, PcepClient pc) { + + if (connectedClients.get(pccId) != null) { + return false; + } else { + connectedClients.put(pccId, pc); + for (PcepClientListener l : pcepClientListener) { + l.clientConnected(pccId); + } + return true; + } + } + + @Override + public boolean validActivation(PccId pccId) { + if (connectedClients.get(pccId) == null) { + //log.error("Trying to activate client but is not in " + // + "connected switches: pccIp {}. Aborting ..", pccIpAddress.toString()); + return false; + } + + return true; + } + + @Override + public void removeConnectedClient(PccId pccId) { + connectedClients.remove(pccId); + for (PcepClientListener l : pcepClientListener) { + //log.warn("removal for {}", pccIpAddress.toString()); + l.clientDisconnected(pccId); + } + } + + @Override + public void processPcepMessage(PccId pccId, PcepMessage m) { + processClientMessage(pccId, m); + } + } + +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepControllerAdapter.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepControllerAdapter.java new file mode 100644 index 00000000..65266116 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepControllerAdapter.java @@ -0,0 +1,85 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.onosproject.provider.pcep.tunnel.impl; + +import org.onosproject.net.DeviceId; +import org.onosproject.pcep.api.PcepController; +import org.onosproject.pcep.api.PcepDpid; +import org.onosproject.pcep.api.PcepLinkListener; +import org.onosproject.pcep.api.PcepSwitch; +import org.onosproject.pcep.api.PcepSwitchListener; +import org.onosproject.pcep.api.PcepTunnel; +import org.onosproject.pcep.api.PcepTunnelListener; + +public class PcepControllerAdapter implements PcepController { + + @Override + public Iterable<PcepSwitch> getSwitches() { + return null; + } + + @Override + public PcepSwitch getSwitch(PcepDpid did) { + return null; + } + + @Override + public void addListener(PcepSwitchListener listener) { + + } + + @Override + public void removeListener(PcepSwitchListener listener) { + } + + @Override + public void addLinkListener(PcepLinkListener listener) { + } + + @Override + public void removeLinkListener(PcepLinkListener listener) { + } + + @Override + public void addTunnelListener(PcepTunnelListener listener) { + } + + @Override + public void removeTunnelListener(PcepTunnelListener listener) { + } + + @Override + public PcepTunnel applyTunnel(DeviceId srcDid, DeviceId dstDid, long srcPort, long dstPort, long bandwidth, + String name) { + return null; + } + + @Override + public Boolean deleteTunnel(String id) { + return null; + } + + @Override + public Boolean updateTunnelBandwidth(String id, long bandwidth) { + return null; + } + + @Override + public void getTunnelStatistics(String pcepTunnelId) { + + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepReleaseTunnelProviderTest.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepReleaseTunnelProviderTest.java new file mode 100644 index 00000000..e3861381 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepReleaseTunnelProviderTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import static org.onosproject.net.DefaultAnnotations.EMPTY; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.incubator.net.tunnel.DefaultTunnel; +import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.IpElementId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.pcepio.types.StatefulIPv4LspIdentidiersTlv; + + +public class PcepReleaseTunnelProviderTest { + + static final String PROVIDER_ID = "org.onosproject.provider.tunnel.pcep"; + PcepTunnelProvider tunnelProvider = new PcepTunnelProvider(); + private final TunnelProviderRegistryAdapter registry = new TunnelProviderRegistryAdapter(); + private final PcepClientControllerAdapter controller = new PcepClientControllerAdapter(); + private final PcepControllerAdapter ctl = new PcepControllerAdapter(); + private final PcepTunnelApiMapper pcepTunnelAPIMapper = new PcepTunnelApiMapper(); + private final TunnelServiceAdapter tunnelService = new TunnelServiceAdapter(); + + @Test + public void testCasePcepReleaseTunnel() { + tunnelProvider.tunnelProviderRegistry = registry; + tunnelProvider.pcepClientController = controller; + tunnelProvider.controller = ctl; + tunnelProvider.tunnelService = tunnelService; + tunnelProvider.pcepTunnelAPIMapper = pcepTunnelAPIMapper; + tunnelProvider.cfgService = new ComponentConfigAdapter(); + tunnelProvider.activate(); + + Tunnel tunnel; + Path path; + List<Link> links = new ArrayList<Link>(); + + ProviderId pid = new ProviderId("pcep", PROVIDER_ID); + + IpAddress srcIp = IpAddress.valueOf(0xB6024E20); + IpElementId srcElementId = IpElementId.ipElement(srcIp); + + IpAddress dstIp = IpAddress.valueOf(0xB6024E21); + IpElementId dstElementId = IpElementId.ipElement(dstIp); + + IpTunnelEndPoint ipTunnelEndPointSrc; + ipTunnelEndPointSrc = IpTunnelEndPoint.ipTunnelPoint(srcIp); + + IpTunnelEndPoint ipTunnelEndPointDst; + ipTunnelEndPointDst = IpTunnelEndPoint.ipTunnelPoint(dstIp); + + ConnectPoint src = new ConnectPoint(srcElementId, PortNumber.portNumber(10023)); + + ConnectPoint dst = new ConnectPoint(dstElementId, PortNumber.portNumber(10023)); + + Link link = new DefaultLink(pid, src, dst, Link.Type.DIRECT, EMPTY); + links.add(link); + + path = new DefaultPath(pid, links, 20, EMPTY); + + tunnel = new DefaultTunnel(pid, ipTunnelEndPointSrc, ipTunnelEndPointDst, Tunnel.Type.MPLS, + new DefaultGroupId(0), TunnelId.valueOf(1), TunnelName.tunnelName("T123"), + path, EMPTY); + + // for releasing tunnel tunnel should exist in db + PcepTunnelData pcepTunnelData = new PcepTunnelData(tunnel, path, RequestType.DELETE); + pcepTunnelData.setPlspId(1); + StatefulIPv4LspIdentidiersTlv tlv = new StatefulIPv4LspIdentidiersTlv(0, (short) 1, (short) 2, 3, 4); + pcepTunnelData.setStatefulIpv4IndentifierTlv(tlv); + tunnelProvider.pcepTunnelAPIMapper.addToTunnelIdMap(pcepTunnelData); + + tunnelProvider.pcepTunnelAPIMapper.handleCreateTunnelRequestQueue(1, pcepTunnelData); + + tunnelProvider.releaseTunnel(tunnel); + } + + + @After + public void tearDown() throws IOException { + tunnelProvider.deactivate(); + tunnelProvider.controller = null; + tunnelProvider.pcepClientController = null; + tunnelProvider.tunnelProviderRegistry = null; + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepSetupTunnelProviderTest.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepSetupTunnelProviderTest.java new file mode 100644 index 00000000..ef4816ec --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepSetupTunnelProviderTest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import static org.onosproject.net.DefaultAnnotations.EMPTY; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.incubator.net.tunnel.DefaultTunnel; +import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.IpElementId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; + +public class PcepSetupTunnelProviderTest { + + static final String PROVIDER_ID = "org.onosproject.provider.tunnel.pcep"; + PcepTunnelProvider tunnelProvider = new PcepTunnelProvider(); + private final TunnelProviderRegistryAdapter registry = new TunnelProviderRegistryAdapter(); + private final PcepClientControllerAdapter controller = new PcepClientControllerAdapter(); + private final PcepControllerAdapter ctl = new PcepControllerAdapter(); + private final TunnelServiceAdapter tunnelService = new TunnelServiceAdapter(); + + @Test + public void testCasePcepSetupTunnel() { + + tunnelProvider.tunnelProviderRegistry = registry; + tunnelProvider.pcepClientController = controller; + tunnelProvider.controller = ctl; + tunnelProvider.cfgService = new ComponentConfigAdapter(); + tunnelProvider.tunnelService = tunnelService; + tunnelProvider.activate(); + + + Tunnel tunnel; + Path path; + ProviderId pid = new ProviderId("pcep", PROVIDER_ID); + List<Link> links = new ArrayList<Link>(); + IpAddress srcIp = IpAddress.valueOf(0xC010101); + IpElementId srcElementId = IpElementId.ipElement(srcIp); + + IpAddress dstIp = IpAddress.valueOf(0xC010102); + IpElementId dstElementId = IpElementId.ipElement(dstIp); + + IpTunnelEndPoint ipTunnelEndPointSrc; + ipTunnelEndPointSrc = IpTunnelEndPoint.ipTunnelPoint(srcIp); + + IpTunnelEndPoint ipTunnelEndPointDst; + ipTunnelEndPointDst = IpTunnelEndPoint.ipTunnelPoint(dstIp); + + ConnectPoint src = new ConnectPoint(srcElementId, PortNumber.portNumber(10023)); + + ConnectPoint dst = new ConnectPoint(dstElementId, PortNumber.portNumber(10023)); + + Link link = new DefaultLink(pid, src, dst, Link.Type.DIRECT, EMPTY); + links.add(link); + + path = new DefaultPath(pid, links, 10, EMPTY); + + tunnel = new DefaultTunnel(pid, ipTunnelEndPointSrc, ipTunnelEndPointDst, Tunnel.Type.MPLS, + new DefaultGroupId(0), TunnelId.valueOf(1), TunnelName.tunnelName("T123"), + path, EMPTY); + + tunnelProvider.setupTunnel(tunnel, path); + } + + @After + public void tearDown() throws IOException { + tunnelProvider.deactivate(); + tunnelProvider.controller = null; + tunnelProvider.pcepClientController = null; + tunnelProvider.tunnelProviderRegistry = null; + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelProviderTest.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelProviderTest.java new file mode 100644 index 00000000..0fcd1447 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepTunnelProviderTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import static org.onosproject.net.DefaultAnnotations.EMPTY; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.incubator.net.tunnel.DefaultTunnel; +import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.IpElementId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.cfg.ComponentConfigAdapter; + +public class PcepTunnelProviderTest { + + static final String PROVIDER_ID = "org.onosproject.provider.tunnel.pcep"; + PcepTunnelProvider tunnelProvider = new PcepTunnelProvider(); + private final TunnelProviderRegistryAdapter registry = new TunnelProviderRegistryAdapter(); + private final PcepClientControllerAdapter controller = new PcepClientControllerAdapter(); + private final PcepControllerAdapter ctl = new PcepControllerAdapter(); + private final TunnelServiceAdapter tunnelService = new TunnelServiceAdapter(); + + @Test + public void testCasePcepSetupTunnel() { + + tunnelProvider.tunnelProviderRegistry = registry; + tunnelProvider.pcepClientController = controller; + tunnelProvider.controller = ctl; + tunnelProvider.cfgService = new ComponentConfigAdapter(); + tunnelProvider.tunnelService = tunnelService; + tunnelProvider.activate(); + + Tunnel tunnel; + Path path; + ProviderId pid = new ProviderId("pcep", PROVIDER_ID); + List<Link> links = new ArrayList<Link>(); + IpAddress srcIp = IpAddress.valueOf(0xC010101); + IpElementId srcElementId = IpElementId.ipElement(srcIp); + + IpAddress dstIp = IpAddress.valueOf(0xC010102); + IpElementId dstElementId = IpElementId.ipElement(dstIp); + + IpTunnelEndPoint ipTunnelEndPointSrc; + ipTunnelEndPointSrc = IpTunnelEndPoint.ipTunnelPoint(srcIp); + + IpTunnelEndPoint ipTunnelEndPointDst; + ipTunnelEndPointDst = IpTunnelEndPoint.ipTunnelPoint(dstIp); + + ConnectPoint src = new ConnectPoint(srcElementId, PortNumber.portNumber(10023)); + + ConnectPoint dst = new ConnectPoint(dstElementId, PortNumber.portNumber(10023)); + + Link link = new DefaultLink(pid, src, dst, Link.Type.DIRECT, EMPTY); + links.add(link); + + path = new DefaultPath(pid, links, 10, EMPTY); + + tunnel = new DefaultTunnel(pid, ipTunnelEndPointSrc, ipTunnelEndPointDst, Tunnel.Type.MPLS, + new DefaultGroupId(0), TunnelId.valueOf(1), TunnelName.tunnelName("T123"), + path, EMPTY); + + tunnelProvider.setupTunnel(tunnel, path); + } + + @After + public void tearDown() throws IOException { + tunnelProvider.deactivate(); + tunnelProvider.controller = null; + tunnelProvider.pcepClientController = null; + tunnelProvider.tunnelProviderRegistry = null; + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepUpdateTunnelProviderTest.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepUpdateTunnelProviderTest.java new file mode 100644 index 00000000..1bcf99dd --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/PcepUpdateTunnelProviderTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 2014 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.provider.pcep.tunnel.impl; + +import static org.onosproject.net.DefaultAnnotations.EMPTY; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.junit.After; +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.cfg.ComponentConfigAdapter; +import org.onosproject.core.DefaultGroupId; +import org.onosproject.incubator.net.tunnel.DefaultTunnel; +import org.onosproject.incubator.net.tunnel.IpTunnelEndPoint; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.DefaultLink; +import org.onosproject.net.DefaultPath; +import org.onosproject.net.IpElementId; +import org.onosproject.net.Link; +import org.onosproject.net.Path; +import org.onosproject.net.PortNumber; +import org.onosproject.net.provider.ProviderId; +import org.onosproject.pcepio.types.StatefulIPv4LspIdentidiersTlv; + + +public class PcepUpdateTunnelProviderTest { + + static final String PROVIDER_ID = "org.onosproject.provider.tunnel.pcep"; + PcepTunnelProvider tunnelProvider = new PcepTunnelProvider(); + private final TunnelProviderRegistryAdapter registry = new TunnelProviderRegistryAdapter(); + private final PcepClientControllerAdapter controller = new PcepClientControllerAdapter(); + private final PcepControllerAdapter ctl = new PcepControllerAdapter(); + private final PcepTunnelApiMapper pcepTunnelAPIMapper = new PcepTunnelApiMapper(); + private final TunnelServiceAdapter tunnelService = new TunnelServiceAdapter(); + + + @Test + public void testCasePcepUpdateTunnel() { + tunnelProvider.tunnelProviderRegistry = registry; + tunnelProvider.pcepClientController = controller; + tunnelProvider.controller = ctl; + tunnelProvider.pcepTunnelAPIMapper = pcepTunnelAPIMapper; + tunnelProvider.cfgService = new ComponentConfigAdapter(); + tunnelProvider.tunnelService = tunnelService; + tunnelProvider.activate(); + + Tunnel tunnel; + Path path; + ProviderId pid = new ProviderId("pcep", PROVIDER_ID); + List<Link> links = new ArrayList<Link>(); + IpAddress srcIp = IpAddress.valueOf(0xD010101); + IpElementId srcElementId = IpElementId.ipElement(srcIp); + + IpAddress dstIp = IpAddress.valueOf(0xD010102); + IpElementId dstElementId = IpElementId.ipElement(dstIp); + + IpTunnelEndPoint ipTunnelEndPointSrc; + ipTunnelEndPointSrc = IpTunnelEndPoint.ipTunnelPoint(srcIp); + + IpTunnelEndPoint ipTunnelEndPointDst; + ipTunnelEndPointDst = IpTunnelEndPoint.ipTunnelPoint(dstIp); + + ConnectPoint src = new ConnectPoint(srcElementId, PortNumber.portNumber(10023)); + + ConnectPoint dst = new ConnectPoint(dstElementId, PortNumber.portNumber(10023)); + + Link link = new DefaultLink(pid, src, dst, Link.Type.DIRECT, EMPTY); + links.add(link); + + path = new DefaultPath(pid, links, 20, EMPTY); + + tunnel = new DefaultTunnel(pid, ipTunnelEndPointSrc, ipTunnelEndPointDst, Tunnel.Type.MPLS, + new DefaultGroupId(0), TunnelId.valueOf(1), TunnelName.tunnelName("T123"), + path, EMPTY); + + // for updating tunnel tunnel should exist in db + PcepTunnelData pcepTunnelData = new PcepTunnelData(tunnel, path, RequestType.UPDATE); + pcepTunnelData.setPlspId(1); + StatefulIPv4LspIdentidiersTlv tlv = new StatefulIPv4LspIdentidiersTlv(0, (short) 1, (short) 2, 3, 4); + pcepTunnelData.setStatefulIpv4IndentifierTlv(tlv); + tunnelProvider.pcepTunnelAPIMapper.addToTunnelIdMap(pcepTunnelData); + + tunnelProvider.pcepTunnelAPIMapper.handleCreateTunnelRequestQueue(1, pcepTunnelData); + + tunnelProvider.updateTunnel(tunnel, path); + } + + @After + public void tearDown() throws IOException { + tunnelProvider.deactivate(); + tunnelProvider.controller = null; + tunnelProvider.pcepClientController = null; + tunnelProvider.tunnelProviderRegistry = null; + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/TunnelProviderRegistryAdapter.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/TunnelProviderRegistryAdapter.java new file mode 100644 index 00000000..7292d0b1 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/TunnelProviderRegistryAdapter.java @@ -0,0 +1,56 @@ +package org.onosproject.provider.pcep.tunnel.impl; + +import java.util.Set; + +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelDescription; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelProvider; +import org.onosproject.incubator.net.tunnel.TunnelProviderRegistry; +import org.onosproject.incubator.net.tunnel.TunnelProviderService; +import org.onosproject.net.provider.ProviderId; + +public class TunnelProviderRegistryAdapter implements TunnelProviderRegistry { + TunnelProvider provider; + + @Override + public TunnelProviderService register(TunnelProvider provider) { + this.provider = provider; + return new TestProviderService(); + } + + @Override + public void unregister(TunnelProvider provider) { + } + + @Override + public Set<ProviderId> getProviders() { + return null; + } + + private class TestProviderService implements TunnelProviderService { + + @Override + public TunnelProvider provider() { + return null; + } + + @Override + public TunnelId tunnelAdded(TunnelDescription tunnel) { + return null; + } + + @Override + public void tunnelRemoved(TunnelDescription tunnel) { + } + + @Override + public void tunnelUpdated(TunnelDescription tunnel) { + } + + @Override + public Tunnel tunnelQueryById(TunnelId tunnelId) { + return null; + } + } +} diff --git a/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/TunnelServiceAdapter.java b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/TunnelServiceAdapter.java new file mode 100644 index 00000000..1dd77332 --- /dev/null +++ b/framework/src/onos/providers/pcep/tunnel/src/test/java/org/onosproject/provider/pcep/tunnel/impl/TunnelServiceAdapter.java @@ -0,0 +1,107 @@ +package org.onosproject.provider.pcep.tunnel.impl; + +import org.onosproject.core.ApplicationId; +import org.onosproject.incubator.net.tunnel.Tunnel; +import org.onosproject.incubator.net.tunnel.TunnelEndPoint; +import org.onosproject.incubator.net.tunnel.TunnelId; +import org.onosproject.incubator.net.tunnel.TunnelListener; +import org.onosproject.incubator.net.tunnel.TunnelName; +import org.onosproject.incubator.net.tunnel.TunnelService; +import org.onosproject.incubator.net.tunnel.TunnelSubscription; +import org.onosproject.net.Annotations; +import org.onosproject.net.DeviceId; + +import java.util.Collection; +import java.util.Collections; + +public class TunnelServiceAdapter implements TunnelService { + @Override + public Tunnel borrowTunnel(ApplicationId consumerId, TunnelId tunnelId, Annotations... annotations) { + return null; + } + + @Override + public Collection<Tunnel> borrowTunnel(ApplicationId consumerId, TunnelName tunnelName, + Annotations... annotations) { + return null; + } + + @Override + public Collection<Tunnel> borrowTunnel(ApplicationId consumerId, TunnelEndPoint src, TunnelEndPoint dst, + Annotations... annotations) { + return null; + } + + @Override + public Collection<Tunnel> borrowTunnel(ApplicationId consumerId, TunnelEndPoint src, TunnelEndPoint dst, + Tunnel.Type type, Annotations... annotations) { + return null; + } + + @Override + public boolean returnTunnel(ApplicationId consumerId, TunnelId tunnelId, Annotations... annotations) { + return false; + } + + @Override + public boolean returnTunnel(ApplicationId consumerId, TunnelName tunnelName, Annotations... annotations) { + return false; + } + + @Override + public boolean returnTunnel(ApplicationId consumerId, TunnelEndPoint src, TunnelEndPoint dst, + Tunnel.Type type, Annotations... annotations) { + return false; + } + + @Override + public boolean returnTunnel(ApplicationId consumerId, TunnelEndPoint src, TunnelEndPoint dst, + Annotations... annotations) { + return false; + } + + @Override + public Tunnel queryTunnel(TunnelId tunnelId) { + return null; + } + + @Override + public Collection<TunnelSubscription> queryTunnelSubscription(ApplicationId consumerId) { + return null; + } + + @Override + public Collection<Tunnel> queryTunnel(Tunnel.Type type) { + return null; + } + + @Override + public Collection<Tunnel> queryTunnel(TunnelEndPoint src, TunnelEndPoint dst) { + return null; + } + + @Override + public Collection<Tunnel> queryAllTunnels() { + return Collections.emptyList(); + } + + @Override + public int tunnelCount() { + return 0; + } + + @Override + public Iterable<Tunnel> getTunnels(DeviceId deviceId) { + return null; + } + + @Override + public void addListener(TunnelListener listener) { + + } + + @Override + public void removeListener(TunnelListener listener) { + + } +} diff --git a/framework/src/onos/providers/pom.xml b/framework/src/onos/providers/pom.xml new file mode 100644 index 00000000..87dd5656 --- /dev/null +++ b/framework/src/onos/providers/pom.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ Copyright 2014 Open Networking Laboratory + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.onosproject</groupId> + <artifactId>onos</artifactId> + <version>1.3.0-SNAPSHOT</version> + <relativePath>../pom.xml</relativePath> + </parent> + + <artifactId>onos-providers</artifactId> + <packaging>pom</packaging> + + <description>ONOS information providers and control/management protocol adapter</description> + + <modules> + <module>openflow</module> + <module>lldp</module> + <module>host</module> + <module>netconf</module> + <module>null</module> + <module>pcep</module> + <module>ovsdb</module> + </modules> + + <dependencies> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-api</artifactId> + </dependency> + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onos-incubator-api</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-misc</artifactId> + </dependency> + + <dependency> + <groupId>org.onosproject</groupId> + <artifactId>onlab-junit</artifactId> + </dependency> + + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.onosproject</groupId> + <artifactId>onos-maven-plugin</artifactId> + </plugin> + </plugins> + </build> + +</project> |