summaryrefslogtreecommitdiffstats
path: root/framework/src/onos/providers/host
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/providers/host
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/providers/host')
-rw-r--r--framework/src/onos/providers/host/pom.xml62
-rw-r--r--framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/HostLocationProvider.java439
-rw-r--r--framework/src/onos/providers/host/src/main/java/org/onosproject/provider/host/impl/package-info.java20
-rw-r--r--framework/src/onos/providers/host/src/test/java/org/onosproject/provider/host/impl/HostLocationProviderTest.java369
4 files changed, 890 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);
+ }
+
+ }
+}