org.midonet.cluster.data.neutron.DataCheckPointTest.java Source code

Java tutorial

Introduction

Here is the source code for org.midonet.cluster.data.neutron.DataCheckPointTest.java

Source

/*
 * Copyright 2014 Midokura SARL
 *
 * 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.midonet.cluster.data.neutron;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import com.google.common.base.Objects;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Injector;

import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.zookeeper.KeeperException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import org.midonet.cluster.DataClient;
import org.midonet.cluster.config.ZookeeperConfig;
import org.midonet.cluster.data.Chain;
import org.midonet.cluster.data.IpAddrGroup;
import org.midonet.cluster.data.Rule;
import org.midonet.cluster.data.rules.ForwardNatRule;
import org.midonet.cluster.data.rules.JumpRule;
import org.midonet.midolman.Setup;
import org.midonet.midolman.guice.cluster.TestDataClientModule;
import org.midonet.midolman.guice.config.ConfigProviderModule;
import org.midonet.midolman.guice.serialization.SerializationModule;
import org.midonet.midolman.guice.zookeeper.MockZookeeperConnectionModule;
import org.midonet.midolman.rules.Condition;
import org.midonet.midolman.rules.NatTarget;
import org.midonet.midolman.rules.RuleResult;
import org.midonet.midolman.serialization.SerializationException;
import org.midonet.midolman.state.CheckpointedDirectory;
import org.midonet.midolman.state.CheckpointedMockDirectory;
import org.midonet.midolman.state.Directory;
import org.midonet.midolman.state.StateAccessException;
import org.midonet.midolman.state.zkManagers.BridgeZkManager;
import org.midonet.midolman.version.guice.VersionModule;
import org.midonet.packets.ARP;

public class DataCheckPointTest {

    @Inject
    NeutronPlugin plugin;
    @Inject
    DataClient dataClient;

    Injector injector = null;
    String zkRoot = "/test";

    HierarchicalConfiguration fillConfig(HierarchicalConfiguration config) {
        config.addNodes(ZookeeperConfig.GROUP_NAME,
                Arrays.asList(new HierarchicalConfiguration.Node("midolman_root_key", zkRoot)));
        return config;
    }

    CheckpointedDirectory zkDir() {
        return injector.getInstance(CheckpointedDirectory.class);
    }

    /*
     * Simple utility functions used in UT to test types of rules.
     */

    public static <T extends Rule.Data, U extends Rule<T, U>> boolean isDropAllExceptArpRule(Rule<T, U> rule) {
        if (rule == null) {
            return false;
        }
        Condition cond = rule.getCondition();
        if (cond == null) {
            return false;
        }
        if (!cond.etherType.equals(new Integer(ARP.ETHERTYPE))) {
            return false;
        }
        if (!(cond.invDlType)) {
            return false;
        }
        if (!Objects.equal(rule.getAction(), RuleResult.Action.DROP)) {
            return false;
        }
        return true;
    }

    public static <T extends Rule.Data, U extends Rule<T, U>> boolean isMacSpoofProtectionRule(String macAddress,
            Rule<T, U> rule) {
        if (rule == null) {
            return false;
        }
        Condition cond = rule.getCondition();
        if (cond == null) {
            return false;
        }
        if (!cond.invDlSrc) {
            return false;
        }

        if (!Objects.equal(cond.ethSrc.toString(), macAddress)) {
            return false;
        }
        if (!Objects.equal(rule.getAction(), RuleResult.Action.DROP)) {
            return false;
        }
        return true;
    }

    public static <T extends Rule.Data, U extends Rule<T, U>> boolean isIpSpoofProtectionRule(IPAllocation subnet,
            Rule<T, U> rule) {
        if (rule == null) {
            return false;
        }
        Condition cond = rule.getCondition();
        if (cond == null) {
            return false;
        }
        if (!cond.nwSrcInv) {
            return false;
        }
        String subnetStr = cond.nwSrcIp.getAddress().toString();
        if (!Objects.equal(subnetStr, subnet.ipAddress)) {
            return false;
        }
        if (!Objects.equal(rule.getAction(), RuleResult.Action.DROP)) {
            return false;
        }
        return true;
    }

    public static boolean isAcceptReturnFlowRule(Rule<?, ?> rule) {
        if (rule == null) {
            return false;
        }
        Condition cond = rule.getCondition();
        if (cond == null) {
            return false;
        }
        if (!cond.matchReturnFlow) {
            return false;
        }
        if (!Objects.equal(rule.getAction(), RuleResult.Action.ACCEPT)) {
            return false;
        }
        return true;
    }

    public Network createStockNetwork() {
        Network network = new Network();
        network.adminStateUp = true;
        network.name = "net";
        network.tenantId = "tenant";
        network.shared = true;
        network.id = UUID.randomUUID();
        return network;
    }

    public Subnet createStockSubnet() {
        Subnet subnet = new Subnet();
        subnet.cidr = "10.0.0.0/24";
        List<String> nss = new ArrayList<>();
        nss.add("10.0.0.1");
        nss.add("10.0.1.1");
        nss.add("10.0.2.1");
        List<Route> routes = new ArrayList<>();
        Route r = new Route();
        r.destination = "10.1.1.1";
        r.nexthop = "10.1.1.2";
        Route r2 = new Route();
        r2.destination = "20.1.1.1";
        r2.nexthop = "20.1.1.2";
        routes.add(r);
        routes.add(r2);
        subnet.dnsNameservers = nss;
        subnet.hostRoutes = routes;
        subnet.enableDhcp = true;
        subnet.gatewayIp = "10.0.0.1";
        subnet.ipVersion = 4;
        subnet.name = "sub";
        subnet.tenantId = "tenant";
        subnet.id = UUID.randomUUID();

        return subnet;
    }

    public Port createStockPort(UUID subnetId, UUID networkId, UUID defaultSgId) {
        Port port = new Port();
        port.adminStateUp = true;
        List<IPAllocation> ips = new ArrayList<>();
        IPAllocation ip = new IPAllocation();
        ip.ipAddress = "10.0.0.10";
        ip.subnetId = subnetId;
        ips.add(ip);
        List<UUID> secGroups = new ArrayList<>();
        if (defaultSgId != null) {
            secGroups.add(defaultSgId);
        }
        port.fixedIps = ips;
        port.tenantId = "tenant";
        port.networkId = networkId;
        port.macAddress = "aa:bb:cc:00:11:22";
        port.securityGroups = secGroups;
        port.id = UUID.randomUUID();
        return port;
    }

    public SecurityGroup createStockSecurityGroup() {
        SecurityGroup sg = new SecurityGroup();
        sg.description = "block stuff";
        sg.tenantId = "tenant";
        sg.id = UUID.randomUUID();
        sg.name = "nameOfSg";
        SecurityGroupRule sgr1 = new SecurityGroupRule();
        sgr1.direction = RuleDirection.INGRESS;
        sgr1.ethertype = RuleEthertype.IPv4;
        sgr1.securityGroupId = sg.id;
        sgr1.id = UUID.randomUUID();
        SecurityGroupRule sgr2 = new SecurityGroupRule();
        sgr2.direction = RuleDirection.INGRESS;
        sgr2.ethertype = RuleEthertype.IPv4;
        sgr2.securityGroupId = sg.id;
        sgr2.id = UUID.randomUUID();
        sg.securityGroupRules = new ArrayList<>();
        sg.securityGroupRules.add(sgr1);
        sg.securityGroupRules.add(sgr2);
        return sg;
    }

    public Router createStockRouter() {
        Router r = new Router();
        r.id = UUID.randomUUID();
        r.externalGatewayInfo = new ExternalGatewayInfo();
        r.adminStateUp = true;
        r.tenantId = "tenant";
        return r;
    }

    public RouterInterface createStockRouterInterface(UUID portId, UUID subnetId) throws StateAccessException {
        RouterInterface ri = new RouterInterface();
        ri.id = UUID.randomUUID();
        ri.portId = portId;
        ri.subnetId = subnetId;
        return ri;
    }

    public void verifyIpAddrGroups() throws StateAccessException, SerializationException {
        List<IpAddrGroup> ipgs = dataClient.ipAddrGroupsGetAll();
        List<Port> ports = plugin.getPorts();

        // Each Ip under the ipaddr groups needs to be associated with a port
        for (IpAddrGroup ipg : ipgs) {
            Set<String> ips = dataClient.getAddrsByIpAddrGroup(ipg.getId());
            for (String ip : ips) {
                boolean found = false;
                for (Port port : ports) {
                    if (port.securityGroups.contains(ipg.getId())) {
                        for (IPAllocation ipAllocation : port.fixedIps) {
                            if (ipAllocation.ipAddress.equals(ip)) {
                                found = true;
                            }
                        }
                    }
                }
                Assert.assertTrue(found);
            }
        }
    }

    public void verifySGRules(Port port) throws StateAccessException, SerializationException {

        // Ensure that the port has both of its INBOUND and OUTBOUND chains.
        Chain inbound = null, outbound = null;
        for (Chain c : dataClient.chainsGetAll()) {
            String cName = c.getName();
            if (cName == null) {
                continue;
            }
            if (cName.contains("INBOUND") && cName.contains(port.id.toString())) {
                inbound = c;
            } else if (cName.contains("OUTBOUND") && cName.contains(port.id.toString())) {
                outbound = c;
            }
        }
        Assert.assertNotNull(inbound);
        Assert.assertNotNull(outbound);

        List<Rule<?, ?>> inboundRules = dataClient.rulesFindByChain(inbound.getId());
        List<Rule<?, ?>> outboundRules = dataClient.rulesFindByChain(outbound.getId());

        Assert.assertTrue(isAcceptReturnFlowRule(outboundRules.get(0)));
        Assert.assertTrue(isDropAllExceptArpRule(outboundRules.get(outboundRules.size() - 1)));

        List<Rule<?, ?>> spoofRules = inboundRules.subList(0, port.fixedIps.size());
        for (IPAllocation ip : port.fixedIps) {

            // verify the rule exists
            boolean found = false;
            for (Rule<?, ?> r : spoofRules) {
                if (isIpSpoofProtectionRule(ip, r)) {
                    found = true;
                    break;
                }
            }
            Assert.assertTrue(found);
        }

        Assert.assertTrue(isMacSpoofProtectionRule(port.macAddress, inboundRules.get(spoofRules.size())));

        Assert.assertTrue(isAcceptReturnFlowRule(inboundRules.get(spoofRules.size() + 1)));

        List<Rule<?, ?>> sgJumpRulesInbound = inboundRules.subList(spoofRules.size() + 2, inboundRules.size() - 1);
        // TODO: FAILS
        //Assert.assertEquals(sgJumpRulesInbound.size(),
        //        port.securityGroups.size());

        List<Rule<?, ?>> sgJumpRulesOutbound = outboundRules.subList(1, outboundRules.size() - 1);

        // TODO: FAILS
        //Assert.assertEquals(sgJumpRulesOutbound.size(),
        //        port.securityGroups.size());

        for (UUID sgid : port.securityGroups) {
            // First verify that the security group chains exists
            Chain ingress = null, egress = null;
            for (Chain c : dataClient.chainsGetAll()) {
                String cName = c.getName();
                if (cName == null) {
                    continue;
                }
                if (cName.contains("INGRESS") && cName.contains(sgid.toString())) {
                    ingress = c;
                } else if (cName.contains("EGRESS") && cName.contains(sgid.toString())) {
                    egress = c;
                }
            }
            Assert.assertNotNull(ingress);
            Assert.assertNotNull(egress);

            // Each security group should have all of this ports ips
            for (IPAllocation ip : port.fixedIps) {
                // verify this ip is part of the ip addr group associated
                // with this security group
                Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sgid, ip.ipAddress));
            }

            //Verify that there is a jump rule to the egress and ingress chains
            boolean inboundFound = false, outboundFound = false;
            for (Rule<?, ?> r : sgJumpRulesInbound) {
                JumpRule jr = (JumpRule) r;
                if (egress.getName().equals(jr.getJumpToChainName())
                        && egress.getId().equals(jr.getJumpToChainId())) {
                    inboundFound = true;
                }
            }
            for (Rule<?, ?> r : sgJumpRulesOutbound) {
                JumpRule jr = (JumpRule) r;
                if (ingress.getName().equals(jr.getJumpToChainName())
                        && ingress.getId().equals(jr.getJumpToChainId())) {
                    outboundFound = true;
                }
            }
            Assert.assertTrue(inboundFound);
            Assert.assertTrue(outboundFound);

            SecurityGroup sg = plugin.getSecurityGroup(sgid);
            for (SecurityGroupRule sgr : sg.securityGroupRules) {
                SecurityGroupRule zkSgr = plugin.getSecurityGroupRule(sgr.id);
                Assert.assertTrue(Objects.equal(sgr, zkSgr));
                Rule<?, ?> r = dataClient.rulesGet(sgr.id);
                Assert.assertNotNull(r);
            }
        }

        Assert.assertTrue(isDropAllExceptArpRule(inboundRules.get(inboundRules.size() - 1)));
    }

    private class CheckpointMockZookeeperConnectionModule extends MockZookeeperConnectionModule {

        @Override
        protected void bindDirectory() {
            CheckpointedDirectory dir = new CheckpointedMockDirectory();
            bind(Directory.class).toInstance(dir);
            bind(CheckpointedDirectory.class).toInstance(dir);
            expose(CheckpointedDirectory.class);
        }
    }

    @Before
    public void initialize() throws InterruptedException, KeeperException {
        HierarchicalConfiguration config = fillConfig(new HierarchicalConfiguration());
        injector = Guice.createInjector(new VersionModule(), new SerializationModule(),
                new ConfigProviderModule(config), new CheckpointMockZookeeperConnectionModule(),
                new TestDataClientModule(), new NeutronClusterModule(), new AbstractModule() {
                    @Override
                    protected void configure() {
                        bind(NeutronPlugin.class);
                    }
                });
        injector.injectMembers(this);
        Setup.ensureZkDirectoryStructureExists(zkDir(), zkRoot);
    }

    @Test
    public void testSubnetCRUD() throws StateAccessException, SerializationException {
        int cp1 = zkDir().createCheckPoint();
        Network network = plugin.createNetwork(createStockNetwork());
        int cp2 = zkDir().createCheckPoint();

        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);
        int cp3 = zkDir().createCheckPoint();

        subnet.enableDhcp = false;
        plugin.updateSubnet(subnet.id, subnet);
        int cp4 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp3, cp4).size() == 0);
        assert (zkDir().getModifiedPaths(cp3, cp4).size() == 1);
        assert (zkDir().getAddedPaths(cp3, cp4).size() == 0);

        subnet.enableDhcp = true;
        plugin.updateSubnet(subnet.id, subnet);
        int cp5 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp4, cp5).size() == 0);
        assert (zkDir().getModifiedPaths(cp4, cp5).size() == 1);
        assert (zkDir().getAddedPaths(cp4, cp5).size() == 0);

        plugin.deleteSubnet(subnet.id);
        int cp6 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp2, cp6).size() == 0);
        assert (zkDir().getModifiedPaths(cp2, cp6).size() == 0);
        assert (zkDir().getAddedPaths(cp2, cp6).size() == 0);

        plugin.deleteNetwork(network.id);
        int cp7 = zkDir().createCheckPoint();
        assert (zkDir().getRemovedPaths(cp1, cp7).size() == 0);
        assert (zkDir().getModifiedPaths(cp1, cp7).size() == 0);
        // There is one added path we expect: the gre tunnel key
        assert (zkDir().getAddedPaths(cp1, cp7).size() == 1);
    }

    @Test
    public void testNetworkCRUD()
            throws SerializationException, StateAccessException, BridgeZkManager.VxLanPortIdUpdateException {
        int cp1 = zkDir().createCheckPoint();
        Network network = createStockNetwork();
        network.external = true;
        network = plugin.createNetwork(createStockNetwork());
        int cp2 = zkDir().createCheckPoint();

        network.adminStateUp = false;
        network = plugin.updateNetwork(network.id, network);
        int cp3 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp2, cp3).size() == 0);
        assert (zkDir().getModifiedPaths(cp2, cp3).size() == 2);
        assert (zkDir().getAddedPaths(cp2, cp3).size() == 0);

        network.adminStateUp = true;
        network = plugin.updateNetwork(network.id, network);
        int cp4 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp3, cp4).size() == 0);
        assert (zkDir().getModifiedPaths(cp3, cp4).size() == 2);
        assert (zkDir().getAddedPaths(cp3, cp4).size() == 0);

        assert (zkDir().getRemovedPaths(cp2, cp4).size() == 0);
        assert (zkDir().getModifiedPaths(cp2, cp4).size() == 0);
        assert (zkDir().getAddedPaths(cp2, cp4).size() == 0);

        plugin.deleteNetwork(network.id);
        int cp5 = zkDir().createCheckPoint();
        assert (zkDir().getRemovedPaths(cp1, cp5).size() == 0);
        assert (zkDir().getModifiedPaths(cp1, cp5).size() == 0);
        assert (zkDir().getAddedPaths(cp1, cp5).size() == 1);
    }

    @Test
    public void testPortCRUD()
            throws SerializationException, StateAccessException, Rule.RuleIndexOutOfBoundsException {
        Network network = plugin.createNetwork(createStockNetwork());
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);

        int cp1 = zkDir().createCheckPoint();

        Port port = createStockPort(subnet.id, network.id, null);
        port = plugin.createPort(port);
        int cp2 = zkDir().createCheckPoint();

        Port dhcpPort = createStockPort(subnet.id, network.id, null);
        dhcpPort.deviceOwner = DeviceOwner.DHCP;
        dhcpPort = plugin.createPort(dhcpPort);

        int cp3 = zkDir().createCheckPoint();

        dhcpPort.securityGroups.add(UUID.randomUUID());
        dhcpPort = plugin.updatePort(dhcpPort.id, dhcpPort);

        int cp4 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp3, cp4).size() == 0);
        assert (zkDir().getModifiedPaths(cp3, cp4).size() == 1);
        assert (zkDir().getAddedPaths(cp3, cp4).size() == 0);

        plugin.deletePort(dhcpPort.id);
        int cp6 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp2, cp6).size() == 0);
        assert (zkDir().getModifiedPaths(cp2, cp6).size() == 0);
        assert (zkDir().getAddedPaths(cp2, cp6).size() == 0);

        plugin.deletePort(port.id);

        int cp7 = zkDir().createCheckPoint();

        assert (zkDir().getRemovedPaths(cp1, cp7).size() == 0);
        assert (zkDir().getModifiedPaths(cp1, cp7).size() == 0);
        assert (zkDir().getAddedPaths(cp1, cp7).size() == 0);

        plugin.deleteSubnet(subnet.id);
        plugin.deleteNetwork(network.id);
    }

    @Test
    public void testSecurityGroupCRUD()
            throws SerializationException, StateAccessException, Rule.RuleIndexOutOfBoundsException {
        Network network = plugin.createNetwork(createStockNetwork());
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);

        int cp1 = zkDir().createCheckPoint();

        // Create a security group and a port
        SecurityGroup csg = createStockSecurityGroup();
        Collections.sort(csg.securityGroupRules);
        SecurityGroup sg = plugin.createSecurityGroup(csg);
        Collections.sort(sg.securityGroupRules);
        Assert.assertTrue(Objects.equal(sg, csg));

        Port port = createStockPort(subnet.id, network.id, sg.id);
        port = plugin.createPort(port);

        // Create a second security group and add it to the port
        SecurityGroup sg2 = createStockSecurityGroup();
        sg2 = plugin.createSecurityGroup(sg2);

        port.securityGroups.add(sg2.id);
        port = plugin.updatePort(port.id, port);
        verifyIpAddrGroups();
        verifySGRules(port);

        // Create a second port and add one of the security groups to it
        Port port2 = createStockPort(subnet.id, network.id, null);
        port2.id = UUID.randomUUID();
        port2.fixedIps = new ArrayList<>();
        port2.fixedIps.add(new IPAllocation("10.0.1.0", subnet.id));
        port2.macAddress = "11:22:33:44:55:66";
        port2 = plugin.createPort(port2);
        verifyIpAddrGroups();
        verifySGRules(port2);
        verifySGRules(port);

        // Add a security group to port2
        port2.securityGroups.add(sg2.id);
        port2 = plugin.updatePort(port2.id, port2);
        verifyIpAddrGroups();
        verifySGRules(port2);
        verifySGRules(port);

        // Remove a security group from a port
        port2.securityGroups = new ArrayList<>();
        port2 = plugin.updatePort(port2.id, port2);
        verifyIpAddrGroups();
        verifySGRules(port2);
        verifySGRules(port);

        // Remove all security groups from a port
        port.securityGroups = new ArrayList<>();
        port = plugin.updatePort(port.id, port);
        verifyIpAddrGroups();
        verifySGRules(port2);
        verifySGRules(port);

        // Create a third port with no fixedIps
        Port port3 = createStockPort(subnet.id, network.id, null);
        port3.id = UUID.randomUUID();
        port3.fixedIps = new ArrayList<>();
        port3.macAddress = "11:22:33:44:55:66";
        port3 = plugin.createPort(port3);
        verifyIpAddrGroups();
        verifySGRules(port3);
        verifySGRules(port2);
        verifySGRules(port);

        port3.name = "RYU";
        port3 = plugin.updatePort(port3.id, port3);
        verifyIpAddrGroups();
        verifySGRules(port3);
        verifySGRules(port2);
        verifySGRules(port);

        // Delete security groups
        plugin.deleteSecurityGroup(sg.id);
        plugin.deleteSecurityGroup(sg2.id);

        plugin.deletePort(port.id);
        plugin.deletePort(port2.id);
        plugin.deletePort(port3.id);
        verifyIpAddrGroups();

        int cp2 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp2).size(), 0);
        // TODO: FAILS
        //Assert.assertEquals(zkDir().getAddedPaths(cp1, cp2).size(), 0);
    }

    @Test
    public void testSGSameIpCreate()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        Network network = plugin.createNetwork(createStockNetwork());
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);

        Network network2 = plugin.createNetwork(createStockNetwork());
        Subnet subnet2 = createStockSubnet();
        subnet2.networkId = network2.id;
        subnet2 = plugin.createSubnet(subnet2);

        SecurityGroup sg = createStockSecurityGroup();
        sg = plugin.createSecurityGroup(sg);

        Port port = createStockPort(subnet.id, network.id, sg.id);
        port = plugin.createPort(port);

        String bothPortsIp = port.fixedIps.get(0).ipAddress;
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg.id, bothPortsIp));

        Port port2 = createStockPort(subnet2.id, network2.id, sg.id);
        port2.id = UUID.randomUUID();
        port2 = plugin.createPort(port2);
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg.id, bothPortsIp));

        // Delete just one of the ports. The IP should remain.
        plugin.deletePort(port.id);
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg.id, bothPortsIp));

        // Delete the other port. The IP should not be present.
        plugin.deletePort(port2.id);
        Assert.assertFalse(dataClient.ipAddrGroupHasAddr(sg.id, bothPortsIp));

        plugin.deleteSecurityGroup(sg.id);
        plugin.deleteSubnet(subnet.id);
        plugin.deleteNetwork(network.id);
        plugin.deleteSubnet(subnet2.id);
        plugin.deleteNetwork(network2.id);
    }

    @Test
    public void testSGSameIpUpdate()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        Network network = plugin.createNetwork(createStockNetwork());
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);

        Network network2 = plugin.createNetwork(createStockNetwork());
        Subnet subnet2 = createStockSubnet();
        subnet2.networkId = network2.id;
        subnet2 = plugin.createSubnet(subnet2);

        SecurityGroup sg = createStockSecurityGroup();
        sg = plugin.createSecurityGroup(sg);

        SecurityGroup sg2 = createStockSecurityGroup();
        sg2 = plugin.createSecurityGroup(sg2);

        Port port = createStockPort(subnet.id, network.id, sg.id);
        port = plugin.createPort(port);

        String portsIp = port.fixedIps.get(0).ipAddress;
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg.id, portsIp));
        port.securityGroups.add(sg2.id);
        port = plugin.updatePort(port.id, port);
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg.id, portsIp));
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg2.id, portsIp));

        port.securityGroups = new ArrayList<>();
        port.securityGroups.add(sg2.id);
        port = plugin.updatePort(port.id, port);

        Assert.assertFalse(dataClient.ipAddrGroupHasAddr(sg.id, portsIp));
        Assert.assertTrue(dataClient.ipAddrGroupHasAddr(sg2.id, portsIp));

        port.securityGroups = new ArrayList<>();
        plugin.updatePort(port.id, port);

        Assert.assertFalse(dataClient.ipAddrGroupHasAddr(sg.id, portsIp));
        Assert.assertFalse(dataClient.ipAddrGroupHasAddr(sg2.id, portsIp));
    }

    @Test
    public void testAddRouterInterfaceCreateDelete()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        Network network = createStockNetwork();
        network = plugin.createNetwork(network);
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);
        int cp1 = zkDir().createCheckPoint();

        Router r = createStockRouter();
        r = plugin.createRouter(r);
        int cp2 = zkDir().createCheckPoint();

        Port p = createStockPort(subnet.id, network.id, null);
        p.deviceOwner = DeviceOwner.ROUTER_INTF;
        p.deviceId = r.id.toString();
        p = plugin.createPort(p);

        RouterInterface ri = createStockRouterInterface(p.id, subnet.id);
        ri.id = r.id;
        plugin.addRouterInterface(r.id, ri);

        plugin.deletePort(p.id);
        int cp3 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp2, cp3).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp2, cp3).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp2, cp3).size(), 0);

        plugin.deleteRouter(r.id);
        int cp4 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp4).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp4).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp1, cp4).size(), 0);
    }

    @Test
    public void testRouterInterfaceConvertPort()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        Network network = createStockNetwork();
        network = plugin.createNetwork(network);
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);
        int cp1 = zkDir().createCheckPoint();

        Router r = createStockRouter();
        r = plugin.createRouter(r);
        int cp2 = zkDir().createCheckPoint();

        Port p = createStockPort(subnet.id, network.id, null);
        p = plugin.createPort(p);

        RouterInterface ri = createStockRouterInterface(p.id, subnet.id);
        ri.id = r.id;
        ri.portId = p.id;
        ri = plugin.addRouterInterface(r.id, ri);

        plugin.deletePort(p.id);
        int cp3 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp2, cp3).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp2, cp3).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp2, cp3).size(), 0);

        plugin.deleteRouter(r.id);
        int cp4 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp4).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp4).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp1, cp4).size(), 0);
    }

    @Test
    public void testRouterGatewayCreate()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        Network network = createStockNetwork();
        network.external = true;
        network = plugin.createNetwork(network);
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);
        int cp1 = zkDir().createCheckPoint();

        Port port = createStockPort(subnet.id, network.id, null);
        port.deviceOwner = DeviceOwner.ROUTER_GW;
        port = plugin.createPort(port);

        Router router = createStockRouter();
        router.externalGatewayInfo.networkId = network.id;
        router.externalGatewayInfo.enableSnat = true;
        router.gwPortId = port.id;
        router = plugin.createRouter(router);

        plugin.deletePort(port.id);
        plugin.deleteRouter(router.id);

        int cp2 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp1, cp2).size(), 0);
    }

    private void verifySnatAddr(UUID routerId, String snatIp)
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        org.midonet.cluster.data.Router routerMido = dataClient.routersGet(routerId);

        List<Rule<?, ?>> inboundRules = dataClient.rulesFindByChain(routerMido.getInboundFilter());
        List<Rule<?, ?>> outboundRules = dataClient.rulesFindByChain(routerMido.getOutboundFilter());

        boolean foundSnat = false;
        boolean foundRevSnat = false;

        for (Rule<?, ?> r : inboundRules) {
            String dstAddr = r.getCondition().nwDstIp.toString();
            if (dstAddr.equals(snatIp + "/32")) {
                foundSnat = true;
            }
        }

        for (Rule<?, ?> r : outboundRules) {
            if (r instanceof ForwardNatRule) {
                Set<NatTarget> targets = ((ForwardNatRule) r).getTargets();
                for (NatTarget nt : targets) {
                    if (nt.getNwEnd().equals(snatIp) && nt.getNwStart().equals(snatIp)) {
                        foundRevSnat = true;
                    }
                }
            }
        }

        Assert.assertTrue(foundSnat && foundRevSnat);
    }

    private void verifyStaticNat(FloatingIp floatingIp, Port port, Router router)
            throws StateAccessException, SerializationException {
        boolean snatRuleFound = false;
        boolean dnatRuleFound = false;

        org.midonet.cluster.data.Router zkRouter = dataClient.routersGet(router.id);

        List<Rule<?, ?>> outRules = dataClient.rulesFindByChain(zkRouter.getInboundFilter());
        List<Rule<?, ?>> inRules = dataClient.rulesFindByChain(zkRouter.getOutboundFilter());

        for (Rule<?, ?> r : inRules) {
            if (r instanceof ForwardNatRule) {
                ForwardNatRule fnr = (ForwardNatRule) r;
                for (NatTarget target : fnr.getTargets()) {
                    if (Objects.equal(target.getNwStart(), floatingIp.floatingIpAddress)
                            && Objects.equal(target.getNwEnd(), floatingIp.floatingIpAddress) && target.tpEnd == 0
                            && target.tpStart == 0) {
                        snatRuleFound = true;
                    }
                }
            }
        }
        Assert.assertTrue(snatRuleFound);
        for (Rule<?, ?> r : outRules) {
            if (r instanceof ForwardNatRule) {
                ForwardNatRule fnr = (ForwardNatRule) r;
                for (NatTarget target : fnr.getTargets()) {
                    if (Objects.equal(target.getNwStart(), port.fixedIps.get(0).ipAddress)
                            && Objects.equal(target.getNwEnd(), port.fixedIps.get(0).ipAddress) && target.tpEnd == 0
                            && target.tpStart == 0) {
                        dnatRuleFound = true;
                    }
                }
            }
        }
        Assert.assertTrue(dnatRuleFound);
    }

    @Test
    public void testRouterGatewayUpdate()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        Network network = createStockNetwork();
        network.external = true;
        network = plugin.createNetwork(network);
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);

        Network network2 = createStockNetwork();
        network2.external = true;
        network2 = plugin.createNetwork(network2);
        Subnet subnet2 = createStockSubnet();
        subnet2.networkId = network2.id;
        subnet2.cidr = "10.0.1.0/24";
        subnet2 = plugin.createSubnet(subnet2);

        int cp1 = zkDir().createCheckPoint();

        Port port2 = createStockPort(subnet2.id, network2.id, null);
        port2.fixedIps = new ArrayList<>();
        IPAllocation ip = new IPAllocation();
        ip.ipAddress = "10.0.1.10";
        ip.subnetId = subnet2.id;
        port2.fixedIps.add(ip);
        port2.deviceOwner = DeviceOwner.ROUTER_GW;
        port2 = plugin.createPort(port2);

        Port port = createStockPort(subnet.id, network.id, null);
        port.deviceOwner = DeviceOwner.ROUTER_GW;
        port = plugin.createPort(port);

        Router router = createStockRouter();
        router = plugin.createRouter(router);

        router.externalGatewayInfo.networkId = network.id;
        router.externalGatewayInfo.enableSnat = true;
        router.gwPortId = port.id;

        router = plugin.updateRouter(router.id, router);
        verifySnatAddr(router.id, port.fixedIps.get(0).ipAddress);

        router.externalGatewayInfo.networkId = network2.id;
        router.gwPortId = port2.id;

        plugin.deletePort(port.id);
        router = plugin.updateRouter(router.id, router);
        verifySnatAddr(router.id, port2.fixedIps.get(0).ipAddress);

        router.externalGatewayInfo.networkId = null;
        router.externalGatewayInfo.enableSnat = false;
        router.gwPortId = null;

        plugin.deletePort(port2.id);
        router = plugin.updateRouter(router.id, router);

        plugin.deletePort(port2.id);
        plugin.deleteRouter(router.id);
        int cp2 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp1, cp2).size(), 0);
    }

    @Test
    public void testFIPCreateDelete()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {

        // Create the external network
        Network network = createStockNetwork();
        network.external = true;
        network = plugin.createNetwork(network);
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);
        int cp1 = zkDir().createCheckPoint();

        // Create a router and hook it up to the external network
        Port port = createStockPort(subnet.id, network.id, null);
        port.deviceOwner = DeviceOwner.ROUTER_GW;
        port = plugin.createPort(port);
        Router router = createStockRouter();
        router.externalGatewayInfo.networkId = network.id;
        router.gwPortId = port.id;
        router = plugin.createRouter(router);

        // Create a new network
        Network network2 = createStockNetwork();
        network2 = plugin.createNetwork(network2);
        Subnet subnet2 = createStockSubnet();
        subnet2.networkId = network2.id;
        subnet2 = plugin.createSubnet(subnet2);

        Port p = createStockPort(subnet2.id, network2.id, null);
        p = plugin.createPort(p);

        int cp2 = zkDir().createCheckPoint();
        FloatingIp fip = new FloatingIp();
        fip.routerId = router.id;
        fip.fixedIpAddress = p.fixedIps.get(0).ipAddress;
        fip.floatingIpAddress = "10.0.1.5";
        fip.portId = p.id;
        fip.floatingNetworkId = network.id;
        fip.tenantId = "tenant";
        fip.id = UUID.randomUUID();
        fip = plugin.createFloatingIp(fip);
        verifyStaticNat(fip, p, router);

        plugin.deleteFloatingIp(fip.id);
        int cp3 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp2, cp3).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp2, cp3).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp2, cp3).size(), 0);
    }

    @Test
    public void testFIPUpdate()
            throws StateAccessException, SerializationException, Rule.RuleIndexOutOfBoundsException {
        // Create the external network
        Network network = createStockNetwork();
        network.external = true;
        network = plugin.createNetwork(network);
        Subnet subnet = createStockSubnet();
        subnet.networkId = network.id;
        subnet = plugin.createSubnet(subnet);

        // Create a router and hook it up to the external network
        Port port = createStockPort(subnet.id, network.id, null);
        port.deviceOwner = DeviceOwner.ROUTER_GW;
        port = plugin.createPort(port);
        Router router = createStockRouter();
        router.externalGatewayInfo.networkId = network.id;
        router.gwPortId = port.id;
        router = plugin.createRouter(router);

        // Create a new network
        Network network2 = createStockNetwork();
        network2 = plugin.createNetwork(network2);
        Subnet subnet2 = createStockSubnet();
        subnet2.networkId = network2.id;
        subnet2 = plugin.createSubnet(subnet2);

        // Create a

        Port port2 = createStockPort(subnet2.id, network2.id, null);
        port2 = plugin.createPort(port2);

        Port port3 = createStockPort(subnet2.id, network2.id, null);
        port3.fixedIps = new ArrayList<>();
        IPAllocation ip = new IPAllocation();
        ip.ipAddress = "10.0.0.11";
        ip.subnetId = subnet2.id;
        port3.fixedIps.add(ip);
        port3.macAddress = "01:23:45:67:89:ab";
        port3 = plugin.createPort(port3);

        // Create a floating IP not attached to a port
        FloatingIp fip = new FloatingIp();
        fip.routerId = router.id;
        fip.floatingIpAddress = "10.0.1.5";
        fip.floatingNetworkId = network.id;
        fip.tenantId = "tenant";
        fip.id = UUID.randomUUID();
        fip = plugin.createFloatingIp(fip);
        int cp1 = zkDir().createCheckPoint();

        // update to associate
        fip.fixedIpAddress = port2.fixedIps.get(0).ipAddress;
        fip.portId = port2.id;
        fip = plugin.updateFloatingIp(fip.id, fip);

        // update to disassociate
        fip.fixedIpAddress = null;
        fip.portId = null;
        fip = plugin.updateFloatingIp(fip.id, fip);
        int cp2 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp2).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp1, cp2).size(), 0);

        // Update to associate
        fip.fixedIpAddress = port3.fixedIps.get(0).ipAddress;
        fip.portId = port3.id;
        fip = plugin.updateFloatingIp(fip.id, fip);

        // update to disassociate
        fip.fixedIpAddress = null;
        fip.portId = null;
        fip = plugin.updateFloatingIp(fip.id, fip);
        int cp3 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp1, cp3).size(), 0);
        Assert.assertEquals(zkDir().getModifiedPaths(cp1, cp3).size(), 0);
        Assert.assertEquals(zkDir().getAddedPaths(cp1, cp3).size(), 0);

        // Now try with a FIP created with a port attached
        // Create a floating IP not attached to a port
        FloatingIp fip2 = new FloatingIp();
        fip2.routerId = router.id;
        fip2.fixedIpAddress = port3.fixedIps.get(0).ipAddress;
        fip2.floatingIpAddress = "10.0.1.5";
        fip2.portId = port3.id;
        fip2.floatingNetworkId = network.id;
        fip2.tenantId = "tenant";
        fip2.id = UUID.randomUUID();
        fip2 = plugin.createFloatingIp(fip2);
        int cp4 = zkDir().createCheckPoint();

        // update to disassociate
        fip2.fixedIpAddress = null;
        fip2.portId = null;
        fip2 = plugin.updateFloatingIp(fip2.id, fip2);
        int cp5 = zkDir().createCheckPoint();

        // update to associate
        fip2.fixedIpAddress = port3.fixedIps.get(0).ipAddress;
        fip2.portId = port3.id;
        fip.floatingIpAddress = "10.0.1.5";
        fip2 = plugin.updateFloatingIp(fip2.id, fip2);
        int cp6 = zkDir().createCheckPoint();

        // Added Paths needs to equal Removed paths. Although the content
        // of the paths is logically the same, different UUIDs will be used
        // for the new routes.
        Assert.assertEquals(zkDir().getRemovedPaths(cp4, cp6).size(), zkDir().getAddedPaths(cp4, cp6).size(), 4);
        Assert.assertEquals(zkDir().getModifiedPaths(cp4, cp6).size(), 2);

        // update to associate with a different port
        fip2.fixedIpAddress = port2.fixedIps.get(0).ipAddress;
        fip2.portId = port2.id;
        fip2 = plugin.updateFloatingIp(fip2.id, fip2);

        // associate back to the first port
        fip2.fixedIpAddress = port3.fixedIps.get(0).ipAddress;
        fip2.portId = port3.id;
        fip2 = plugin.updateFloatingIp(fip2.id, fip2);
        int cp7 = zkDir().createCheckPoint();

        Assert.assertEquals(zkDir().getRemovedPaths(cp4, cp7).size(), zkDir().getAddedPaths(cp4, cp7).size(), 4);
        Assert.assertEquals(zkDir().getModifiedPaths(cp4, cp7).size(), 2);
    }
}