Added ability to remove host by CLI and by the provider on device/port down events.

Change-Id: I28de4b6b5bbfb5a00f35e1808bcd916369d7d1a4
diff --git a/cli/src/main/java/org/onlab/onos/cli/net/HostRemoveCommand.java b/cli/src/main/java/org/onlab/onos/cli/net/HostRemoveCommand.java
new file mode 100644
index 0000000..5837a5e
--- /dev/null
+++ b/cli/src/main/java/org/onlab/onos/cli/net/HostRemoveCommand.java
@@ -0,0 +1,40 @@
+/*
+ * 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.onlab.onos.cli.net;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onlab.onos.cli.AbstractShellCommand;
+import org.onlab.onos.net.HostId;
+import org.onlab.onos.net.host.HostAdminService;
+
+/**
+ * Removes an end-station host.
+ */
+@Command(scope = "onos", name = "host-remove",
+         description = "Removes an end-station host")
+public class HostRemoveCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "id", description = "Host ID",
+              required = true, multiValued = false)
+    String id = null;
+
+    @Override
+    protected void execute() {
+        get(HostAdminService.class).removeHost(HostId.hostId(id));
+    }
+
+}
diff --git a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index 073d237..1621069 100644
--- a/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/cli/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -207,6 +207,13 @@
             <action class="org.onlab.onos.cli.net.HostsListCommand"/>
         </command>
         <command>
+            <action class="org.onlab.onos.cli.net.HostRemoveCommand"/>
+            <completers>
+                <ref component-id="hostIdCompleter"/>
+                <null/>
+            </completers>
+        </command>
+        <command>
             <action class="org.onlab.onos.cli.net.AddressBindingsListCommand"/>
         </command>
 
diff --git a/providers/host/pom.xml b/providers/host/pom.xml
index d217bd0..b156fee 100644
--- a/providers/host/pom.xml
+++ b/providers/host/pom.xml
@@ -26,7 +26,6 @@
         <relativePath>../pom.xml</relativePath>
     </parent>
 
-
     <artifactId>onos-host-provider</artifactId>
     <packaging>bundle</packaging>
 
@@ -38,8 +37,17 @@
             <classifier>tests</classifier>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.onlab.onos</groupId>
+            <artifactId>onlab-osgi</artifactId>
+            <version>${project.version}</version>
+            <classifier>tests</classifier>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
-
-
 </project>
diff --git a/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java b/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java
index 48762bb..8821d76 100644
--- a/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java
+++ b/providers/host/src/main/java/org/onlab/onos/provider/host/impl/HostLocationProvider.java
@@ -15,22 +15,27 @@
  */
 package org.onlab.onos.provider.host.impl;
 
-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.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.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
 import org.onlab.onos.net.Host;
 import org.onlab.onos.net.HostId;
 import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceService;
 import org.onlab.onos.net.host.DefaultHostDescription;
 import org.onlab.onos.net.host.HostDescription;
 import org.onlab.onos.net.host.HostProvider;
 import org.onlab.onos.net.host.HostProviderRegistry;
 import org.onlab.onos.net.host.HostProviderService;
+import org.onlab.onos.net.host.HostService;
 import org.onlab.onos.net.packet.PacketContext;
 import org.onlab.onos.net.packet.PacketProcessor;
 import org.onlab.onos.net.packet.PacketService;
@@ -42,8 +47,14 @@
 import org.onlab.packet.Ethernet;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.VlanId;
