com.vmware.photon.controller.housekeeper.xenon.SubnetIPLeaseSyncServiceTest.java Source code

Java tutorial

Introduction

Here is the source code for com.vmware.photon.controller.housekeeper.xenon.SubnetIPLeaseSyncServiceTest.java

Source

/*
 * Copyright 2016 VMware, Inc. All Rights Reserved.
 *
 * 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 com.vmware.photon.controller.housekeeper.xenon;

import com.vmware.photon.controller.cloudstore.xenon.entity.DhcpSubnetService;
import com.vmware.photon.controller.cloudstore.xenon.entity.IpLeaseService;
import com.vmware.photon.controller.common.IpHelper;
import com.vmware.photon.controller.common.clients.HostClientFactory;
import com.vmware.photon.controller.common.xenon.BasicServiceHost;
import com.vmware.photon.controller.common.xenon.CloudStoreHelper;
import com.vmware.photon.controller.common.xenon.ServiceHostUtils;
import com.vmware.photon.controller.common.xenon.ServiceUtils;
import com.vmware.photon.controller.common.xenon.exceptions.BadRequestException;
import com.vmware.photon.controller.housekeeper.helpers.xenon.TestEnvironment;
import com.vmware.photon.controller.housekeeper.helpers.xenon.services.TestServiceWithStage;
import com.vmware.xenon.common.FactoryService;
import com.vmware.xenon.common.Operation;
import com.vmware.xenon.common.Service;
import com.vmware.xenon.common.ServiceHost;
import com.vmware.xenon.common.TaskState;
import com.vmware.xenon.common.UriUtils;
import com.vmware.xenon.common.Utils;

import com.google.common.collect.ImmutableMap;

import org.apache.commons.net.util.SubnetUtils;
import org.hamcrest.Matchers;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.closeTo;
import static org.hamcrest.Matchers.startsWith;
import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.fail;

import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.URI;
import java.util.EnumSet;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Tests {@link com.vmware.photon.controller.housekeeper.xenon.SubnetIPLeaseSyncService}.
 */
public class SubnetIPLeaseSyncServiceTest {

    private static final int TEST_PAGE_LIMIT = 100;
    private static final String SUBNET_ID = "subnetId";

    private BasicServiceHost host;
    private SubnetIPLeaseSyncService service;

    /**
     * Dummy test case to make Intellij recognize this as a test class.
     */
    @Test
    private void dummy() {
    }

    /**
     * Tests for the constructors.
     */
    public class InitializationTest {

        @BeforeMethod
        public void setUp() throws Throwable {
            service = new SubnetIPLeaseSyncService();
        }

        /**
         * Test that the service starts with the expected capabilities.
         */
        @Test
        public void testServiceOptions() {
            // Factory capability is implicitly added as part of the factory constructor.
            EnumSet<Service.ServiceOption> expected = EnumSet.of(Service.ServiceOption.REPLICATION,
                    Service.ServiceOption.OWNER_SELECTION, Service.ServiceOption.INSTRUMENTATION);
            assertThat(service.getOptions(), Matchers.is(expected));
        }
    }

    /**
     * Tests for the handleStart method.
     */
    public class HandleStartTest {
        @BeforeMethod
        public void setUp() throws Throwable {
            service = new SubnetIPLeaseSyncService();
            host = BasicServiceHost.create();
        }

        @AfterMethod
        public void tearDown() throws Throwable {
            if (host != null) {
                BasicServiceHost.destroy(host);
            }

            service = null;
        }

        /**
         * Test start of service.
         *
         * @throws Throwable
         */
        @Test
        public void testStartState() throws Throwable {
            SubnetIPLeaseSyncService.State startState = buildValidStartupState();
            Operation startOp = host.startServiceSynchronously(service, startState);
            assertThat(startOp.getStatusCode(), Matchers.is(200));

            SubnetIPLeaseSyncService.State savedState = host.getServiceState(SubnetIPLeaseSyncService.State.class);
            assertThat(savedState.documentSelfLink, Matchers.is(BasicServiceHost.SERVICE_URI));
            assertThat(new BigDecimal(savedState.documentExpirationTimeMicros),
                    Matchers.is(closeTo(
                            new BigDecimal(ServiceUtils
                                    .computeExpirationTime(ServiceUtils.DEFAULT_DOC_EXPIRATION_TIME_MICROS)),
                            new BigDecimal(TimeUnit.SECONDS.toMicros(10)))));
        }

        /**
         * Tests that exception is raised for all fields that expect a positive value.
         *
         * @param fieldName
         * @param value
         *
         * @throws Throwable
         */
        @Test(dataProvider = "AutoInitializedFields")
        public void testAutoInitializedFields(String fieldName, Object value) throws Throwable {
            SubnetIPLeaseSyncService.State startState = buildValidStartupState();
            Field fieldObj = startState.getClass().getField(fieldName);
            fieldObj.set(startState, null);

            Operation startOp = host.startServiceSynchronously(service, startState);
            assertThat(startOp.getStatusCode(), Matchers.is(200));

            SubnetIPLeaseSyncService.State savedState = host.getServiceState(SubnetIPLeaseSyncService.State.class);
            if (fieldObj.getType().equals(SubnetIPLeaseSyncService.TaskState.class)) {
                assertThat(Utils.toJson(false, false, fieldObj.get(savedState)),
                        Matchers.is(Utils.toJson(false, false, value)));
            } else {
                assertThat(fieldObj.get(savedState), Matchers.is(value));
            }
        }

