Thomas Vachuska
Committed by Gerrit Code Review

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

Change-Id: I28de4b6b5bbfb5a00f35e1808bcd916369d7d1a4
/*
* 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));
}
}
......@@ -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>
......
......@@ -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>
......
......@@ -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.ARP;
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 @@ public class HostLocationProvider extends AbstractProvider implements HostProvid
@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 @@ public class HostLocationProvider extends AbstractProvider implements HostProvid
}
@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 @@ public class HostLocationProvider extends AbstractProvider implements HostProvid
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 @@ public class HostLocationProvider extends AbstractProvider implements HostProvid
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 @@ public class HostLocationProvider extends AbstractProvider implements HostProvid
}
}
}
// 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());
}
}
}
......
......@@ -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.packet.PacketService;
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 @@ public class HostLocationProviderTest {
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 @@ public class HostLocationProviderTest {
@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 @@ public class HostLocationProviderTest {
@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 @@ public class HostLocationProviderTest {
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 @@ public class HostLocationProviderTest {
DeviceId added = null;
DeviceId moved = null;
DeviceId spine = null;
public int removeCount;
protected TestHostProviderService(HostProvider provider) {
super(provider);
......@@ -156,6 +239,7 @@ public class HostLocationProviderTest {
@Override
public void hostVanished(HostId hostId) {
removeCount++;
}
}
......@@ -169,12 +253,10 @@ public class HostLocationProviderTest {
@Override
public void removeProcessor(PacketProcessor processor) {
}
@Override
public void emit(OutboundPacket packet) {
}
}
......@@ -184,7 +266,7 @@ public class HostLocationProviderTest {
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 @@ public class HostLocationProviderTest {
.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 @@ public class HostLocationProviderTest {
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);
}
}
}
......
/*
* 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;
}
}
......@@ -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>
......