+import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 
+import java.util.Dictionary;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
 /**
  * Provider which uses an OpenFlow controller to detect network
  * end-station hosts.
@@ -62,9 +73,20 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected TopologyService topologyService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DeviceService deviceService;
+
     private HostProviderService providerService;
 
     private final InternalHostProvider processor = new InternalHostProvider();
+    private final DeviceListener deviceListener = new InternalDeviceListener();
+
+    @Property(name = "hostRemovalEnabled", boolValue = true,
+            label = "Enable host removal on port/device down events")
+    private boolean hostRemovalEnabled = true;
 
 
     /**
@@ -75,9 +97,11 @@
     }
 
     @Activate
-    public void activate() {
+    public void activate(ComponentContext context) {
+        modified(context);
         providerService = providerRegistry.register(this);
         pktService.addProcessor(processor, 1);
+        deviceService.addListener(deviceListener);
         log.info("Started");
     }
 
@@ -89,6 +113,20 @@
         log.info("Stopped");
     }
 
+    @Modified
+    public void modified(ComponentContext context) {
+        Dictionary properties = context.getProperties();
+        try {
+            String flag = (String) properties.get("hostRemovalEnabled");
+            if (flag != null) {
+                hostRemovalEnabled = flag.equals("true");
+            }
+        } catch (Exception e) {
+            hostRemovalEnabled = true;
+        }
+        log.info("Host removal is {}", hostRemovalEnabled ? "enabled" : "disabled");
+    }
+
     @Override
     public void triggerProbe(Host host) {
         log.info("Triggering probe on device {}", host);
@@ -120,8 +158,8 @@
             if (eth.getEtherType() == Ethernet.TYPE_ARP) {
                 ARP arp = (ARP) eth.getPayload();
                 IpAddress ip =
-                    IpAddress.valueOf(IpAddress.Version.INET,
-                                      arp.getSenderProtocolAddress());
+                        IpAddress.valueOf(IpAddress.Version.INET,
+                                          arp.getSenderProtocolAddress());
                 HostDescription hdescr =
                         new DefaultHostDescription(eth.getSourceMAC(), vlan, hloc, ip);
                 providerService.hostDetected(hid, hdescr);
@@ -135,4 +173,37 @@
             }
         }
     }
+
+    // Auxiliary listener to device events.
+    private class InternalDeviceListener implements DeviceListener {
+        @Override
+        public void event(DeviceEvent event) {
+            if (!hostRemovalEnabled) {
+                return;
+            }
+
+            DeviceEvent.Type type = event.type();
+            DeviceId deviceId = event.subject().id();
+            if (type == DeviceEvent.Type.PORT_UPDATED) {
+                ConnectPoint point = new ConnectPoint(deviceId, event.port().number());
+                removeHosts(hostService.getConnectedHosts(point));
+
+            } else if (type == DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED) {
+                if (!deviceService.isAvailable(deviceId)) {
+                    removeHosts(hostService.getConnectedHosts(deviceId));
+                }
+
+            } else if (type == DeviceEvent.Type.DEVICE_REMOVED) {
+                removeHosts(hostService.getConnectedHosts(deviceId));
+            }
+        }
+    }
+
+    // Signals host vanish for all specified hosts.
+    private void removeHosts(Set<Host> hosts) {
+        for (Host host : hosts) {
+            providerService.hostVanished(host.id());
+        }
+    }
+
 }
diff --git a/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java b/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java
index e42d657..00c467a 100644
--- a/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java
+++ b/providers/host/src/test/java/org/onlab/onos/provider/host/impl/HostLocationProviderTest.java
@@ -15,25 +15,28 @@
  */
 package org.onlab.onos.provider.host.impl;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import java.nio.ByteBuffer;
-import java.util.Set;
-
+import com.google.common.collect.ImmutableSet;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultDevice;
+import org.onlab.onos.net.DefaultHost;
+import org.onlab.onos.net.DefaultPort;
+import org.onlab.onos.net.Device;
 import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Host;
 import org.onlab.onos.net.HostId;
-import org.onlab.onos.net.PortNumber;
+import org.onlab.onos.net.HostLocation;
+import org.onlab.onos.net.device.DeviceEvent;
+import org.onlab.onos.net.device.DeviceListener;
+import org.onlab.onos.net.device.DeviceServiceAdapter;
 import org.onlab.onos.net.flow.TrafficTreatment;
 import org.onlab.onos.net.host.HostDescription;
 import org.onlab.onos.net.host.HostProvider;
 import org.onlab.onos.net.host.HostProviderRegistry;
 import org.onlab.onos.net.host.HostProviderService;
+import org.onlab.onos.net.host.HostServiceAdapter;
 import org.onlab.onos.net.packet.DefaultInboundPacket;
 import org.onlab.onos.net.packet.InboundPacket;
 import org.onlab.onos.net.packet.OutboundPacket;
@@ -43,13 +46,30 @@
 import org.onlab.onos.net.provider.AbstractProviderService;
 import org.onlab.onos.net.provider.ProviderId;
 import org.onlab.onos.net.topology.Topology;
-
 import org.onlab.onos.net.topology.TopologyServiceAdapter;