        @DataProvider(name = "AutoInitializedFields")
        public Object[][] getAutoInitializedFieldsParams() {
            SubnetIPLeaseSyncService.TaskState state = new SubnetIPLeaseSyncService.TaskState();
            state.stage = TaskState.TaskStage.CREATED;

            return new Object[][] { { "taskState", state } };
        }

        /**
         * Test expiration time settings.
         *
         * @param time
         * @param expectedTime
         * @param delta
         *
         * @throws Throwable
         */
        @Test(dataProvider = "ExpirationTime")
        public void testExpirationTimeInitialization(long time, BigDecimal expectedTime, BigDecimal delta)
                throws Throwable {
            SubnetIPLeaseSyncService.State startState = buildValidStartupState();
            startState.documentExpirationTimeMicros = time;

            Operation startOp = host.startServiceSynchronously(service, startState);
            assertThat(startOp.getStatusCode(), Matchers.is(200));

            SubnetIPLeaseSyncService.State savedState = host.getServiceState(SubnetIPLeaseSyncService.State.class);
            assertThat(new BigDecimal(savedState.documentExpirationTimeMicros),
                    Matchers.is(closeTo(expectedTime, delta)));
        }

        @DataProvider(name = "ExpirationTime")
        public Object[][] getExpirationTime() {
            long expTime = ServiceUtils.computeExpirationTime(TimeUnit.HOURS.toMillis(1));

            return new Object[][] {
                    { -10L, new BigDecimal(
                            ServiceUtils.computeExpirationTime(ServiceUtils.DEFAULT_DOC_EXPIRATION_TIME_MICROS)),
                            new BigDecimal(TimeUnit.SECONDS.toMicros(10)) },
                    { 0L, new BigDecimal(
                            ServiceUtils.computeExpirationTime(ServiceUtils.DEFAULT_DOC_EXPIRATION_TIME_MICROS)),
                            new BigDecimal(TimeUnit.SECONDS.toMicros(10)) },
                    { expTime, new BigDecimal(expTime), new BigDecimal(0) } };
        }
    }

    /**
     * Tests for the handlePatch method.
     */
    public class HandlePatchTest {
        SubnetIPLeaseSyncService.State serviceState;

        @BeforeMethod
        public void setUp() throws Throwable {
            host = BasicServiceHost.create();

            service = new SubnetIPLeaseSyncService();
            serviceState = buildValidStartupState();
            host.startServiceSynchronously(service, serviceState);
        }

        @AfterMethod
        public void tearDown() throws Throwable {
            if (host != null) {
                BasicServiceHost.destroy(host);
            }

            service = null;
        }

        /**
         * Test patch operation with invalid payload.
         *
         * @throws Throwable
         */
        @Test
        public void testInvalidPatch() throws Throwable {
            Operation op = Operation.createPatch(UriUtils.buildUri(host, BasicServiceHost.SERVICE_URI, null))
                    .setBody("invalid body");

            try {
                host.sendRequestAndWait(op);
                fail("handlePatch did not throw exception on invalid patch");
            } catch (BadRequestException e) {
                assertThat(e.getMessage(), startsWith(
                        "Unparseable JSON body: java.lang.IllegalStateException: Expected BEGIN_OBJECT"));
            }
        }
    }

    /**
     * Tests for end-to-end scenarios.
     */
    public class EndToEndTest {
        private TestEnvironment machine;
        private TestEnvironment.Builder machineBuilder;

        private HostClientFactory hostClientFactory;
        private CloudStoreHelper cloudStoreHelper;
        private SubnetIPLeaseSyncService.State request;
        public final Map<Class<? extends Service>, Supplier<FactoryService>> factoryServicesMap = ImmutableMap
                .<Class<? extends Service>, Supplier<FactoryService>>builder()
                .put(DhcpSubnetService.class, DhcpSubnetService::createFactory)
                .put(IpLeaseService.class, IpLeaseService::createFactory)
                .put(TestServiceWithStage.class, TestServiceWithStage::createFactory).build();

        @BeforeMethod
        public void setUp() throws Throwable {
            hostClientFactory = mock(HostClientFactory.class);
            cloudStoreHelper = new CloudStoreHelper();

            machineBuilder = new TestEnvironment.Builder().cloudStoreHelper(cloudStoreHelper)
                    .hostClientFactory(hostClientFactory);
            // Build input.
            request = buildValidStartupState();
            request.taskState = new SubnetIPLeaseSyncService.TaskState();
            request.taskState.stage = TaskState.TaskStage.CREATED;
            request.isSelfProgressionDisabled = false;
            request.pageLimit = TEST_PAGE_LIMIT;
        }

