Java tutorial
/* * 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.cloudstore.xenon.entity; import com.vmware.photon.controller.cloudstore.xenon.helpers.TestEnvironment; import com.vmware.photon.controller.cloudstore.xenon.helpers.TestHelper; import com.vmware.photon.controller.common.xenon.ServiceHostUtils; import com.vmware.photon.controller.common.xenon.ServiceUriPaths; import com.vmware.xenon.common.Operation; import com.vmware.xenon.common.ServiceHost; import org.apache.commons.math3.stat.descriptive.moment.Mean; import org.apache.commons.math3.stat.descriptive.moment.StandardDeviation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.Matchers.notNullValue; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.IntStream; /** * Tests for SchedulingConstantGenerator. * * This class includes tests for the distribution of scheduling constants, which * work by creating several HostService instances and measuring some properties * of the distribution of their scheduling constants. * The metric of choice is the coefficient of variation of the differences * between each scheduling constant and the next greater scheduling constant. * A smaller coefficient of variation indicates a more even spacing between * scheduling constants. */ public class SchedulingConstantGeneratorTest { private final Logger logger = LoggerFactory.getLogger(SchedulingConstantGeneratorTest.class); // Thread count for concurrent-create tests private static final int THREADS = 10; // Maximum acceptable coefficient of variation of gaps between adjacent // scheduling constants. // This value is somewhat arbitrary, but simulation and testing showed that it // is a good choice for distinguishing randomly-generated and Halton-generated // scheduling constants. private static final double MAX_VARIATION = 1.11; /** * Test that a PATCH operation returns a body with index and lastValue set. */ @Test public void testPatchBody() throws Throwable { TestEnvironment env = TestEnvironment.create(1); Operation patchOp = env.sendPatchAndWait(SchedulingConstantGenerator.SINGLETON_LINK, new SchedulingConstantGenerator.State()); SchedulingConstantGenerator.State state = patchOp.getBody(SchedulingConstantGenerator.State.class); assertThat(state.nextHaltonSequenceIndex, notNullValue()); assertThat(state.lastSchedulingConstant, notNullValue()); env.stop(); } /** * Test that a PATCH operation increments the index field by exactly 1. */ @Test public void testPatchIncrementsIndex() throws Throwable { TestEnvironment env = TestEnvironment.create(1); SchedulingConstantGenerator.State currentState = env.getServiceState( SchedulingConstantGenerator.SINGLETON_LINK, SchedulingConstantGenerator.State.class); int currentIndex = currentState.nextHaltonSequenceIndex; Operation patchOp = env.sendPatchAndWait(SchedulingConstantGenerator.SINGLETON_LINK, new SchedulingConstantGenerator.State()); int newIndex = patchOp.getBody(SchedulingConstantGenerator.State.class).nextHaltonSequenceIndex; assertThat(newIndex, equalTo(currentIndex + 1)); env.stop(); } /** * Test distribution of scheduling constants, creating hosts serially on a * single Xenon host. * * @param hostCount Number of HostService instances to create */ @Test(dataProvider = "HostCounts") public void testSchedulingConstantVariationSerial(int hostCount) throws Throwable { List<Long> schedulingConstants; TestEnvironment env = TestEnvironment.create(1); ServiceHost xenonHost = env.getHosts()[0]; schedulingConstants = createHosts(xenonHost, hostCount); env.stop(); assertThat(schedulingConstants.size(), equalTo(hostCount)); Collections.sort(schedulingConstants); double cv = schedulingConstantGapCV(schedulingConstants); logger.info("Scheduling constant gap coefficient of variation: {}", cv); assertThat(cv, lessThan(MAX_VARIATION)); } /** * Test distribution of scheduling constants, creating hosts concurrently on a * single Xenon host. */ @Test(dataProvider = "HostCounts") public void testSchedulingConstantVariationConcurrent(int hostCount) throws Throwable { List<Long> schedulingConstants = Collections.synchronizedList(new ArrayList<>()); TestEnvironment env = TestEnvironment.create(1); List<Thread> threads = new ArrayList<>(); ServiceHost xenonHost = env.getHosts()[0]; IntStream.range(0, THREADS).forEach((threadId) -> { Thread t = new Thread(() -> { List<Long> thisThreadSchedulingConstants = createHosts(xenonHost, hostCount); schedulingConstants.addAll(thisThreadSchedulingConstants); }); t.start(); threads.add(t); }); for (Thread t : threads) { t.join(); } env.stop(); assertThat(schedulingConstants.size(), equalTo(hostCount * THREADS)); Collections.sort(schedulingConstants); double cv = schedulingConstantGapCV(schedulingConstants); logger.info("Scheduling constant gap coefficient of variation: {}", cv); assertThat(cv, lessThan(MAX_VARIATION)); } /** * Test distribution of scheduling constants, creating hosts on multiple Xenon * hosts, one thread per Xenon host. */ @Test(dataProvider = "MultiHostHostCounts") public void testSchedulingConstantVariationMultiHost(int xenonHostCount, int hostCount) throws Throwable { List<Long> schedulingConstants = Collections.synchronizedList(new ArrayList<>()); TestEnvironment env = TestEnvironment.create(xenonHostCount); List<Thread> threads = new ArrayList<>(); ServiceHost[] xenonHosts = env.getHosts(); IntStream.range(0, xenonHostCount).forEach((xenonHostId) -> { Thread t = new Thread(() -> { List<Long> thisThreadSchedulingConstants = createHosts(xenonHosts[xenonHostId], hostCount); schedulingConstants.addAll(thisThreadSchedulingConstants); }); t.start(); threads.add(t); }); for (Thread t : threads) { t.join(); } env.stop(); assertThat(schedulingConstants.size(), equalTo(hostCount * xenonHostCount)); Collections.sort(schedulingConstants); double cv = schedulingConstantGapCV(schedulingConstants); logger.info("Scheduling constant gap coefficient of variation: {}", cv); assertThat(cv, lessThan(MAX_VARIATION)); } /** * Test for distinct scheduling constants, creating hosts serially on a single * Xenon host. */ @Test(dataProvider = "HostCounts") public void testDistinctSchedulingConstantsSerial(int hostCount) throws Throwable { List<Long> schedulingConstants; TestEnvironment env = TestEnvironment.create(1); ServiceHost xenonHost = env.getHosts()[0]; schedulingConstants = createHosts(xenonHost, hostCount); env.stop(); assertThat(schedulingConstants.size(), equalTo(hostCount)); // Check that all scheduling constants are distinct by adding them to a set // and checking the size of the set. Set<Long> schedulingConstantsSet = new HashSet<>(); schedulingConstantsSet.addAll(schedulingConstants); assertThat(schedulingConstantsSet.size(), equalTo(schedulingConstants.size())); } /** * Test for distinct scheduling constants, creating hosts concurrently on a * single Xenon host. */ @Test(dataProvider = "HostCounts") public void testDistinctSchedulingConstantsConcurrent(int hostCount) throws Throwable { List<Long> schedulingConstants = Collections.synchronizedList(new ArrayList<>()); TestEnvironment env = TestEnvironment.create(1); List<Thread> threads = new ArrayList<>(); ServiceHost xenonHost = env.getHosts()[0]; IntStream.range(0, THREADS).forEach((threadId) -> { Thread t = new Thread(() -> { List<Long> thisThreadSchedulingConstants = createHosts(xenonHost, hostCount); schedulingConstants.addAll(thisThreadSchedulingConstants); }); t.start(); threads.add(t); }); for (Thread t : threads) { t.join(); } env.stop(); assertThat(schedulingConstants.size(), equalTo(hostCount * THREADS)); // Check that all scheduling constants are distinct (see note in // testDistinctSchedulingConstantsSerial) Set<Long> schedulingConstantsSet = new HashSet<>(); schedulingConstantsSet.addAll(schedulingConstants); assertThat(schedulingConstantsSet.size(), equalTo(schedulingConstants.size())); } /** * Create several new HostService instances on the given Xenon ServiceHost. * * @return list of the newly-created hosts' scheduling constants. */ private List<Long> createHosts(ServiceHost xenonHost, int hostCount) { List<Long> schedulingConstants = new ArrayList<>(); for (int i = 0; i < hostCount; i++) { try { HostService.State h = TestHelper.getHostServiceStartState(); Operation op = Operation.createPost(xenonHost, HostServiceFactory.SELF_LINK) .setReferer(ServiceUriPaths.CLOUDSTORE_ROOT).setBody(h); Operation completedOp = ServiceHostUtils.sendRequestAndWait(xenonHost, op, ServiceUriPaths.CLOUDSTORE_ROOT); long newSchedConst = completedOp.getBody(HostService.State.class).schedulingConstant; schedulingConstants.add(newSchedConst); } catch (Throwable t) { logger.error("Exception creating host: {}", t); // Skip this iteration by swallowing the exception. The caller should // assert that the length of this function's output is equal to // hostCount; that should correctly cause tests to fail in case of error // here. } } return schedulingConstants; } /** * Compute the coefficient of variation of the gaps between adjacent * scheduling constants. * * @param schedulingConstants Sorted list of scheduling constants. */ private double schedulingConstantGapCV(List<Long> schedulingConstants) { // Compute the difference between each scheduling constant and the next. double[] gaps = new double[schedulingConstants.size()]; for (int i = 0; i < schedulingConstants.size(); i++) { long gap; // Special case at end of list: wrap around if (i == schedulingConstants.size() - 1) { gap = schedulingConstants.get(0) - schedulingConstants.get(i) + 10000; } else { gap = schedulingConstants.get(i + 1) - schedulingConstants.get(i); } gaps[i] = (double) gap; } // Compute coefficient of variation double gapMean = new Mean().evaluate(gaps); double gapSD = new StandardDeviation().evaluate(gaps); return gapSD / gapMean; } // Counts of HostService instances to create @DataProvider(name = "HostCounts") public Object[][] getHostCounts() { return new Object[][] { { 6 }, { 30 }, { 100 }, }; } // Counts of Xenon ServiceHosts, HostService instances to create @DataProvider(name = "MultiHostHostCounts") public Object[][] getMultiHostHostCounts() { return new Object[][] { { 3, 6 }, { 3, 30 }, { 3, 100 }, }; } }