+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 java.nio.ByteBuffer;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+
+import static org.junit.Assert.*;
+import static org.onlab.onos.net.Device.Type.SWITCH;
+import static org.onlab.onos.net.DeviceId.deviceId;
+import static org.onlab.onos.net.HostId.hostId;
+import static org.onlab.onos.net.PortNumber.portNumber;
+import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED;
+import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_REMOVED;
+import static org.onlab.onos.net.device.DeviceEvent.Type.PORT_UPDATED;
+import static org.onlab.packet.VlanId.vlanId;
+
 public class HostLocationProviderTest {
 
     private static final Integer INPORT = 10;
@@ -57,14 +77,44 @@
     private static final String DEV2 = "of:2";
     private static final String DEV3 = "of:3";
 
-    private static final VlanId VLAN = VlanId.vlanId();
+    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 hostService = new TestHostRegistry();
+    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;
@@ -72,12 +122,13 @@
 
     @Before
     public void setUp() {
-        provider.providerRegistry = hostService;
+        provider.providerRegistry = hostRegistry;
         provider.topologyService = topoService;
         provider.pktService = packetService;
+        provider.deviceService = deviceService;
+        provider.hostService = hostService;
 
-        provider.activate();
-
+        provider.activate(CTX_FOR_NO_REMOVE);
     }
 
     @Test
@@ -89,8 +140,6 @@
     @Test
     public void events() {
         // new host
-
-
         testProcessor.process(new TestPacketContext(DEV1));
         assertNotNull("new host expected", providerService.added);
         assertNull("host motion unexpected", providerService.moved);
@@ -104,6 +153,39 @@
         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();
@@ -137,6 +219,7 @@
         DeviceId added = null;
         DeviceId moved = null;
         DeviceId spine = null;
+        public int removeCount;
 
         protected TestHostProviderService(HostProvider provider) {
             super(provider);
@@ -156,6 +239,7 @@
 
         @Override
         public void hostVanished(HostId hostId) {
+            removeCount++;
         }
 
     }
@@ -169,12 +253,10 @@
 
         @Override
         public void removeProcessor(PacketProcessor processor) {
-
         }
 
         @Override
         public void emit(OutboundPacket packet) {
-
         }
     }
 
@@ -184,7 +266,7 @@
         public boolean isInfrastructure(Topology topology,
                                         ConnectPoint connectPoint) {
             //simulate DPID3 as an infrastructure switch
-            if ((connectPoint.deviceId()).equals(DeviceId.deviceId(DEV3))) {
+            if ((connectPoint.deviceId()).equals(deviceId(DEV3))) {
                 return true;
             }
             return false;
@@ -218,8 +300,8 @@
                     .setSourceMACAddress(MAC.toBytes())
                     .setDestinationMACAddress(BCMAC)
                     .setPayload(arp);
-            ConnectPoint receivedFrom = new ConnectPoint(DeviceId.deviceId(deviceId),
-                                                         PortNumber.portNumber(INPORT));
+            ConnectPoint receivedFrom = new ConnectPoint(deviceId(deviceId),
+                                                         portNumber(INPORT));
             return new DefaultInboundPacket(receivedFrom, eth,
                                             ByteBuffer.wrap(eth.serialize()));
         }
@@ -249,4 +331,26 @@
             return false;
         }
     }
+
+    private class TestDeviceService extends DeviceServiceAdapter {
+        private DeviceListener listener;
+
+        @Override
+        public void addListener(DeviceListener listener) {
+            this.listener = listener;
+        }
+    }
+
+    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/utils/osgi/src/test/java/org/onlab/osgi/ComponentContextAdapter.java b/utils/osgi/src/test/java/org/onlab/osgi/ComponentContextAdapter.java
new file mode 100644
index 0000000..0278ccd
--- /dev/null
+++ b/utils/osgi/src/test/java/org/onlab/osgi/ComponentContextAdapter.java
@@ -0,0 +1,79 @@
+/*
+ * 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.onlab.osgi;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.ComponentContext;
+import org.osgi.service.component.ComponentInstance;
+
+import java.util.Dictionary;
+
+/**
+ * Adapter implementation of OSGI component context.
+ */
+public class ComponentContextAdapter implements ComponentContext {
+    @Override
+    public Dictionary getProperties() {
+        return null;
+    }
+
+    @Override
+    public Object locateService(String name) {
+        return null;
+    }
+
+    @Override
+    public Object locateService(String name, ServiceReference reference) {
+        return null;
+    }
+
+    @Override
+    public Object[] locateServices(String name) {
+        return new Object[0];
+    }
+
+    @Override
+    public BundleContext getBundleContext() {
+        return null;
+    }
+
+    @Override
+    public Bundle getUsingBundle() {
+        return null;
+    }
+
+    @Override
+    public ComponentInstance getComponentInstance() {
+        return null;
+    }
+
+    @Override
+    public void enableComponent(String name) {
+
+    }
+
+    @Override
+    public void disableComponent(String name) {
+
+    }
+
+    @Override
+    public ServiceReference getServiceReference() {
+        return null;
+    }
+}
diff --git a/utils/pom.xml b/utils/pom.xml
index f46bc61..29afe1a 100644
--- a/utils/pom.xml
+++ b/utils/pom.xml
@@ -46,6 +46,11 @@
             <groupId>com.google.guava</groupId>
             <artifactId>guava</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.osgi</groupId>
+            <artifactId>org.osgi.compendium</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <build>