        @AfterMethod
        public void tearDown() throws Throwable {
            if (machine != null) {
                // Note that this will fully clean up the Xenon host's Lucene index: all
                // services we created will be fully removed.
                machine.stop();
                machine = null;
            }
        }

        /**
         * Default provider to control host count.
         *
         * @return
         */
        @DataProvider(name = "hostCount")
        public Object[][] getHostCount() {
            return new Object[][] { { 1 }, { TestEnvironment.DEFAULT_MULTI_HOST_COUNT } };
        }

        /**
         * Tests sync success scenarios.
         *
         * @param totalIpLeases
         * @param totalWithNoVMId
         *
         * @throws Throwable
         */
        @Test(dataProvider = "Success")
        public void testSuccess(int totalIpLeases, int totalWithNoVMId, int hostCount) throws Throwable {
            machine = machineBuilder.hostCount(hostCount).build();

            seedTestEnvironment(machine, totalIpLeases, totalWithNoVMId);

            SubnetIPLeaseSyncService.State response = machine.callServiceAndWaitForState(
                    SubnetIPLeaseSyncService.FACTORY_LINK, request, SubnetIPLeaseSyncService.State.class,
                    (SubnetIPLeaseSyncService.State state) -> state.taskState.stage == TaskState.TaskStage.FINISHED);

            if (totalIpLeases > 0) {
                assertThat(response.subnetIPLease.subnetId, Matchers.is(response.subnetId));
                assertNotNull(response.subnetIPLease.ipToMACAddressMap);
                assertThat(response.subnetIPLease.version, Matchers.is(1L));
                assertThat(response.operationPatch.versionStaged, Matchers.is(1L));
                assertThat(response.operationPatch.versionPushed, Matchers.is(1L));
            }
        }

        @DataProvider(name = "Success")
        public Object[][] getSuccessData() {
            return new Object[][] { { 0, 0, 1 }, { 0, 0, TestEnvironment.DEFAULT_MULTI_HOST_COUNT }, { 2, 2, 1 },
                    { 2, 2, TestEnvironment.DEFAULT_MULTI_HOST_COUNT }, { 5, 3, 1 },
                    { 5, 3, TestEnvironment.DEFAULT_MULTI_HOST_COUNT },
                    // Test cases with Ip Lease service documents greater than the default page limit.
                    { TEST_PAGE_LIMIT + 10, TEST_PAGE_LIMIT + 1, 1 },
                    { TEST_PAGE_LIMIT + 10, TEST_PAGE_LIMIT + 1, TestEnvironment.DEFAULT_MULTI_HOST_COUNT }, };
        }

        private void seedTestEnvironment(TestEnvironment env, int totalIpLeases, int totalWithNoVMId)
                throws Throwable {
            ServiceHost[] hostsList = env.getHosts();
            for (int i = 0; i < hostsList.length; i++) {
                ServiceHostUtils.startFactoryServices(hostsList[i], factoryServicesMap);
            }

            createDhcpSubnet(env);
            createIPLeases(env, totalIpLeases, totalWithNoVMId);
        }
    }

    private void createDhcpSubnet(TestEnvironment env) throws Throwable {
        String cidr = "192.168.0.0/16";
        SubnetUtils subnetUtils = new SubnetUtils(cidr);
        SubnetUtils.SubnetInfo subnetInfo = subnetUtils.getInfo();

        DhcpSubnetService.State subnetState = new DhcpSubnetService.State();
        subnetState.subnetId = SUBNET_ID;
        subnetState.version = 1L;
        subnetState.lowIp = IpHelper.ipStringToLong(subnetInfo.getLowAddress());
        subnetState.highIp = IpHelper.ipStringToLong(subnetInfo.getHighAddress());
        subnetState.lowIpDynamic = subnetState.lowIp + 1;
        subnetState.highIpDynamic = subnetState.highIp - 1;
        subnetState.cidr = cidr;
        URI hostUri = env.getHosts()[0].getUri();
        subnetState.dhcpAgentEndpoint = hostUri.getScheme() + "://" + hostUri.getHost() + ":" + hostUri.getPort();

        env.sendPostAndWaitForReplication(DhcpSubnetService.FACTORY_LINK, subnetState);
    }

    private void createIPLeases(TestEnvironment env, int totalIpLeases, int totalWithNoVMId) throws Throwable {
        for (int i = 0; i < totalIpLeases; i++) {
            IpLeaseService.State state = new IpLeaseService.State();
            state.documentSelfLink = "ip-lease-" + i;
            state.subnetId = SUBNET_ID;
            state.ip = "dummy-ip";

            if (i >= totalWithNoVMId) {
                state.ownerVmId = "vmId" + i;
            }

            env.sendPostAndWaitForReplication(IpLeaseService.FACTORY_LINK, state);
        }
    }

    private SubnetIPLeaseSyncService.State buildValidStartupState() {
        SubnetIPLeaseSyncService.State state = new SubnetIPLeaseSyncService.State();
        state.isSelfProgressionDisabled = true;
        state.subnetId = SUBNET_ID;
        return state;
    }
}