org.onosproject.dhcprelay.DhcpRelayManagerTest.java Source code

Java tutorial

Introduction

Here is the source code for org.onosproject.dhcprelay.DhcpRelayManagerTest.java

Source

/*
 * Copyright 2017-present Open Networking Foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.onosproject.dhcprelay;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import com.google.common.io.Resources;
import org.apache.commons.io.Charsets;
import org.apache.commons.lang3.tuple.Pair;
import org.easymock.Capture;
import org.easymock.CaptureType;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.packet.ARP;
import org.onlab.packet.DHCP;
import org.onlab.packet.DeserializationException;
import org.onlab.packet.Ethernet;
import org.onlab.packet.IPv4;
import org.onlab.packet.Ip4Address;
import org.onlab.packet.Ip6Address;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
import org.onlab.packet.UDP;
import org.onlab.packet.VlanId;
import org.onlab.packet.dhcp.CircuitId;
import org.onlab.packet.dhcp.DhcpOption;
import org.onlab.packet.dhcp.DhcpRelayAgentOption;
import org.onlab.packet.dhcp.Dhcp6InterfaceIdOption;
import org.onlab.packet.dhcp.Dhcp6RelayOption;
import org.onlab.packet.dhcp.Dhcp6IaNaOption;
import org.onlab.packet.dhcp.Dhcp6IaPdOption;
import org.onlab.packet.dhcp.Dhcp6IaAddressOption;
import org.onlab.packet.dhcp.Dhcp6IaPrefixOption;
import org.onlab.packet.dhcp.Dhcp6Option;
import org.onlab.packet.dhcp.Dhcp6ClientIdOption;
import org.onlab.packet.dhcp.Dhcp6Duid;
import org.onosproject.dhcprelay.store.DhcpRelayStore;
import org.onosproject.dhcprelay.store.DhcpRecord;
import org.onosproject.dhcprelay.store.DhcpRelayStoreEvent;
import org.onosproject.dhcprelay.store.DhcpRelayCounters;
import org.onosproject.dhcprelay.store.DhcpRelayCountersStore;
import org.onosproject.net.ConnectPoint;
import org.onosproject.TestApplicationId;
import org.onosproject.cfg.ComponentConfigService;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.dhcprelay.config.DefaultDhcpRelayConfig;
import org.onosproject.dhcprelay.config.DhcpServerConfig;
import org.onosproject.dhcprelay.config.IgnoreDhcpConfig;
import org.onosproject.dhcprelay.config.IndirectDhcpRelayConfig;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.behaviour.Pipeliner;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.DefaultTrafficTreatment;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.flowobjective.FlowObjectiveService;
import org.onosproject.net.flowobjective.ForwardingObjective;
import org.onosproject.net.flowobjective.Objective;
import org.onosproject.net.host.HostProviderService;
import org.onosproject.net.intf.Interface;
import org.onosproject.net.intf.InterfaceServiceAdapter;
import org.onosproject.net.packet.PacketPriority;
import org.onosproject.routeservice.Route;
import org.onosproject.routeservice.RouteStoreAdapter;
import org.onosproject.net.DefaultHost;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.HostLocation;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.host.HostDescription;
import org.onosproject.net.host.HostService;
import org.onosproject.net.host.InterfaceIpAddress;
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.PacketContextAdapter;
import org.onosproject.net.packet.PacketProcessor;
import org.onosproject.net.packet.PacketServiceAdapter;
import org.onosproject.store.StoreDelegate;

import org.osgi.service.component.ComponentContext;
import org.onlab.packet.DHCP6;
import org.onlab.packet.IPv6;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
import static org.onosproject.dhcprelay.DhcpRelayManager.DHCP_RELAY_APP;

public class DhcpRelayManagerTest {
    private static final short VLAN_LEN = 2;
    private static final short SEPARATOR_LEN = 1;
    private static final String CONFIG_FILE_PATH = "dhcp-relay.json";
    private static final DeviceId DEV_1_ID = DeviceId.deviceId("of:0000000000000001");
    private static final DeviceId DEV_2_ID = DeviceId.deviceId("of:0000000000000002");
    // Ip address for interfaces
    private static final InterfaceIpAddress INTERFACE_IP = InterfaceIpAddress.valueOf("10.0.3.254/32");
    private static final InterfaceIpAddress INTERFACE_IP_V6 = InterfaceIpAddress.valueOf("2001:db8:1::254/128");
    private static final List<InterfaceIpAddress> INTERFACE_IPS = ImmutableList.of(INTERFACE_IP, INTERFACE_IP_V6);

    // DHCP client (will send without option 82)
    private static final Ip4Address IP_FOR_CLIENT = Ip4Address.valueOf("10.0.0.1");
    private static final Ip6Address IP_FOR_CLIENT_V6 = Ip6Address.valueOf("2001:db8:1::110");
    private static final IpPrefix PREFIX_FOR_CLIENT_V6 = IpPrefix.valueOf("2001:db8:10::/56");
    private static final IpPrefix PREFIX_FOR_ZERO = IpPrefix.valueOf("::/0");
    private static final MacAddress CLIENT_MAC = MacAddress.valueOf("00:00:00:00:00:01");
    private static final VlanId CLIENT_VLAN = VlanId.vlanId("100");
    private static final ConnectPoint CLIENT_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/1");
    private static final MacAddress CLIENT_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
    private static final HostLocation CLIENT_LOCATION = new HostLocation(CLIENT_CP, 0);
    private static final HostId CLIENT_HOST_ID = HostId.hostId(CLIENT_MAC, CLIENT_VLAN);
    private static final Ip6Address CLIENT_LL_IP_V6 = Ip6Address.valueOf("fe80::200:00ff:fe00:0001");
    private static final Host EXISTS_HOST = new DefaultHost(Dhcp4HandlerImpl.PROVIDER_ID, CLIENT_HOST_ID,
            CLIENT_MAC, CLIENT_VLAN, CLIENT_LOCATION, ImmutableSet.of(CLIENT_LL_IP_V6));
    private static final Interface CLIENT_INTERFACE = createInterface("C1", CLIENT_CP, INTERFACE_IPS,
            CLIENT_IFACE_MAC, CLIENT_VLAN, null);

    // Dual homing test
    private static final ConnectPoint CLIENT_DH_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/3");
    private static final HostLocation CLIENT_DH_LOCATION = new HostLocation(CLIENT_DH_CP, 0);
    private static final Interface CLIENT_DH_INTERFACE = createInterface("C1-DH", CLIENT_DH_CP, INTERFACE_IPS,
            CLIENT_IFACE_MAC, CLIENT_VLAN, null);

    // DHCP client 2 (will send with option 82, so the vlan should equals to vlan from server)
    private static final MacAddress CLIENT2_MAC = MacAddress.valueOf("00:00:00:00:00:01");
    private static final VlanId CLIENT2_VLAN = VlanId.NONE;
    private static final VlanId CLIENT2_VLAN_NATIVE = VlanId.vlanId("20");
    private static final ConnectPoint CLIENT2_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
    private static final MacAddress CLIENT2_IFACE_MAC = MacAddress.valueOf("00:00:00:00:11:01");
    private static final Interface CLIENT2_INTERFACE = createInterface("C2", CLIENT2_CP, INTERFACE_IPS,
            CLIENT2_IFACE_MAC, CLIENT2_VLAN, CLIENT2_VLAN_NATIVE);
    private static final VlanId CLIENT_BOGUS_VLAN = VlanId.vlanId("108");

    // Outer relay information
    private static final Ip4Address OUTER_RELAY_IP = Ip4Address.valueOf("10.0.6.253");
    private static final Ip6Address OUTER_RELAY_IP_V6 = Ip6Address.valueOf("2001:db8:1::4");
    private static final Ip6Address OUTER_RELAY_LL_IP_V6 = Ip6Address.valueOf("fe80::200:0102:0304:0501");
    private static final Set<IpAddress> OUTER_RELAY_IPS = ImmutableSet.of(OUTER_RELAY_IP, OUTER_RELAY_IP_V6,
            OUTER_RELAY_LL_IP_V6);
    private static final MacAddress OUTER_RELAY_MAC = MacAddress.valueOf("00:01:02:03:04:05");
    private static final VlanId OUTER_RELAY_VLAN = VlanId.NONE;
    private static final ConnectPoint OUTER_RELAY_CP = ConnectPoint.deviceConnectPoint("of:0000000000000001/2");
    private static final HostLocation OUTER_REPLAY_HL = new HostLocation(OUTER_RELAY_CP, 0);
    private static final HostId OUTER_RELAY_HOST_ID = HostId.hostId(OUTER_RELAY_MAC, OUTER_RELAY_VLAN);
    private static final Host OUTER_RELAY_HOST = new DefaultHost(Dhcp4HandlerImpl.PROVIDER_ID, OUTER_RELAY_HOST_ID,
            OUTER_RELAY_MAC, OUTER_RELAY_VLAN, OUTER_REPLAY_HL, OUTER_RELAY_IPS);

    // DHCP Server
    private static final MacAddress SERVER_MAC = MacAddress.valueOf("00:00:00:00:00:01");
    private static final VlanId SERVER_VLAN = VlanId.NONE;
    private static final VlanId SERVER_VLAN_NATIVE = VlanId.vlanId("10");
    private static final ConnectPoint SERVER_CONNECT_POINT = ConnectPoint
            .deviceConnectPoint("of:0000000000000001/5");
    private static final HostLocation SERVER_LOCATION = new HostLocation(SERVER_CONNECT_POINT, 0);
    private static final Ip4Address GATEWAY_IP = Ip4Address.valueOf("10.0.5.253");
    private static final Ip6Address GATEWAY_IP_V6 = Ip6Address.valueOf("2000::105:253");
    private static final Ip4Address SERVER_IP = Ip4Address.valueOf("10.0.3.253");
    private static final Ip6Address SERVER_IP_V6 = Ip6Address.valueOf("2000::103:253");
    private static final Ip6Address SERVER_IP_V6_MCAST = Ip6Address.valueOf("ff02::1:2");
    private static final Set<IpAddress> DHCP_SERVER_IPS = ImmutableSet.of(SERVER_IP, SERVER_IP_V6);
    private static final HostId SERVER_HOST_ID = HostId.hostId(SERVER_MAC, SERVER_VLAN);
    private static final Host SERVER_HOST = new DefaultHost(Dhcp4HandlerImpl.PROVIDER_ID, SERVER_HOST_ID,
            SERVER_MAC, SERVER_VLAN, SERVER_LOCATION, DHCP_SERVER_IPS);
    private static final MacAddress SERVER_IFACE_MAC = MacAddress.valueOf("00:00:00:00:00:01");
    private static final Interface SERVER_INTERFACE = createInterface("SERVER", SERVER_CONNECT_POINT, INTERFACE_IPS,
            SERVER_IFACE_MAC, SERVER_VLAN, SERVER_VLAN_NATIVE);

    // Relay agent config
    private static final Ip4Address RELAY_AGENT_IP = Ip4Address.valueOf("10.0.4.254");

    private static final List<TrafficSelector> DHCP_SELECTORS = buildClientDhcpSelectors();

    // Components
    private static final ApplicationId APP_ID = TestApplicationId.create(DhcpRelayManager.DHCP_RELAY_APP);
    private static final DefaultDhcpRelayConfig CONFIG = new MockDefaultDhcpRelayConfig();
    private static final IndirectDhcpRelayConfig CONFIG_INDIRECT = new MockIndirectDhcpRelayConfig();
    private static final Set<Interface> INTERFACES = ImmutableSet.of(CLIENT_INTERFACE, CLIENT2_INTERFACE,
            SERVER_INTERFACE, CLIENT_DH_INTERFACE);
    private static final String NON_ONOS_CID = "Non-ONOS circuit ID";
    private static final VlanId IGNORED_VLAN = VlanId.vlanId("100");
    private static final int IGNORE_CONTROL_PRIORITY = PacketPriority.CONTROL.priorityValue() + 1000;

    private DhcpRelayManager manager;
    private MockPacketService packetService;
    private MockRouteStore mockRouteStore;
    private MockDhcpRelayStore mockDhcpRelayStore;
    private MockDhcpRelayCountersStore mockDhcpRelayCountersStore;
    private HostProviderService mockHostProviderService;
    private FlowObjectiveService flowObjectiveService;
    private DeviceService deviceService;
    private Dhcp4HandlerImpl v4Handler;
    private Dhcp6HandlerImpl v6Handler;

    private static Interface createInterface(String name, ConnectPoint connectPoint,
            List<InterfaceIpAddress> interfaceIps, MacAddress macAddress, VlanId vlanId, VlanId vlanNative) {

        if (vlanId.equals(VlanId.NONE)) {
            return new Interface(name, connectPoint, interfaceIps, macAddress, vlanId, null, null, vlanNative);
        } else {
            return new Interface(name, connectPoint, interfaceIps, macAddress, vlanId, null,
                    ImmutableSet.of(vlanId), null);
        }
    }

    @Before
    public void setup() {
        manager = new DhcpRelayManager();
        manager.cfgService = createNiceMock(NetworkConfigRegistry.class);

        expect(manager.cfgService.getConfig(APP_ID, DefaultDhcpRelayConfig.class)).andReturn(CONFIG).anyTimes();

        expect(manager.cfgService.getConfig(APP_ID, IndirectDhcpRelayConfig.class)).andReturn(CONFIG_INDIRECT)
                .anyTimes();

        manager.coreService = createNiceMock(CoreService.class);
        expect(manager.coreService.registerApplication(anyString())).andReturn(APP_ID).anyTimes();

        manager.hostService = createNiceMock(HostService.class);

        expect(manager.hostService.getHostsByIp(OUTER_RELAY_IP_V6)).andReturn(ImmutableSet.of(OUTER_RELAY_HOST))
                .anyTimes();
        expect(manager.hostService.getHostsByIp(SERVER_IP)).andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
        expect(manager.hostService.getHostsByIp(SERVER_IP_V6)).andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
        expect(manager.hostService.getHostsByIp(GATEWAY_IP)).andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
        expect(manager.hostService.getHostsByIp(GATEWAY_IP_V6)).andReturn(ImmutableSet.of(SERVER_HOST)).anyTimes();
        expect(manager.hostService.getHostsByIp(CLIENT_LL_IP_V6)).andReturn(ImmutableSet.of(EXISTS_HOST))
                .anyTimes();

        expect(manager.hostService.getHost(OUTER_RELAY_HOST_ID)).andReturn(OUTER_RELAY_HOST).anyTimes();

        packetService = new MockPacketService();
        manager.packetService = packetService;
        manager.compCfgService = createNiceMock(ComponentConfigService.class);
        deviceService = createNiceMock(DeviceService.class);

        Device device = createNiceMock(Device.class);
        expect(device.is(Pipeliner.class)).andReturn(true).anyTimes();

        expect(deviceService.getDevice(DEV_1_ID)).andReturn(device).anyTimes();
        expect(deviceService.getDevice(DEV_2_ID)).andReturn(device).anyTimes();
        replay(deviceService, device);

        mockRouteStore = new MockRouteStore();
        mockDhcpRelayStore = new MockDhcpRelayStore();
        mockDhcpRelayCountersStore = new MockDhcpRelayCountersStore();

        manager.dhcpRelayStore = mockDhcpRelayStore;

        manager.deviceService = deviceService;

        manager.interfaceService = new MockInterfaceService();
        flowObjectiveService = EasyMock.niceMock(FlowObjectiveService.class);
        mockHostProviderService = createNiceMock(HostProviderService.class);
        v4Handler = new Dhcp4HandlerImpl();
        v4Handler.providerService = mockHostProviderService;
        v4Handler.dhcpRelayStore = mockDhcpRelayStore;
        v4Handler.hostService = manager.hostService;
        v4Handler.interfaceService = manager.interfaceService;
        v4Handler.packetService = manager.packetService;
        v4Handler.routeStore = mockRouteStore;
        v4Handler.coreService = createNiceMock(CoreService.class);
        v4Handler.flowObjectiveService = flowObjectiveService;
        v4Handler.appId = TestApplicationId.create(Dhcp4HandlerImpl.DHCP_V4_RELAY_APP);
        v4Handler.deviceService = deviceService;
        manager.v4Handler = v4Handler;

        v6Handler = new Dhcp6HandlerImpl();
        v6Handler.dhcpRelayStore = mockDhcpRelayStore;
        v6Handler.dhcpRelayCountersStore = mockDhcpRelayCountersStore;
        v6Handler.hostService = manager.hostService;
        v6Handler.interfaceService = manager.interfaceService;
        v6Handler.packetService = manager.packetService;
        v6Handler.routeStore = mockRouteStore;
        v6Handler.providerService = mockHostProviderService;
        v6Handler.coreService = createNiceMock(CoreService.class);
        v6Handler.flowObjectiveService = flowObjectiveService;
        v6Handler.appId = TestApplicationId.create(Dhcp6HandlerImpl.DHCP_V6_RELAY_APP);
        v6Handler.deviceService = deviceService;
        manager.v6Handler = v6Handler;

        // properties
        Dictionary<String, Object> dictionary = createNiceMock(Dictionary.class);
        expect(dictionary.get("arpEnabled")).andReturn(true).anyTimes();
        expect(dictionary.get("dhcpPollInterval")).andReturn(120).anyTimes();
        ComponentContext context = createNiceMock(ComponentContext.class);
        expect(context.getProperties()).andReturn(dictionary).anyTimes();

        replay(manager.cfgService, manager.coreService, manager.hostService, manager.compCfgService, dictionary,
                context);
        manager.activate(context);
    }

    @After
    public void tearDown() {
        manager.deactivate();
    }

    /**
     * Relay a DHCP packet without option 82.
     * Should add new host to host store after dhcp ack.
     */
    @Test
    public void relayDhcpWithoutAgentInfo() {
        replay(mockHostProviderService);
        // send request
        packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT_MAC, CLIENT_VLAN, CLIENT_CP,
                INTERFACE_IP.ipAddress().getIp4Address(), false));
        // won't trigger the host provider service
        verify(mockHostProviderService);
        reset(mockHostProviderService);

        assertEquals(0, mockRouteStore.routes.size());

        HostId expectHostId = HostId.hostId(CLIENT_MAC, CLIENT_VLAN);
        Capture<HostDescription> capturedHostDesc = newCapture();
        mockHostProviderService.hostDetected(eq(expectHostId), capture(capturedHostDesc), eq(false));
        replay(mockHostProviderService);
        // send ack
        packetService.processPacket(new TestDhcpAckPacketContext(CLIENT_CP, CLIENT_MAC, CLIENT_VLAN,
                INTERFACE_IP.ipAddress().getIp4Address(), false));
        verify(mockHostProviderService);
        assertEquals(0, mockRouteStore.routes.size());

        HostDescription host = capturedHostDesc.getValue();
        assertEquals(false, host.configured());
        assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
        assertEquals(CLIENT_CP.port(), host.location().port());
        assertEquals(1, host.ipAddress().size());
        assertEquals(IP_FOR_CLIENT, host.ipAddress().iterator().next());
    }

    /**
     * Relay a DHCP packet with option 82 (Indirectly connected host).
     */
    @Test
    public void relayDhcpWithAgentInfo() {
        replay(mockHostProviderService);
        // Assume outer dhcp relay agent exists in store already
        // send request
        packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC, CLIENT2_VLAN, CLIENT2_CP,
                INTERFACE_IP.ipAddress().getIp4Address(), true));
        // No routes
        assertEquals(0, mockRouteStore.routes.size());
        // send ack
        packetService.processPacket(new TestDhcpAckPacketContext(CLIENT2_CP, CLIENT2_MAC, CLIENT2_VLAN,
                INTERFACE_IP.ipAddress().getIp4Address(), true));

        // won't trigger the host provider service
        verify(mockHostProviderService);
        reset(mockHostProviderService);

        assertEquals(1, mockRouteStore.routes.size());

        Route route = mockRouteStore.routes.get(0);
        assertEquals(OUTER_RELAY_IP, route.nextHop());
        assertEquals(IP_FOR_CLIENT.toIpPrefix(), route.prefix());
        assertEquals(Route.Source.DHCP, route.source());
    }

    @Test
    public void testWithRelayAgentConfig() throws DeserializationException {
        manager.v4Handler.setDefaultDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
        manager.v4Handler.setIndirectDhcpServerConfigs(ImmutableList.of(new MockDhcpServerConfig(RELAY_AGENT_IP)));
        packetService.processPacket(new TestDhcpRequestPacketContext(CLIENT2_MAC, CLIENT2_VLAN, CLIENT2_CP,
                INTERFACE_IP.ipAddress().getIp4Address(), true));
        OutboundPacket outPacket = packetService.emittedPacket;
        byte[] outData = outPacket.data().array();
        Ethernet eth = Ethernet.deserializer().deserialize(outData, 0, outData.length);
        IPv4 ip = (IPv4) eth.getPayload();
        UDP udp = (UDP) ip.getPayload();
        DHCP dhcp = (DHCP) udp.getPayload();
        assertEquals(RELAY_AGENT_IP.toInt(), dhcp.getGatewayIPAddress());
    }

    @Test
    public void testArpRequest() throws Exception {
        packetService.processPacket(new TestArpRequestPacketContext(CLIENT_INTERFACE));
        OutboundPacket outboundPacket = packetService.emittedPacket;
        byte[] outPacketData = outboundPacket.data().array();
        Ethernet eth = Ethernet.deserializer().deserialize(outPacketData, 0, outPacketData.length);

        assertEquals(eth.getEtherType(), Ethernet.TYPE_ARP);
        ARP arp = (ARP) eth.getPayload();
        assertArrayEquals(arp.getSenderHardwareAddress(), CLIENT_INTERFACE.mac().toBytes());
    }

    /**
     * Ignores specific vlans from specific devices if config.
     *
     * @throws Exception the exception from this test
     */
    @Test
    public void testIgnoreVlan() throws Exception {
        ObjectMapper om = new ObjectMapper();
        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);

        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
        flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
        expectLastCall().times(DHCP_SELECTORS.size());
        Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
        flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
        expectLastCall().times(DHCP_SELECTORS.size());
        replay(flowObjectiveService);
        manager.updateConfig(config);
        verify(flowObjectiveService);

        List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
        List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();

        assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
        assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));

        for (int index = 0; index < objectivesFromDev1.size(); index++) {
            TrafficSelector selector = DefaultTrafficSelector.builder(DHCP_SELECTORS.get(index))
                    .matchVlanId(IGNORED_VLAN).build();
            ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
            assertEquals(selector, fwd.selector());
            assertEquals(DefaultTrafficTreatment.emptyTreatment(), fwd.treatment());
            assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
            assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
            assertEquals(Objective.Operation.ADD, fwd.op());
            fwd.context().ifPresent(ctx -> {
                ctx.onSuccess(fwd);
            });
        }
        objectivesFromDev2.forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
        assertEquals(2, v4Handler.ignoredVlans.size());
        assertEquals(2, v6Handler.ignoredVlans.size());
    }

    /**
     * "IgnoreVlan" policy should be removed when the config removed.
     */
    @Test
    public void testRemoveIgnoreVlan() {
        v4Handler.ignoredVlans.put(DEV_1_ID, IGNORED_VLAN);
        v4Handler.ignoredVlans.put(DEV_2_ID, IGNORED_VLAN);
        v6Handler.ignoredVlans.put(DEV_1_ID, IGNORED_VLAN);
        v6Handler.ignoredVlans.put(DEV_2_ID, IGNORED_VLAN);
        IgnoreDhcpConfig config = new IgnoreDhcpConfig();

        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
        flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
        expectLastCall().times(DHCP_SELECTORS.size());
        Capture<Objective> capturedFromDev2 = newCapture(CaptureType.ALL);
        flowObjectiveService.apply(eq(DEV_2_ID), capture(capturedFromDev2));
        expectLastCall().times(DHCP_SELECTORS.size());
        replay(flowObjectiveService);
        manager.removeConfig(config);
        verify(flowObjectiveService);

        List<Objective> objectivesFromDev1 = capturedFromDev1.getValues();
        List<Objective> objectivesFromDev2 = capturedFromDev2.getValues();

        assertTrue(objectivesFromDev1.containsAll(objectivesFromDev2));
        assertTrue(objectivesFromDev2.containsAll(objectivesFromDev1));

        for (int index = 0; index < objectivesFromDev1.size(); index++) {
            TrafficSelector selector = DefaultTrafficSelector.builder(DHCP_SELECTORS.get(index))
                    .matchVlanId(IGNORED_VLAN).build();
            ForwardingObjective fwd = (ForwardingObjective) objectivesFromDev1.get(index);
            assertEquals(selector, fwd.selector());
            assertEquals(DefaultTrafficTreatment.emptyTreatment(), fwd.treatment());
            assertEquals(IGNORE_CONTROL_PRIORITY, fwd.priority());
            assertEquals(ForwardingObjective.Flag.VERSATILE, fwd.flag());
            assertEquals(Objective.Operation.REMOVE, fwd.op());
            fwd.context().ifPresent(ctx -> {
                ctx.onSuccess(fwd);
            });
        }
        objectivesFromDev2.forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
        assertEquals(0, v4Handler.ignoredVlans.size());
        assertEquals(0, v6Handler.ignoredVlans.size());
    }

    /**
     * Should ignore ignore rules installation when device not available.
     */
    @Test
    public void testIgnoreUnknownDevice() throws IOException {
        reset(manager.deviceService);
        Device device = createNiceMock(Device.class);
        expect(device.is(Pipeliner.class)).andReturn(true).anyTimes();

        expect(manager.deviceService.getDevice(DEV_1_ID)).andReturn(device).anyTimes();
        expect(manager.deviceService.getDevice(DEV_2_ID)).andReturn(null).anyTimes();

        ObjectMapper om = new ObjectMapper();
        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);

        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
        flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
        expectLastCall().times(DHCP_SELECTORS.size());
        replay(flowObjectiveService, manager.deviceService, device);

        manager.updateConfig(config);
        capturedFromDev1.getValues().forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));

        assertEquals(1, v4Handler.ignoredVlans.size());
        assertEquals(1, v6Handler.ignoredVlans.size());
    }

    /**
     * Should try install ignore rules when device comes up.
     */
    @Test
    public void testInstallIgnoreRuleWhenDeviceComesUp() throws IOException {
        ObjectMapper om = new ObjectMapper();
        JsonNode json = om.readTree(Resources.getResource(CONFIG_FILE_PATH));
        IgnoreDhcpConfig config = new IgnoreDhcpConfig();
        json = json.path("apps").path(DHCP_RELAY_APP).path(IgnoreDhcpConfig.KEY);
        config.init(APP_ID, IgnoreDhcpConfig.KEY, json, om, null);

        reset(manager.cfgService, flowObjectiveService, manager.deviceService);
        expect(manager.cfgService.getConfig(APP_ID, IgnoreDhcpConfig.class)).andReturn(config).anyTimes();

        Device device = createNiceMock(Device.class);
        expect(device.is(Pipeliner.class)).andReturn(true).anyTimes();
        expect(device.id()).andReturn(DEV_1_ID).anyTimes();
        expect(manager.deviceService.getDevice(DEV_1_ID)).andReturn(device).anyTimes();
        DeviceEvent event = new DeviceEvent(DeviceEvent.Type.DEVICE_ADDED, device);
        Capture<Objective> capturedFromDev1 = newCapture(CaptureType.ALL);
        flowObjectiveService.apply(eq(DEV_1_ID), capture(capturedFromDev1));
        expectLastCall().times(DHCP_SELECTORS.size());
        replay(manager.cfgService, flowObjectiveService, manager.deviceService, device);
        manager.deviceListener.event(event);
        capturedFromDev1.getValues().forEach(obj -> obj.context().ifPresent(ctx -> ctx.onSuccess(obj)));
        assertEquals(1, v4Handler.ignoredVlans.size());
        assertEquals(1, v6Handler.ignoredVlans.size());
    }

    /**
     * Relay a DHCP6 packet without relay option
     * Note: Should add new host to host store after dhcp ack.
     */
    @Test
    public void relayDhcp6WithoutAgentInfo() {
        replay(mockHostProviderService);
        // send request
        packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.REQUEST.value(), CLIENT_MAC,
                CLIENT_VLAN, CLIENT_CP, INTERFACE_IP_V6.ipAddress().getIp6Address(), 0));

        verify(mockHostProviderService);
        reset(mockHostProviderService);
        assertEquals(0, mockRouteStore.routes.size());

        Capture<HostDescription> capturedHostDesc = newCapture();
        mockHostProviderService.hostDetected(eq(HostId.hostId(CLIENT_MAC, CLIENT_VLAN)), capture(capturedHostDesc),
                eq(false));
        replay(mockHostProviderService);
        // send reply
        packetService.processPacket(new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(), CLIENT_CP,
                CLIENT_MAC, CLIENT_VLAN, INTERFACE_IP_V6.ipAddress().getIp6Address(), 0, false, CLIENT_VLAN));
        verify(mockHostProviderService);
        assertEquals(0, mockRouteStore.routes.size());

        HostDescription host = capturedHostDesc.getValue();
        assertEquals(CLIENT_VLAN, host.vlan());
        assertEquals(CLIENT_CP.deviceId(), host.location().elementId());
        assertEquals(CLIENT_CP.port(), host.location().port());
        assertEquals(1, host.ipAddress().size());
        assertEquals(IP_FOR_CLIENT_V6, host.ipAddress().iterator().next());

        // send release
        packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.RELEASE.value(), CLIENT_MAC,
                CLIENT_VLAN, CLIENT_CP, INTERFACE_IP_V6.ipAddress().getIp6Address(), 0));

        assertEquals(null, manager.hostService.getHost(HostId.hostId(CLIENT_MAC, CLIENT_VLAN)));
    }

    /**
     * Relay a DHCP6 packet with Relay Message opion (Indirectly connected host).
     */
    @Test
    public void relayDhcp6WithAgentInfo() {
        replay(mockHostProviderService);
        // Assume outer dhcp6 relay agent exists in store already
        // send request
        packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.REQUEST.value(), CLIENT2_MAC,
                CLIENT2_VLAN, CLIENT2_CP, OUTER_RELAY_IP_V6, 1));

        assertEquals(0, mockRouteStore.routes.size());

        // send reply
        packetService.processPacket(new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(), CLIENT2_CP,
                CLIENT2_MAC, CLIENT2_VLAN, OUTER_RELAY_IP_V6, 1, false, CLIENT2_VLAN));

        // won't trigger the host provider service
        verify(mockHostProviderService);
        reset(mockHostProviderService);
        assertEquals(2, mockRouteStore.routes.size()); // ipAddress and prefix

        Route aRoute = mockRouteStore.routes.stream().filter(rt -> rt.prefix().contains(IP_FOR_CLIENT_V6))
                .findFirst().orElse(null);
        assertNotEquals(null, aRoute);

        aRoute = mockRouteStore.routes.stream().filter(rt -> rt.prefix().contains(PREFIX_FOR_CLIENT_V6)).findFirst()
                .orElse(null);
        assertNotEquals(null, aRoute);

        // send release msg
        packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.RELEASE.value(), CLIENT2_MAC,
                CLIENT2_VLAN, CLIENT2_CP, OUTER_RELAY_IP_V6, 1));

        aRoute = mockRouteStore.routes.stream().filter(rt -> rt.prefix().contains(IP_FOR_CLIENT_V6)).findFirst()
                .orElse(null);
        assertEquals(null, aRoute);

        aRoute = mockRouteStore.routes.stream().filter(rt -> rt.prefix().contains(PREFIX_FOR_CLIENT_V6)).findFirst()
                .orElse(null);
        assertEquals(null, aRoute);

        assertEquals(0, mockRouteStore.routes.size());

    }

    /**
     * Relay a DHCP6 packet with Relay Message opion (Indirectly connected host) and server responded
     * with vlan differnt from client interface vlan.
     */
    @Test
    public void relayDhcp6WithAgentInfoWrongVlan() {
        replay(mockHostProviderService);
        // Assume outer dhcp6 relay agent exists in store already
        // send request
        packetService.processPacket(new TestDhcp6RequestPacketContext(DHCP6.MsgType.REQUEST.value(), CLIENT2_MAC,
                CLIENT2_VLAN, CLIENT2_CP, INTERFACE_IP_V6.ipAddress().getIp6Address(), 1));

        assertEquals(0, mockRouteStore.routes.size());

        // send reply
        packetService.processPacket(new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(), CLIENT2_CP,
                CLIENT2_MAC, CLIENT2_VLAN, INTERFACE_IP_V6.ipAddress().getIp6Address(), 1, true, CLIENT_BOGUS_VLAN // mismatch
        ));

        // won't trigger the host provider service
        verify(mockHostProviderService);
        reset(mockHostProviderService);
        assertEquals(0, mockRouteStore.routes.size()); // ipAddress and prefix

    }

    @Test
    public void testDhcp4DualHome() {
        PacketContext packetContext = new TestDhcpAckPacketContext(CLIENT_DH_CP, CLIENT_MAC, CLIENT_VLAN,
                INTERFACE_IP.ipAddress().getIp4Address(), false);
        reset(manager.hostService);
        expect(manager.hostService.getHost(CLIENT_HOST_ID)).andReturn(EXISTS_HOST).anyTimes();
        Capture<HostDescription> capturedHostDesc = newCapture();
        mockHostProviderService.hostDetected(eq(CLIENT_HOST_ID), capture(capturedHostDesc), eq(false));
        replay(mockHostProviderService, manager.hostService);
        packetService.processPacket(packetContext);
        verify(mockHostProviderService);

        HostDescription hostDesc = capturedHostDesc.getValue();
        Set<HostLocation> hostLocations = hostDesc.locations();
        assertEquals(2, hostLocations.size());
        assertTrue(hostLocations.contains(CLIENT_LOCATION));
        assertTrue(hostLocations.contains(CLIENT_DH_LOCATION));
    }

    @Test
    public void testDhcp6DualHome() {
        PacketContext packetContext = new TestDhcp6ReplyPacketContext(DHCP6.MsgType.REPLY.value(), CLIENT_DH_CP,
                CLIENT_MAC, CLIENT_VLAN, INTERFACE_IP_V6.ipAddress().getIp6Address(), 0, false, CLIENT_VLAN);
        reset(manager.hostService);
        expect(manager.hostService.getHostsByIp(CLIENT_LL_IP_V6)).andReturn(ImmutableSet.of(EXISTS_HOST))
                .anyTimes();

        // FIXME: currently DHCPv6 has a bug, we can't get correct vlan of client......
        // XXX: The vlan relied from DHCP6 handler might be wrong, do hack here
        HostId hostId = HostId.hostId(CLIENT_MAC, VlanId.NONE);
        expect(manager.hostService.getHost(hostId)).andReturn(EXISTS_HOST).anyTimes();

        // XXX: sometimes this will work, sometimes not
        expect(manager.hostService.getHost(CLIENT_HOST_ID)).andReturn(EXISTS_HOST).anyTimes();

        Capture<HostDescription> capturedHostDesc = newCapture();

        // XXX: also a hack here
        mockHostProviderService.hostDetected(eq(hostId), capture(capturedHostDesc), eq(false));
        expectLastCall().anyTimes();

        mockHostProviderService.hostDetected(eq(CLIENT_HOST_ID), capture(capturedHostDesc), eq(false));
        expectLastCall().anyTimes();
        replay(mockHostProviderService, manager.hostService);
        packetService.processPacket(packetContext);
        verify(mockHostProviderService);

        HostDescription hostDesc = capturedHostDesc.getValue();
        Set<HostLocation> hostLocations = hostDesc.locations();
        assertEquals(2, hostLocations.size());
        assertTrue(hostLocations.contains(CLIENT_LOCATION));
        assertTrue(hostLocations.contains(CLIENT_DH_LOCATION));
    }

    private static class MockDefaultDhcpRelayConfig extends DefaultDhcpRelayConfig {
        @Override
        public boolean isValid() {
            return true;
        }

        @Override
        public List<DhcpServerConfig> dhcpServerConfigs() {
            return ImmutableList.of(new MockDhcpServerConfig(null));
        }
    }

    private static class MockIndirectDhcpRelayConfig extends IndirectDhcpRelayConfig {
        @Override
        public boolean isValid() {
            return true;
        }

        @Override
        public List<DhcpServerConfig> dhcpServerConfigs() {
            return ImmutableList.of(new MockDhcpServerConfig(null));
        }
    }

    private static class MockDhcpServerConfig extends DhcpServerConfig {
        Ip4Address relayAgentIp;

        /**
         * Create mocked version DHCP server config.
         *
         * @param relayAgentIp the relay agent Ip config; null if we don't need it
         */
        public MockDhcpServerConfig(Ip4Address relayAgentIp) {
            this.relayAgentIp = relayAgentIp;
            this.relayAgentIps.put(DEV_1_ID, Pair.of(relayAgentIp, null));
            this.relayAgentIps.put(DEV_2_ID, Pair.of(relayAgentIp, null));
        }

        @Override
        public Optional<Ip4Address> getRelayAgentIp4(DeviceId deviceId) {
            return Optional.ofNullable(this.relayAgentIps.get(deviceId).getLeft());
        }

        @Override
        public Optional<ConnectPoint> getDhcpServerConnectPoint() {
            return Optional.of(SERVER_CONNECT_POINT);
        }

        @Override
        public Optional<Ip4Address> getDhcpServerIp4() {
            return Optional.of(SERVER_IP);
        }

        @Override
        public Optional<Ip4Address> getDhcpGatewayIp4() {
            return Optional.of(GATEWAY_IP);
        }

        @Override
        public Optional<Ip6Address> getDhcpServerIp6() {
            return Optional.of(SERVER_IP_V6);
        }

        @Override
        public Optional<Ip6Address> getDhcpGatewayIp6() {
            return Optional.of(GATEWAY_IP_V6);
        }
    }

    private class MockRouteStore extends RouteStoreAdapter {
        private List<Route> routes = Lists.newArrayList();

        @Override
        public void updateRoute(Route route) {
            routes.add(route);
        }

        @Override
        public void removeRoute(Route route) {
            routes.remove(route);
        }
    }

    private class MockInterfaceService extends InterfaceServiceAdapter {

        @Override
        public Set<Interface> getInterfaces() {
            return INTERFACES;
        }

        @Override
        public Set<Interface> getInterfacesByIp(IpAddress ip) {
            return INTERFACES.stream().filter(iface -> {
                return iface.ipAddressesList().stream().anyMatch(ifaceIp -> ifaceIp.ipAddress().equals(ip));
            }).collect(Collectors.toSet());
        }

        @Override
        public Set<Interface> getInterfacesByPort(ConnectPoint port) {
            return INTERFACES.stream().filter(iface -> iface.connectPoint().equals(port))
                    .collect(Collectors.toSet());
        }
    }

    private class MockDhcpRelayStore implements DhcpRelayStore {
        StoreDelegate<DhcpRelayStoreEvent> delegate;
        private Map<HostId, DhcpRecord> records = Maps.newHashMap();

        @Override
        public void updateDhcpRecord(HostId hostId, DhcpRecord dhcpRecord) {
            records.put(hostId, dhcpRecord);
            DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.UPDATED, dhcpRecord);
            if (delegate != null) {
                delegate.notify(event);
            }
        }

        @Override
        public Optional<DhcpRecord> getDhcpRecord(HostId hostId) {
            return Optional.ofNullable(records.get(hostId));
        }

        @Override
        public Collection<DhcpRecord> getDhcpRecords() {
            return records.values();
        }

        @Override
        public Optional<DhcpRecord> removeDhcpRecord(HostId hostId) {
            DhcpRecord dhcpRecord = records.remove(hostId);
            if (dhcpRecord != null) {
                DhcpRelayStoreEvent event = new DhcpRelayStoreEvent(DhcpRelayStoreEvent.Type.REMOVED, dhcpRecord);
                if (delegate != null) {
                    delegate.notify(event);
                }
            }
            return Optional.ofNullable(dhcpRecord);
        }

        @Override
        public void setDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
            this.delegate = delegate;
        }

        @Override
        public void unsetDelegate(StoreDelegate<DhcpRelayStoreEvent> delegate) {
            this.delegate = null;
        }

        @Override
        public boolean hasDelegate() {
            return this.delegate != null;
        }
    }

    private class MockDhcpRelayCountersStore implements DhcpRelayCountersStore {
        private Map<String, DhcpRelayCounters> counters = Maps.newHashMap();

        public void incrementCounter(String coutnerClass, String counterName) {
            DhcpRelayCounters countersRecord;

            DhcpRelayCounters classCounters = counters.get(coutnerClass);
            if (classCounters == null) {
                classCounters = new DhcpRelayCounters();
            }
            classCounters.incrementCounter(counterName);
            counters.put(coutnerClass, classCounters);
        }

        @Override
        public Set<Map.Entry<String, DhcpRelayCounters>> getAllCounters() {
            return counters.entrySet();
        }

        @Override
        public Optional<DhcpRelayCounters> getCounters(String counterClass) {
            DhcpRelayCounters classCounters = counters.get(counterClass);
            if (classCounters == null) {
                return Optional.empty();
            }
            return Optional.of(classCounters);
        }

        @Override
        public void resetAllCounters() {
            counters.clear();
        }

        @Override
        public void resetCounters(String counterClass) {
            DhcpRelayCounters classCounters = counters.get(counterClass);
            classCounters.resetCounters();
            counters.put(counterClass, classCounters);
        }
    }

    private class MockPacketService extends PacketServiceAdapter {
        Set<PacketProcessor> packetProcessors = Sets.newHashSet();
        OutboundPacket emittedPacket;

        @Override
        public void addProcessor(PacketProcessor processor, int priority) {
            packetProcessors.add(processor);
        }

        public void processPacket(PacketContext packetContext) {
            packetProcessors.forEach(p -> p.process(packetContext));
        }

        @Override
        public void emit(OutboundPacket packet) {
            this.emittedPacket = packet;
        }
    }

    /**
     * Generates DHCP REQUEST packet.
     */
    private class TestDhcpRequestPacketContext extends PacketContextAdapter {

        private InboundPacket inPacket;

        public TestDhcpRequestPacketContext(MacAddress clientMac, VlanId vlanId, ConnectPoint clientCp,
                Ip4Address clientGwAddr, boolean withNonOnosRelayInfo) {
            super(0, null, null, false);
            byte[] dhcpMsgType = new byte[1];
            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPREQUEST.getValue();

            DhcpOption dhcpOption = new DhcpOption();
            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
            dhcpOption.setData(dhcpMsgType);
            dhcpOption.setLength((byte) 1);
            DhcpOption endOption = new DhcpOption();
            endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());

            DHCP dhcp = new DHCP();
            dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
            dhcp.setHardwareAddressLength((byte) 6);
            dhcp.setClientHardwareAddress(clientMac.toBytes());
            if (withNonOnosRelayInfo) {
                DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
                DhcpOption circuitIdOption = new DhcpOption();
                CircuitId circuitId = new CircuitId("Custom option", VlanId.NONE);
                byte[] cid = circuitId.serialize();
                circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
                circuitIdOption.setLength((byte) cid.length);
                circuitIdOption.setData(cid);
                relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
                relayOption.addSubOption(circuitIdOption);
                dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
                dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
            } else {
                dhcp.setOptions(ImmutableList.of(dhcpOption, endOption));
            }

            UDP udp = new UDP();
            udp.setPayload(dhcp);
            udp.setSourcePort(UDP.DHCP_CLIENT_PORT);
            udp.setDestinationPort(UDP.DHCP_SERVER_PORT);

            IPv4 ipv4 = new IPv4();
            ipv4.setPayload(udp);
            ipv4.setDestinationAddress(SERVER_IP.toInt());
            ipv4.setSourceAddress(clientGwAddr.toInt());

            Ethernet eth = new Ethernet();
            if (withNonOnosRelayInfo) {
                eth.setEtherType(Ethernet.TYPE_IPV4).setVlanID(vlanId.toShort())
                        .setSourceMACAddress(OUTER_RELAY_MAC).setDestinationMACAddress(MacAddress.BROADCAST)
                        .setPayload(ipv4);
            } else {
                eth.setEtherType(Ethernet.TYPE_IPV4).setVlanID(vlanId.toShort()).setSourceMACAddress(clientMac)
                        .setDestinationMACAddress(MacAddress.BROADCAST).setPayload(ipv4);
            }

            this.inPacket = new DefaultInboundPacket(clientCp, eth, ByteBuffer.wrap(eth.serialize()));
        }

        @Override
        public InboundPacket inPacket() {
            return this.inPacket;
        }
    }

    /**
     * Generates DHCP ACK packet.
     */
    private class TestDhcpAckPacketContext extends PacketContextAdapter {
        private InboundPacket inPacket;

        public TestDhcpAckPacketContext(ConnectPoint clientCp, MacAddress clientMac, VlanId clientVlan,
                Ip4Address clientGwAddr, boolean withNonOnosRelayInfo) {
            super(0, null, null, false);

            byte[] dhcpMsgType = new byte[1];
            dhcpMsgType[0] = (byte) DHCP.MsgType.DHCPACK.getValue();

            DhcpOption dhcpOption = new DhcpOption();
            dhcpOption.setCode(DHCP.DHCPOptionCode.OptionCode_MessageType.getValue());
            dhcpOption.setData(dhcpMsgType);
            dhcpOption.setLength((byte) 1);

            DhcpOption endOption = new DhcpOption();
            endOption.setCode(DHCP.DHCPOptionCode.OptionCode_END.getValue());

            DHCP dhcp = new DHCP();
            if (withNonOnosRelayInfo) {
                DhcpRelayAgentOption relayOption = new DhcpRelayAgentOption();
                DhcpOption circuitIdOption = new DhcpOption();
                String circuitId = NON_ONOS_CID;
                byte[] cid = circuitId.getBytes(Charsets.US_ASCII);
                circuitIdOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
                circuitIdOption.setLength((byte) cid.length);
                circuitIdOption.setData(cid);
                relayOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
                relayOption.addSubOption(circuitIdOption);
                dhcp.setOptions(ImmutableList.of(dhcpOption, relayOption, endOption));
                dhcp.setGatewayIPAddress(OUTER_RELAY_IP.getIp4Address().toInt());
            } else {
                CircuitId cid = new CircuitId(clientCp.toString(), clientVlan);
                byte[] circuitId = cid.serialize();
                DhcpOption circuitIdSubOption = new DhcpOption();
                circuitIdSubOption.setCode(DhcpRelayAgentOption.RelayAgentInfoOptions.CIRCUIT_ID.getValue());
                circuitIdSubOption.setData(circuitId);
                circuitIdSubOption.setLength((byte) circuitId.length);

                DhcpRelayAgentOption relayInfoOption = new DhcpRelayAgentOption();
                relayInfoOption.setCode(DHCP.DHCPOptionCode.OptionCode_CircuitID.getValue());
                relayInfoOption.addSubOption(circuitIdSubOption);
                dhcp.setOptions(ImmutableList.of(dhcpOption, relayInfoOption, endOption));
                dhcp.setGatewayIPAddress(clientGwAddr.toInt());
            }
            dhcp.setHardwareType(DHCP.HWTYPE_ETHERNET);
            dhcp.setHardwareAddressLength((byte) 6);
            dhcp.setClientHardwareAddress(clientMac.toBytes());
            dhcp.setYourIPAddress(IP_FOR_CLIENT.toInt());

            UDP udp = new UDP();
            udp.setPayload(dhcp);
            udp.setSourcePort(UDP.DHCP_SERVER_PORT);
            udp.setDestinationPort(UDP.DHCP_CLIENT_PORT);
            IPv4 ipv4 = new IPv4();
            ipv4.setPayload(udp);
            ipv4.setDestinationAddress(IP_FOR_CLIENT.toString());
            ipv4.setSourceAddress(SERVER_IP.toString());
            Ethernet eth = new Ethernet();
            if (withNonOnosRelayInfo) {
                eth.setEtherType(Ethernet.TYPE_IPV4).setVlanID(SERVER_VLAN.toShort())
                        .setSourceMACAddress(SERVER_MAC).setDestinationMACAddress(OUTER_RELAY_MAC).setPayload(ipv4);
            } else {
                eth.setEtherType(Ethernet.TYPE_IPV4).setVlanID(SERVER_VLAN.toShort())
                        .setSourceMACAddress(SERVER_MAC).setDestinationMACAddress(CLIENT_MAC).setPayload(ipv4);
            }

            this.inPacket = new DefaultInboundPacket(SERVER_CONNECT_POINT, eth, ByteBuffer.wrap(eth.serialize()));

        }

        @Override
        public InboundPacket inPacket() {
            return this.inPacket;
        }
    }

    private class TestArpRequestPacketContext extends PacketContextAdapter {
        private InboundPacket inPacket;

        public TestArpRequestPacketContext(Interface fromInterface) {
            super(0, null, null, false);
            ARP arp = new ARP();
            arp.setOpCode(ARP.OP_REQUEST);

            IpAddress targetIp = fromInterface.ipAddressesList().get(0).ipAddress();
            arp.setTargetProtocolAddress(targetIp.toOctets());
            arp.setTargetHardwareAddress(MacAddress.BROADCAST.toBytes());
            arp.setSenderHardwareAddress(MacAddress.NONE.toBytes());
            arp.setSenderProtocolAddress(Ip4Address.valueOf(0).toOctets());
            arp.setHardwareAddressLength((byte) MacAddress.MAC_ADDRESS_LENGTH);
            Ethernet eth = new Ethernet();
            eth.setEtherType(Ethernet.TYPE_ARP);
            eth.setSourceMACAddress(MacAddress.NONE);
            eth.setDestinationMACAddress(MacAddress.BROADCAST);
            eth.setVlanID(fromInterface.vlan().toShort());
            eth.setPayload(arp);

            this.inPacket = new DefaultInboundPacket(fromInterface.connectPoint(), eth,
                    ByteBuffer.wrap(eth.serialize()));
        }

        @Override
        public InboundPacket inPacket() {
            return this.inPacket;
        }
    }

    /**
     * Generates DHCP6 REQUEST packet.
     */
    private void buildDhcp6Packet(DHCP6 dhcp6, byte msgType, Ip6Address ip6Addr, IpPrefix prefix) {

        // build address option
        Dhcp6IaAddressOption iaAddressOption = new Dhcp6IaAddressOption();
        iaAddressOption.setCode(DHCP6.OptionCode.IAADDR.value());
        iaAddressOption.setIp6Address(ip6Addr);
        iaAddressOption.setPreferredLifetime(3600);
        iaAddressOption.setValidLifetime(1200);
        iaAddressOption.setLength((short) Dhcp6IaAddressOption.DEFAULT_LEN);

        Dhcp6ClientIdOption clientIdOption = new Dhcp6ClientIdOption();
        Dhcp6Duid dhcp6Duip = new Dhcp6Duid();
        dhcp6Duip.setDuidType(Dhcp6Duid.DuidType.DUID_LLT);
        dhcp6Duip.setHardwareType((short) 0x01); // Ethernet
        dhcp6Duip.setDuidTime(1234);
        dhcp6Duip.setLinkLayerAddress(CLIENT_MAC.toBytes());
        clientIdOption.setDuid(dhcp6Duip);

        Dhcp6IaNaOption iaNaOption = new Dhcp6IaNaOption();
        iaNaOption.setCode(DHCP6.OptionCode.IA_NA.value());
        iaNaOption.setIaId(0);
        iaNaOption.setT1(302400);
        iaNaOption.setT2(483840);
        List<Dhcp6Option> iaNaSubOptions = new ArrayList<Dhcp6Option>();
        iaNaSubOptions.add(iaAddressOption);
        iaNaOption.setOptions(iaNaSubOptions);
        iaNaOption.setLength((short) (Dhcp6IaNaOption.DEFAULT_LEN + iaAddressOption.getLength()));

        // build prefix option
        Dhcp6IaPrefixOption iaPrefixOption = new Dhcp6IaPrefixOption();
        iaPrefixOption.setCode(DHCP6.OptionCode.IAPREFIX.value());
        iaPrefixOption.setIp6Prefix(prefix.address().getIp6Address());
        iaPrefixOption.setPrefixLength((byte) prefix.prefixLength());
        iaPrefixOption.setPreferredLifetime(3601);
        iaPrefixOption.setValidLifetime(1201);
        iaPrefixOption.setLength((short) Dhcp6IaPrefixOption.DEFAULT_LEN);

        Dhcp6IaPdOption iaPdOption = new Dhcp6IaPdOption();
        iaPdOption.setCode(DHCP6.OptionCode.IA_PD.value());
        iaPdOption.setIaId(0);
        iaPdOption.setT1(302401);
        iaPdOption.setT2(483841);
        List<Dhcp6Option> iaPdSubOptions = new ArrayList<Dhcp6Option>();
        iaPdSubOptions.add(iaPrefixOption);
        iaPdOption.setOptions(iaPdSubOptions);
        iaPdOption.setLength((short) (Dhcp6IaPdOption.DEFAULT_LEN + iaPrefixOption.getLength()));

        dhcp6.setMsgType(msgType);
        List<Dhcp6Option> dhcp6Options = new ArrayList<Dhcp6Option>();
        dhcp6Options.add(iaNaOption);
        dhcp6Options.add(clientIdOption);
        dhcp6Options.add(iaPdOption);
        dhcp6.setOptions(dhcp6Options);

    }

    private void buildRelayMsg(DHCP6 dhcp6Relay, byte msgType, Ip6Address linkAddr, Ip6Address peerAddr, byte hop,
            byte[] interfaceIdBytes, DHCP6 dhcp6Payload) {

        dhcp6Relay.setMsgType(msgType);

        dhcp6Relay.setLinkAddress(linkAddr.toOctets());
        dhcp6Relay.setPeerAddress(peerAddr.toOctets());
        dhcp6Relay.setHopCount(hop);
        List<Dhcp6Option> options = new ArrayList<Dhcp6Option>();

        // interfaceId  option
        Dhcp6Option interfaceId = new Dhcp6Option();
        interfaceId.setCode(DHCP6.OptionCode.INTERFACE_ID.value());

        interfaceId.setData(interfaceIdBytes);
        interfaceId.setLength((short) interfaceIdBytes.length);
        Dhcp6InterfaceIdOption interfaceIdOption = new Dhcp6InterfaceIdOption(interfaceId);
        byte[] optionData = interfaceIdOption.getData();
        ByteBuffer bb = ByteBuffer.wrap(interfaceIdBytes);

        byte[] macAddr = new byte[MacAddress.MAC_ADDRESS_LENGTH];
        byte[] port = new byte[optionData.length - MacAddress.MAC_ADDRESS_LENGTH - VLAN_LEN - SEPARATOR_LEN * 2];
        short vlan;
        bb.get(macAddr);
        bb.get(); // separator
        bb.get(port);
        bb.get(); // separator
        vlan = bb.getShort();
        interfaceIdOption.setMacAddress(MacAddress.valueOf(macAddr));
        interfaceIdOption.setInPort(port);
        interfaceIdOption.setVlanId(vlan);

        options.add(interfaceIdOption);

        // relay message option
        Dhcp6Option relayMsgOption = new Dhcp6Option();
        relayMsgOption.setCode(DHCP6.OptionCode.RELAY_MSG.value());
        byte[] dhcp6PayloadByte = dhcp6Payload.serialize();
        relayMsgOption.setLength((short) dhcp6PayloadByte.length);
        relayMsgOption.setPayload(dhcp6Payload);
        Dhcp6RelayOption relayOpt = new Dhcp6RelayOption(relayMsgOption);

        options.add(relayOpt);

        dhcp6Relay.setOptions(options);
    }

    private byte[] buildInterfaceId(MacAddress clientMac, short vlanId, ConnectPoint clientCp) {
        String inPortString = "-" + clientCp.toString() + ":";
        byte[] clientSoureMacBytes = clientMac.toBytes();
        byte[] inPortStringBytes = inPortString.getBytes();
        byte[] vlanIdBytes = new byte[2];
        vlanIdBytes[0] = (byte) ((vlanId >> 8) & 0xff); // high-order byte first
        vlanIdBytes[1] = (byte) (vlanId & 0xff);
        byte[] interfaceIdBytes = new byte[clientSoureMacBytes.length + inPortStringBytes.length
                + vlanIdBytes.length];

        System.arraycopy(clientSoureMacBytes, 0, interfaceIdBytes, 0, clientSoureMacBytes.length);
        System.arraycopy(inPortStringBytes, 0, interfaceIdBytes, clientSoureMacBytes.length,
                inPortStringBytes.length);
        System.arraycopy(vlanIdBytes, 0, interfaceIdBytes, clientSoureMacBytes.length + inPortStringBytes.length,
                vlanIdBytes.length);

        return interfaceIdBytes;
    }

    private static List<TrafficSelector> buildClientDhcpSelectors() {
        return Streams.concat(Dhcp4HandlerImpl.DHCP_SELECTORS.stream(), Dhcp6HandlerImpl.DHCP_SELECTORS.stream())
                .collect(Collectors.toList());
    }

    private class TestDhcp6RequestPacketContext extends PacketContextAdapter {

        private InboundPacket inPacket;

        public TestDhcp6RequestPacketContext(byte msgType, MacAddress clientMac, VlanId vlanId,
                ConnectPoint clientCp, Ip6Address clientGwAddr, int relayLevel) {
            super(0, null, null, false);

            DHCP6 dhcp6 = new DHCP6();
            if (relayLevel > 0) {
                DHCP6 dhcp6Payload = new DHCP6();
                buildDhcp6Packet(dhcp6Payload, msgType, IP_FOR_CLIENT_V6,
                        msgType == DHCP6.MsgType.REQUEST.value() ? PREFIX_FOR_ZERO : PREFIX_FOR_CLIENT_V6);
                DHCP6 dhcp6Parent = null;
                DHCP6 dhcp6Child = dhcp6Payload;
                for (int i = 0; i < relayLevel; i++) {
                    dhcp6Parent = new DHCP6();
                    byte[] interfaceId = buildInterfaceId(clientMac, vlanId.toShort(), clientCp);
                    buildRelayMsg(dhcp6Parent, DHCP6.MsgType.RELAY_FORW.value(),
                            INTERFACE_IP_V6.ipAddress().getIp6Address(), OUTER_RELAY_IP_V6, (byte) (relayLevel - 1),
                            interfaceId, dhcp6Child);
                    dhcp6Child = dhcp6Parent;
                }
                if (dhcp6Parent != null) {
                    dhcp6 = dhcp6Parent;
                }
            } else {
                buildDhcp6Packet(dhcp6, msgType, IP_FOR_CLIENT_V6,
                        msgType == DHCP6.MsgType.REQUEST.value() ? PREFIX_FOR_ZERO : PREFIX_FOR_CLIENT_V6);
            }

            UDP udp = new UDP();
            udp.setPayload(dhcp6);
            if (relayLevel > 0) {
                udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
            } else {
                udp.setSourcePort(UDP.DHCP_V6_CLIENT_PORT);
            }
            udp.setDestinationPort(UDP.DHCP_V6_SERVER_PORT);

            IPv6 ipv6 = new IPv6();
            ipv6.setPayload(udp);
            ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
            ipv6.setDestinationAddress(SERVER_IP_V6_MCAST.toOctets());
            ipv6.setSourceAddress(clientGwAddr.toOctets());

            Ethernet eth = new Ethernet();
            if (relayLevel > 0) {
                eth.setEtherType(Ethernet.TYPE_IPV6).setVlanID(OUTER_RELAY_VLAN.toShort())
                        .setSourceMACAddress(OUTER_RELAY_MAC)
                        .setDestinationMACAddress(MacAddress.valueOf("33:33:00:01:00:02")).setPayload(ipv6);
            } else {
                eth.setEtherType(Ethernet.TYPE_IPV6).setVlanID(vlanId.toShort()).setSourceMACAddress(clientMac)
                        .setDestinationMACAddress(MacAddress.valueOf("33:33:00:01:00:02")).setPayload(ipv6);
            }
            this.inPacket = new DefaultInboundPacket(clientCp, eth, ByteBuffer.wrap(eth.serialize()));
        }

        @Override
        public InboundPacket inPacket() {
            return this.inPacket;
        }
    }

    /**
     * Generates DHCP6 REPLY  packet.
     */

    private class TestDhcp6ReplyPacketContext extends PacketContextAdapter {
        private InboundPacket inPacket;

        public TestDhcp6ReplyPacketContext(byte msgType, ConnectPoint clientCp, MacAddress clientMac,
                VlanId clientVlan, Ip6Address clientGwAddr, int relayLevel, boolean overWriteFlag,
                VlanId overWriteVlan) {
            super(0, null, null, false);

            DHCP6 dhcp6Payload = new DHCP6();
            buildDhcp6Packet(dhcp6Payload, msgType, IP_FOR_CLIENT_V6, PREFIX_FOR_CLIENT_V6);
            byte[] interfaceId = null;
            if (relayLevel > 0) {
                interfaceId = buildInterfaceId(OUTER_RELAY_MAC,
                        overWriteFlag ? overWriteVlan.toShort() : OUTER_RELAY_VLAN.toShort(), OUTER_RELAY_CP);
            } else {
                interfaceId = buildInterfaceId(clientMac, clientVlan.toShort(), clientCp);
            }
            DHCP6 dhcp6 = new DHCP6();
            buildRelayMsg(dhcp6, DHCP6.MsgType.RELAY_REPL.value(), INTERFACE_IP_V6.ipAddress().getIp6Address(),
                    CLIENT_LL_IP_V6, (byte) 0, interfaceId, dhcp6Payload);

            DHCP6 dhcp6Parent = null;
            DHCP6 dhcp6Child = dhcp6;
            for (int i = 0; i < relayLevel; i++) { // relayLevel 0 : no relay
                dhcp6Parent = new DHCP6();

                buildRelayMsg(dhcp6Parent, DHCP6.MsgType.RELAY_REPL.value(),
                        INTERFACE_IP_V6.ipAddress().getIp6Address(), OUTER_RELAY_IP_V6, (byte) relayLevel,
                        interfaceId, dhcp6Child);

                dhcp6Child = dhcp6Parent;
            }
            if (dhcp6Parent != null) {
                dhcp6 = dhcp6Parent;
            }

            UDP udp = new UDP();
            udp.setPayload(dhcp6);
            udp.setSourcePort(UDP.DHCP_V6_SERVER_PORT);
            udp.setDestinationPort(UDP.DHCP_V6_CLIENT_PORT);
            IPv6 ipv6 = new IPv6();
            ipv6.setPayload(udp);
            ipv6.setNextHeader(IPv6.PROTOCOL_UDP);
            ipv6.setDestinationAddress(IP_FOR_CLIENT_V6.toOctets());
            ipv6.setSourceAddress(SERVER_IP_V6.toOctets());
            Ethernet eth = new Ethernet();
            if (relayLevel > 0) {
                eth.setEtherType(Ethernet.TYPE_IPV6).setVlanID(SERVER_VLAN.toShort())
                        .setSourceMACAddress(SERVER_MAC).setDestinationMACAddress(OUTER_RELAY_MAC).setPayload(ipv6);
            } else {
                eth.setEtherType(Ethernet.TYPE_IPV6).setVlanID(SERVER_VLAN.toShort())
                        .setSourceMACAddress(SERVER_MAC).setDestinationMACAddress(CLIENT_MAC).setPayload(ipv6);
            }

            this.inPacket = new DefaultInboundPacket(SERVER_CONNECT_POINT, eth, ByteBuffer.wrap(eth.serialize()));

        }

        @Override
        public InboundPacket inPacket() {
            return this.inPacket;
        }
    }

}