http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java new file mode 100644 index 0000000..952ee5c --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/GridTestUtils.java @@ -0,0 +1,1438 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.testframework; + +import junit.framework.*; +import org.apache.ignite.*; +import org.apache.ignite.cache.*; +import org.apache.ignite.cache.affinity.consistenthash.*; +import org.apache.ignite.cluster.*; +import org.apache.ignite.internal.*; +import org.apache.ignite.internal.processors.cache.*; +import org.apache.ignite.internal.util.*; +import org.apache.ignite.lang.*; +import org.apache.ignite.client.ssl.*; +import org.apache.ignite.internal.processors.cache.distributed.dht.*; +import org.apache.ignite.internal.processors.cache.distributed.near.*; +import org.apache.ignite.internal.util.future.*; +import org.apache.ignite.internal.util.lang.*; +import org.apache.ignite.internal.util.typedef.*; +import org.apache.ignite.internal.util.typedef.internal.*; +import org.apache.ignite.testframework.config.*; +import org.jetbrains.annotations.*; + +import javax.net.ssl.*; +import java.io.*; +import java.lang.annotation.*; +import java.lang.reflect.*; +import java.net.*; +import java.nio.file.attribute.*; +import java.security.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * Utility class for tests. + */ +@SuppressWarnings({"UnusedCatchParameter"}) +public final class GridTestUtils { + /** Default busy wait sleep interval in milliseconds. */ + public static final long DFLT_BUSYWAIT_SLEEP_INTERVAL = 200; + + /** */ + private static final Map<Class<? extends Test>, String> addrs = new HashMap<>(); + + /** */ + private static final Map<Class<? extends Test>, Integer> mcastPorts = new HashMap<>(); + + /** */ + private static final Map<Class<? extends Test>, Integer> discoPorts = new HashMap<>(); + + /** */ + private static final Map<Class<? extends Test>, Integer> commPorts = new HashMap<>(); + + /** */ + private static int[] addr; + + /** */ + private static final int default_mcast_port = 50000; + + /** */ + private static final int max_mcast_port = 54999; + + /** */ + private static final int default_comm_port = 45000; + + /** */ + private static final int max_comm_port = 49999; + + /** */ + private static final int default_disco_port = 55000; + + /** */ + private static final int max_disco_port = 59999; + + /** */ + private static int mcastPort = default_mcast_port; + + /** */ + private static int discoPort = default_disco_port; + + /** */ + private static int commPort = default_comm_port; + + /** */ + private static final GridBusyLock busyLock = new GridBusyLock(); + + /** + * Ensure singleton. + */ + private GridTestUtils() { + // No-op. + } + + /** + * Checks whether callable throws expected exception or not. + * + * @param log Logger (optional). + * @param call Callable. + * @param cls Exception class. + * @param msg Exception message (optional). If provided exception message + * and this message should be equal. + * @return Thrown throwable. + */ + @Nullable public static Throwable assertThrows(@Nullable IgniteLogger log, Callable<?> call, + Class<? extends Throwable> cls, @Nullable String msg) { + assert call != null; + assert cls != null; + + try { + call.call(); + } + catch (Throwable e) { + if (cls != e.getClass()) { + U.error(log, "Unexpected exception.", e); + + fail("Exception class is not as expected [expected=" + cls + ", actual=" + e.getClass() + ']', e); + } + + if (msg != null && (e.getMessage() == null || !e.getMessage().startsWith(msg))) { + U.error(log, "Unexpected exception message.", e); + + fail("Exception message is not as expected [expected=" + msg + ", actual=" + e.getMessage() + ']', e); + } + + if (log != null) { + if (log.isInfoEnabled()) + log.info("Caught expected exception: " + e.getMessage()); + } + else + X.println("Caught expected exception: " + e.getMessage()); + + return e; + } + + throw new AssertionError("Exception has not been thrown."); + } + + /** + * Checks whether callable throws expected exception or its child or not. + * + * @param log Logger (optional). + * @param call Callable. + * @param cls Exception class. + * @param msg Exception message (optional). If provided exception message + * and this message should be equal. + * @return Thrown throwable. + */ + @Nullable public static Throwable assertThrowsInherited(@Nullable IgniteLogger log, Callable<?> call, + Class<? extends Throwable> cls, @Nullable String msg) { + assert call != null; + assert cls != null; + + try { + call.call(); + } + catch (Throwable e) { + if (!cls.isAssignableFrom(e.getClass())) + fail("Exception class is not as expected [expected=" + cls + ", actual=" + e.getClass() + ']', e); + + if (msg != null && (e.getMessage() == null || !e.getMessage().startsWith(msg))) + fail("Exception message is not as expected [expected=" + msg + ", actual=" + e.getMessage() + ']', e); + + if (log != null) { + if (log.isDebugEnabled()) + log.debug("Caught expected exception: " + e.getMessage()); + } + else + X.println("Caught expected exception: " + e.getMessage()); + + return e; + } + + throw new AssertionError("Exception has not been thrown."); + } + + /** + * Checks whether callable throws exception, which is itself of a specified + * class, or has a cause of the specified class. + * + * @param call Callable. + * @param cls Expected class. + * @return Thrown throwable. + */ + @Nullable public static Throwable assertThrowsWithCause(Callable<?> call, Class<? extends Throwable> cls) { + assert call != null; + assert cls != null; + + try { + call.call(); + } + catch (Throwable e) { + if (!X.hasCause(e, cls)) + fail("Exception is neither of a specified class, nor has a cause of the specified class: " + cls, e); + + return e; + } + + throw new AssertionError("Exception has not been thrown."); + } + + /** + * Throw assertion error with specified error message and initialized cause. + * + * @param msg Error message. + * @param cause Error cause. + * @return Assertion error. + */ + private static AssertionError fail(String msg, @Nullable Throwable cause) { + AssertionError e = new AssertionError(msg); + + if (cause != null) + e.initCause(cause); + + throw e; + } + + /** + * Checks whether object's method call throws expected exception or not. + * + * @param log Logger (optional). + * @param cls Exception class. + * @param msg Exception message (optional). If provided exception message + * and this message should be equal. + * @param obj Object to invoke method for. + * @param mtd Object's method to invoke. + * @param params Method parameters. + * @return Thrown throwable. + */ + @Nullable public static Throwable assertThrows(@Nullable IgniteLogger log, Class<? extends Throwable> cls, + @Nullable String msg, final Object obj, final String mtd, final Object... params) { + return assertThrows(log, new Callable() { + @Override public Object call() throws Exception { + return invoke(obj, mtd, params); + } + }, cls, msg); + } + + /** + * Asserts that each element in iterable has one-to-one correspondence with a + * predicate from list. + * + * @param it Input iterable of elements. + * @param ps Array of predicates (by number of elements in iterable). + */ + @SuppressWarnings("ConstantConditions") + public static <T> void assertOneToOne(Iterable<T> it, IgnitePredicate<T>... ps) { + Collection<IgnitePredicate<T>> ps0 = new ArrayList<>(Arrays.asList(ps)); + Collection<T2<IgnitePredicate<T>, T>> passed = new ArrayList<>(); + + for (T elem : it) { + for (T2<IgnitePredicate<T>, T> p : passed) { + if (p.get1().apply(elem)) + throw new AssertionError("Two elements match one predicate [elem1=" + p.get2() + + ", elem2=" + elem + ", pred=" + p.get1() + ']'); + } + + IgnitePredicate<T> matched = null; + + for (IgnitePredicate<T> p : ps0) { + if (p.apply(elem)) { + if (matched != null) + throw new AssertionError("Element matches more than one predicate [elem=" + elem + + ", pred1=" + p + ", pred2=" + matched + ']'); + + matched = p; + } + } + + if (matched == null) // None matched. + throw new AssertionError("The element does not match [elem=" + elem + + ", numRemainingPreds=" + ps0.size() + ']'); + + ps0.remove(matched); + passed.add(new T2<>(matched, elem)); + } + } + + /** + * Every invocation of this method will never return a + * repeating multicast port for a different test case. + * + * @param cls Class. + * @return Next multicast port. + */ + public static synchronized int getNextMulticastPort(Class<? extends Test> cls) { + Integer portRet = mcastPorts.get(cls); + + if (portRet != null) + return portRet; + + int startPort = mcastPort; + + while (true) { + if (mcastPort >= max_mcast_port) + mcastPort = default_mcast_port; + else + mcastPort++; + + if (startPort == mcastPort) + break; + + portRet = mcastPort; + + MulticastSocket sock = null; + + try { + sock = new MulticastSocket(portRet); + + break; + } + catch (IOException ignored) { + // No-op. + } + finally { + U.closeQuiet(sock); + } + } + + // Cache port to be reused by the same test. + mcastPorts.put(cls, portRet); + + return portRet; + } + + /** + * Every invocation of this method will never return a + * repeating communication port for a different test case. + * + * @param cls Class. + * @return Next communication port. + */ + public static synchronized int getNextCommPort(Class<? extends Test> cls) { + Integer portRet = commPorts.get(cls); + + if (portRet != null) + return portRet; + + if (commPort >= max_comm_port) + commPort = default_comm_port; + else + // Reserve 10 ports per test. + commPort += 10; + + portRet = commPort; + + // Cache port to be reused by the same test. + commPorts.put(cls, portRet); + + return portRet; + } + + /** + * Every invocation of this method will never return a + * repeating discovery port for a different test case. + * + * @param cls Class. + * @return Next discovery port. + */ + public static synchronized int getNextDiscoPort(Class<? extends Test> cls) { + Integer portRet = discoPorts.get(cls); + + if (portRet != null) + return portRet; + + if (discoPort >= max_disco_port) + discoPort = default_disco_port; + else + discoPort += 10; + + portRet = discoPort; + + // Cache port to be reused by the same test. + discoPorts.put(cls, portRet); + + return portRet; + } + + /** + * Every invocation of this method will never return a + * repeating multicast group for a different test case. + * + * @param cls Class. + * @return Next multicast group. + */ + public static synchronized String getNextMulticastGroup(Class<? extends Test> cls) { + String addrStr = addrs.get(cls); + + if (addrStr != null) + return addrStr; + + // Increment address. + if (addr[3] == 255) { + if (addr[2] == 255) + assert false; + else { + addr[2] += 1; + + addr[3] = 1; + } + } + else + addr[3] += 1; + + // Convert address to string. + StringBuilder b = new StringBuilder(15); + + for (int i = 0; i < addr.length; i++) { + b.append(addr[i]); + + if (i < addr.length - 1) + b.append('.'); + } + + addrStr = b.toString(); + + // Cache address to be reused by the same test. + addrs.put(cls, addrStr); + + return addrStr; + } + + /** + * Runs runnable object in specified number of threads. + * + * @param run Target runnable. + * @param threadNum Number of threads. + * @param threadName Thread name. + * @return Execution time in milliseconds. + * @throws Exception Thrown if at least one runnable execution failed. + */ + public static long runMultiThreaded(Runnable run, int threadNum, String threadName) throws Exception { + return runMultiThreaded(makeCallable(run, null), threadNum, threadName); + } + + /** + * Runs runnable object in specified number of threads. + * + * @param run Target runnable. + * @param threadNum Number of threads. + * @param threadName Thread name. + * @return Future for the run. Future returns execution time in milliseconds. + */ + public static IgniteFuture<Long> runMultiThreadedAsync(Runnable run, int threadNum, String threadName) { + return runMultiThreadedAsync(makeCallable(run, null), threadNum, threadName); + } + + /** + * Runs callable object in specified number of threads. + * + * @param call Callable. + * @param threadNum Number of threads. + * @param threadName Thread names. + * @return Execution time in milliseconds. + * @throws Exception If failed. + */ + public static long runMultiThreaded(Callable<?> call, int threadNum, String threadName) throws Exception { + List<Callable<?>> calls = Collections.<Callable<?>>nCopies(threadNum, call); + + return runMultiThreaded(calls, threadName); + } + + /** + * Runs callable object in specified number of threads. + * + * @param call Callable. + * @param threadNum Number of threads. + * @param threadName Thread names. + * @return Future for the run. Future returns execution time in milliseconds. + */ + @SuppressWarnings("ExternalizableWithoutPublicNoArgConstructor") + public static IgniteFuture<Long> runMultiThreadedAsync(Callable<?> call, int threadNum, final String threadName) { + final List<Callable<?>> calls = Collections.<Callable<?>>nCopies(threadNum, call); + final GridTestSafeThreadFactory threadFactory = new GridTestSafeThreadFactory(threadName); + + // Future that supports cancel() operation. + GridFutureAdapter<Long> cancelFut = new GridFutureAdapter<Long>() { + @Override public boolean cancel() { + if (onCancelled()) { + threadFactory.interruptAllThreads(); + + onCancelled(); + + return true; + } + + return false; + } + }; + + // Async execution future (doesn't support cancel()). + IgniteFuture<Long> runFut = runAsync(new Callable<Long>() { + @Override public Long call() throws Exception { + return runMultiThreaded(calls, threadFactory); + } + }); + + // Compound future, that adds cancel() support to execution future. + GridCompoundFuture<Long, Long> compFut = new GridCompoundFuture<>(); + + compFut.addAll(cancelFut, runFut); + compFut.reducer(F.sumLongReducer()); + compFut.markInitialized(); + + cancelFut.onDone(); + + return compFut; + } + + /** + * Runs callable tasks each in separate threads. + * + * @param calls Callable tasks. + * @param threadName Thread name. + * @return Execution time in milliseconds. + * @throws Exception If failed. + */ + public static long runMultiThreaded(Iterable<Callable<?>> calls, String threadName) throws Exception { + return runMultiThreaded(calls, new GridTestSafeThreadFactory(threadName)); + } + + /** + * Runs callable tasks each in separate threads. + * + * @param calls Callable tasks. + * @param threadFactory Thread factory. + * @return Execution time in milliseconds. + * @throws Exception If failed. + */ + public static long runMultiThreaded(Iterable<Callable<?>> calls, GridTestSafeThreadFactory threadFactory) + throws Exception { + if (!busyLock.enterBusy()) + throw new IllegalStateException("Failed to start new threads (test is being stopped)."); + + Collection<Thread> threads = new ArrayList<>(); + long time; + + try { + for (Callable<?> call : calls) + threads.add(threadFactory.newThread(call)); + + time = System.currentTimeMillis(); + + for (Thread t : threads) + t.start(); + } + finally { + busyLock.leaveBusy(); + } + + // Wait threads finish their job. + for (Thread t : threads) + t.join(); + + time = System.currentTimeMillis() - time; + + // Validate errors happens + threadFactory.checkError(); + + return time; + } + + /** + * Runs callable task asyncronously. + * + * @param task Callable. + * @return Future with task result. + */ + @SuppressWarnings("ExternalizableWithoutPublicNoArgConstructor") + public static <T> IgniteFuture<T> runAsync(final Callable<T> task) { + if (!busyLock.enterBusy()) + throw new IllegalStateException("Failed to start new threads (test is being stopped)."); + + try { + final GridTestSafeThreadFactory thrFactory = new GridTestSafeThreadFactory("async-runner"); + + final GridFutureAdapter<T> fut = new GridFutureAdapter<T>() { + @Override public boolean cancel() throws IgniteCheckedException { + super.cancel(); + + thrFactory.interruptAllThreads(); + + onCancelled(); + + return true; + } + }; + + thrFactory.newThread(new Runnable() { + @Override public void run() { + try { + // Execute task. + T res = task.call(); + + fut.onDone(res); + } + catch (Throwable e) { + fut.onDone(e); + } + } + }).start(); + + return fut; + } + finally { + busyLock.leaveBusy(); + } + } + + /** + * Interrupts and waits for termination of all the threads started + * so far by current test. + * + * @param log Logger. + */ + public static void stopThreads(IgniteLogger log) { + busyLock.block(); + + try { + GridTestSafeThreadFactory.stopAllThreads(log); + } + finally { + busyLock.unblock(); + } + } + + /** + * @return GridGain home. + * @throws Exception If failed. + */ + @SuppressWarnings({"ProhibitedExceptionThrown"}) + public static String getGridGainHome() throws Exception { + String ggHome = System.getProperty("GRIDGAIN_HOME"); + + if (ggHome == null) + ggHome = System.getenv("GRIDGAIN_HOME"); + + if (ggHome == null) + throw new Exception("GRIDGAIN_HOME parameter must be set either as system or environment variable."); + + File dir = new File(ggHome); + + if (!dir.exists()) + throw new Exception("Gridgain home does not exist [girdgain-home=" + dir.getAbsolutePath() + ']'); + + if (!dir.isDirectory()) + throw new Exception("Gridgain home is not a directory [gridgain-home=" + dir.getAbsolutePath() + ']'); + + return ggHome; + } + + /** + * @param <T> Type. + * @param cls Class. + * @param annCls Annotation class. + * @return Annotation. + */ + @Nullable public static <T extends Annotation> T getAnnotation(Class<?> cls, Class<T> annCls) { + for (Class<?> cls0 = cls; cls0 != null; cls0 = cls0.getSuperclass()) { + T ann = cls0.getAnnotation(annCls); + + if (ann != null) + return ann; + } + + return null; + } + + /** + * Initializes address. + */ + static { + InetAddress locHost = null; + + try { + locHost = U.getLocalHost(); + } + catch (IOException e) { + assert false : "Unable to get local address. This leads to the same multicast addresses " + + "in the local network."; + } + + if (locHost != null) { + int thirdByte = locHost.getAddress()[3]; + + if (thirdByte < 0) + thirdByte += 256; + + // To get different addresses for different machines. + addr = new int[] {229, thirdByte, 1, 1}; + } + else + addr = new int[] {229, 1, 1, 1}; + } + + /** + * @param path Path. + * @param startFilter Start filter. + * @param endFilter End filter. + * @return List of JARs that corresponds to the filters. + * @throws IOException If failed. + */ + private static Collection<String> getFiles(String path, @Nullable final String startFilter, + @Nullable final String endFilter) throws IOException { + Collection<String> res = new ArrayList<>(); + + File file = new File(path); + + assert file.isDirectory(); + + File[] jars = file.listFiles(new FilenameFilter() { + /** + * @see FilenameFilter#accept(File, String) + */ + @SuppressWarnings({"UnnecessaryJavaDocLink"}) + @Override public boolean accept(File dir, String name) { + // Exclude spring.jar because it tries to load META-INF/spring-handlers.xml from + // all available JARs and create instances of classes from there for example. + // Exclude logging as it is used by spring and casted to Log interface. + // Exclude log4j because of the design - 1 per VM. + if (name.startsWith("spring") || name.startsWith("log4j") || + name.startsWith("commons-logging") || name.startsWith("junit") || + name.startsWith("gridgain-tests")) + return false; + + boolean ret = true; + + if (startFilter != null) + ret = name.startsWith(startFilter); + + if (ret && endFilter != null) + ret = name.endsWith(endFilter); + + return ret; + } + }); + + for (File jar : jars) + res.add(jar.getCanonicalPath()); + + return res; + } + + /** + * Silent stop grid. + * Method doesn't throw any exception. + * + * @param ignite Grid to stop. + * @param log Logger. + */ + @SuppressWarnings({"CatchGenericClass"}) + public static void close(Ignite ignite, IgniteLogger log) { + if (ignite != null) + try { + G.stop(ignite.name(), false); + } + catch (Throwable e) { + U.error(log, "Failed to stop grid: " + ignite.name(), e); + } + } + + /** + * Silent stop grid. + * Method doesn't throw any exception. + * + * @param gridName Grid name. + * @param log Logger. + */ + @SuppressWarnings({"CatchGenericClass"}) + public static void stopGrid(String gridName, IgniteLogger log) { + try { + G.stop(gridName, false); + } + catch (Throwable e) { + U.error(log, "Failed to stop grid: " + gridName, e); + } + } + + /** + * Gets file representing the path passed in. First the check is made if path is absolute. + * If not, then the check is made if path is relative to ${GRIDGAIN_HOME}. If both checks fail, + * then {@code null} is returned, otherwise file representing path is returned. + * <p> + * See {@link #getGridGainHome()} for information on how {@code GRIDGAIN_HOME} is retrieved. + * + * @param path Path to resolve. + * @return Resolved path, or {@code null} if file cannot be resolved. + * @see #getGridGainHome() + */ + @Nullable public static File resolveGridGainPath(String path) { + return resolveGridGainPath(null, path); + } + + /** + * @param ggHome Optional gridgain home path. + * @param path Path to resolve. + * @return Resolved path, or {@code null} if file cannot be resolved. + */ + @Nullable public static File resolveGridGainPath(@Nullable String ggHome, String path) { + File file = resolvePath(ggHome, path); + + return file != null ? file : resolvePath(ggHome, "os/" + path); + } + + /** + * @param ggHome Optional gridgain home path. + * @param path Path to resolve. + * @return Resolved path, or {@code null} if file cannot be resolved. + */ + @Nullable private static File resolvePath(@Nullable String ggHome, String path) { + File file = new File(path).getAbsoluteFile(); + + if (!file.exists()) { + String home = ggHome != null ? ggHome : U.getGridGainHome(); + + if (home == null) + return null; + + file = new File(home, path); + + return file.exists() ? file : null; + } + + return file; + } + + /** + * @param cache Cache. + * @return Cache context. + */ + public static <K, V> GridCacheContext<K, V> cacheContext(GridCacheProjection<K, V> cache) { + return ((GridKernal)cache.gridProjection().ignite()).<K, V>internalCache().context(); + } + + /** + * @param cache Cache. + * @return Near cache. + */ + public static <K, V> GridNearCacheAdapter<K, V> near(GridCacheProjection<K, V> cache) { + return cacheContext(cache).near(); + } + + /** + * @param cache Cache. + * @return DHT cache. + */ + public static <K, V> GridDhtCacheAdapter<K, V> dht(GridCacheProjection<K, V> cache) { + return near(cache).dht(); + } + + /** + * @param cache Cache. + * @return Affinity. + */ + static <K, V> GridCacheConsistentHashAffinityFunction affinity(GridCacheProjection<K, V> cache) { + return (GridCacheConsistentHashAffinityFunction)cache.cache().configuration().getAffinity(); + } + + /** + * @param cacheName Cache name. + * @param backups Number of backups. + * @param log Logger. + * @throws Exception If failed. + */ + @SuppressWarnings("BusyWait") + public static <K, V> void waitTopologyUpdate(@Nullable String cacheName, int backups, IgniteLogger log) + throws Exception { + for (Ignite g : Ignition.allGrids()) { + GridCache<K, V> cache = ((GridEx)g).cachex(cacheName); + + GridDhtPartitionTopology<?, ?> top = dht(cache).topology(); + + while (true) { + boolean wait = false; + + for (int p = 0; p < affinity(cache).partitions(); p++) { + Collection<ClusterNode> nodes = top.nodes(p, -1); + + if (nodes.size() > backups + 1) { + LT.warn(log, null, "Partition map was not updated yet (will wait) [grid=" + g.name() + + ", p=" + p + ", nodes=" + F.nodeIds(nodes) + ']'); + + wait = true; + + break; + } + } + + if (wait) + Thread.sleep(20); + else + break; // While. + } + } + } + + /** + * Convert runnable tasks with callable. + * + * @param run Runnable task to convert into callable one. + * @param res Callable result. + * @param <T> The result type of method <tt>call</tt>, always {@code null}. + * @return Callable task around the specified runnable one. + */ + public static <T> Callable<T> makeCallable(final Runnable run, @Nullable final T res) { + return new Callable<T>() { + @Override public T call() throws Exception { + run.run(); + return res; + } + }; + } + + /** + * Get object field value via reflection. + * + * @param obj Object or class to get field value from. + * @param cls Class. + * @param fieldName Field names to get value for. + * @param <T> Expected field class. + * @return Field value. + * @throws IgniteException In case of error. + */ + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public static <T> T getFieldValue(Object obj, Class cls, String fieldName) throws IgniteException { + assert obj != null; + assert fieldName != null; + + try { + // Resolve inner field. + Field field = cls.getDeclaredField(fieldName); + + synchronized (field) { + // Backup accessible field state. + boolean accessible = field.isAccessible(); + + try { + if (!accessible) + field.setAccessible(true); + + obj = field.get(obj); + } + finally { + // Recover accessible field state. + if (!accessible) + field.setAccessible(false); + } + } + + return (T)obj; + } + catch (NoSuchFieldException | IllegalAccessException e) { + throw new IgniteException("Failed to get object field [obj=" + obj + + ", fieldName=" + fieldName + ']', e); + } + } + + /** + * Get object field value via reflection. + * + * @param obj Object or class to get field value from. + * @param fieldNames Field names to get value for: obj->field1->field2->...->fieldN. + * @param <T> Expected field class. + * @return Field value. + * @throws IgniteException In case of error. + */ + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public static <T> T getFieldValue(Object obj, String... fieldNames) throws IgniteException { + assert obj != null; + assert fieldNames != null; + assert fieldNames.length >= 1; + + try { + for (String fieldName : fieldNames) { + Class<?> cls = obj instanceof Class ? (Class)obj : obj.getClass(); + + try { + // Resolve inner field. + Field field = cls.getDeclaredField(fieldName); + + synchronized (field) { + // Backup accessible field state. + boolean accessible = field.isAccessible(); + + try { + if (!accessible) + field.setAccessible(true); + + obj = field.get(obj); + } + finally { + // Recover accessible field state. + if (!accessible) + field.setAccessible(false); + } + } + } + catch (NoSuchFieldException e) { + // Resolve inner class, if not an inner field. + Class<?> innerCls = getInnerClass(cls, fieldName); + + if (innerCls == null) + throw new IgniteException("Failed to get object field [obj=" + obj + + ", fieldNames=" + Arrays.toString(fieldNames) + ']', e); + + obj = innerCls; + } + } + + return (T)obj; + } + catch (IllegalAccessException e) { + throw new IgniteException("Failed to get object field [obj=" + obj + + ", fieldNames=" + Arrays.toString(fieldNames) + ']', e); + } + } + + /** + * Get inner class by its name from the enclosing class. + * + * @param parentCls Parent class to resolve inner class for. + * @param innerClsName Name of the inner class. + * @return Inner class. + */ + @Nullable public static <T> Class<T> getInnerClass(Class<?> parentCls, String innerClsName) { + for (Class<?> cls : parentCls.getDeclaredClasses()) + if (innerClsName.equals(cls.getSimpleName())) + return (Class<T>)cls; + + return null; + } + + /** + * Set object field value via reflection. + * + * @param obj Object to set field value to. + * @param fieldName Field name to set value for. + * @param val New field value. + * @throws IgniteException In case of error. + */ + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public static void setFieldValue(Object obj, String fieldName, Object val) throws IgniteException { + assert obj != null; + assert fieldName != null; + + try { + Class<?> cls = obj instanceof Class ? (Class)obj : obj.getClass(); + + Field field = cls.getDeclaredField(fieldName); + + synchronized (field) { + // Backup accessible field state. + boolean accessible = field.isAccessible(); + + try { + if (!accessible) + field.setAccessible(true); + + field.set(obj, val); + } + finally { + // Recover accessible field state. + if (!accessible) + field.setAccessible(false); + } + } + } + catch (NoSuchFieldException | IllegalAccessException e) { + throw new IgniteException("Failed to set object field [obj=" + obj + ", field=" + fieldName + ']', e); + } + } + + /** + * Set object field value via reflection. + * + * @param obj Object to set field value to. + * @param cls Class to get field from. + * @param fieldName Field name to set value for. + * @param val New field value. + * @throws IgniteException In case of error. + */ + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + public static void setFieldValue(Object obj, Class cls, String fieldName, Object val) throws IgniteException { + assert obj != null; + assert fieldName != null; + + try { + Field field = cls.getDeclaredField(fieldName); + + synchronized (field) { + // Backup accessible field state. + boolean accessible = field.isAccessible(); + + try { + if (!accessible) + field.setAccessible(true); + + field.set(obj, val); + } + finally { + // Recover accessible field state. + if (!accessible) + field.setAccessible(false); + } + } + } + catch (NoSuchFieldException | IllegalAccessException e) { + throw new IgniteException("Failed to set object field [obj=" + obj + ", field=" + fieldName + ']', e); + } + } + + /** + * Invoke method on an object. + * + * @param obj Object to call method on. + * @param mtd Method to invoke. + * @param params Parameters of the method. + * @return Method invocation result. + * @throws Exception If failed. + */ + @SuppressWarnings("SynchronizationOnLocalVariableOrMethodParameter") + @Nullable public static <T> T invoke(Object obj, String mtd, Object... params) throws Exception { + // We cannot resolve method by parameter classes due to some of parameters can be null. + // Search correct method among all methods collection. + for (Method m : obj.getClass().getDeclaredMethods()) { + // Filter methods by name. + if (!m.getName().equals(mtd)) + continue; + + if (!areCompatible(params, m.getParameterTypes())) + continue; + + try { + synchronized (m) { + // Backup accessible field state. + boolean accessible = m.isAccessible(); + + try { + if (!accessible) + m.setAccessible(true); + + return (T)m.invoke(obj, params); + } + finally { + // Recover accessible field state. + if (!accessible) + m.setAccessible(false); + } + } + } + catch (IllegalAccessException e) { + throw new RuntimeException("Failed to access method" + + " [obj=" + obj + ", mtd=" + mtd + ", params=" + Arrays.toString(params) + ']', e); + } + catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + + if (cause instanceof Error) + throw (Error) cause; + + if (cause instanceof Exception) + throw (Exception) cause; + + throw new RuntimeException("Failed to invoke method)" + + " [obj=" + obj + ", mtd=" + mtd + ", params=" + Arrays.toString(params) + ']', e); + } + } + + throw new RuntimeException("Failed to find method" + + " [obj=" + obj + ", mtd=" + mtd + ", params=" + Arrays.toString(params) + ']'); + } + + /** + * Check objects and corresponding types are compatible. + * + * @param objs Objects array. + * @param types Classes array. + * @return Objects in array can be casted to corresponding types. + */ + private static boolean areCompatible(Object[] objs, Class[] types) { + if (objs.length != types.length) + return false; + + for (int i = 0, size = objs.length; i < size; i++) { + Object o = objs[i]; + + if (o != null && !types[i].isInstance(o)) + return false; + } + + return true; + } + + /** + * Tries few times to perform some assertion. In the worst case + * {@code assertion} closure will be executed {@code retries} + 1 times and + * thread will spend approximately {@code retries} * {@code retryInterval} sleeping. + * + * @param log Log. + * @param retries Number of retries. + * @param retryInterval Interval between retries in milliseconds. + * @param c Closure with assertion. All {@link AssertionError}s thrown + * from this closure will be ignored {@code retries} times. + * @throws org.apache.ignite.IgniteInterruptedException If interrupted. + */ + @SuppressWarnings("ErrorNotRethrown") + public static void retryAssert(@Nullable IgniteLogger log, int retries, long retryInterval, GridAbsClosure c) + throws IgniteInterruptedException { + for (int i = 0; i < retries; i++) { + try { + c.apply(); + + return; + } + catch (AssertionError e) { + U.warn(log, "Check failed (will retry in " + retryInterval + "ms).", e); + + U.sleep(retryInterval); + } + } + + // Apply the last time without guarding try. + c.apply(); + } + + /** + * Reads entire file into byte array. + * + * @param file File to read. + * @return Content of file in byte array. + * @throws IOException If failed. + */ + public static byte[] readFile(File file) throws IOException { + assert file.exists(); + assert file.length() < Integer.MAX_VALUE; + + byte[] bytes = new byte[(int) file.length()]; + + try (FileInputStream fis = new FileInputStream(file)) { + int readBytesCnt = fis.read(bytes); + assert readBytesCnt == bytes.length; + } + + return bytes; + } + + /** + * Sleeps and increments an integer. + * <p> + * Allows for loops like the following: + * <pre>{@code + * for (int i = 0; i < 20 && !condition; i = sleepAndIncrement(200, i)) { + * ... + * } + * }</pre> + * for busy-waiting limited number of iterations. + * + * @param sleepDur Sleep duration in milliseconds. + * @param i Integer to increment. + * @return Incremented value. + * @throws org.apache.ignite.IgniteInterruptedException If sleep was interrupted. + */ + public static int sleepAndIncrement(int sleepDur, int i) throws IgniteInterruptedException { + U.sleep(sleepDur); + + return i + 1; + } + + /** + * Waits for condition, polling in busy wait loop. + * + * @param cond Condition to wait for. + * @param timeout Max time to wait in milliseconds. + * @return {@code true} if condition was achieved, {@code false} otherwise. + * @throws org.apache.ignite.IgniteInterruptedException If interrupted. + */ + public static boolean waitForCondition(GridAbsPredicate cond, long timeout) throws IgniteInterruptedException { + long curTime = U.currentTimeMillis(); + long endTime = curTime + timeout; + + if (endTime < 0) + endTime = Long.MAX_VALUE; + + while (curTime < endTime) { + if (cond.apply()) + return true; + + U.sleep(DFLT_BUSYWAIT_SLEEP_INTERVAL); + + curTime = U.currentTimeMillis(); + } + + return false; + } + + /** + * Creates an SSL context from test key store with disabled trust manager. + * + * @return Initialized context. + * @throws GeneralSecurityException In case if context could not be initialized. + * @throws IOException If keystore cannot be accessed. + */ + public static SSLContext sslContext() throws GeneralSecurityException, IOException { + SSLContext ctx = SSLContext.getInstance("TLS"); + + char[] storePass = GridTestProperties.getProperty("ssl.keystore.password").toCharArray(); + + KeyManagerFactory keyMgrFactory = KeyManagerFactory.getInstance("SunX509"); + + KeyStore keyStore = KeyStore.getInstance("JKS"); + + keyStore.load(new FileInputStream(U.resolveGridGainPath(GridTestProperties.getProperty("ssl.keystore.path"))), + storePass); + + keyMgrFactory.init(keyStore, storePass); + + ctx.init(keyMgrFactory.getKeyManagers(), + new TrustManager[]{GridSslBasicContextFactory.getDisabledTrustManager()}, null); + + return ctx; + } + + /** + * Creates test-purposed SSL context factory from test key store with disabled trust manager. + * + * @return SSL context factory used in test. + */ + public static GridSslContextFactory sslContextFactory() { + GridSslBasicContextFactory factory = new GridSslBasicContextFactory(); + + factory.setKeyStoreFilePath( + U.resolveGridGainPath(GridTestProperties.getProperty("ssl.keystore.path")).getAbsolutePath()); + factory.setKeyStorePassword(GridTestProperties.getProperty("ssl.keystore.password").toCharArray()); + + factory.setTrustManagers(GridSslBasicContextFactory.getDisabledTrustManager()); + + return factory; + } + + /** + * @param o1 Object 1. + * @param o2 Object 2. + * @return Equals or not. + */ + public static boolean deepEquals(@Nullable Object o1, @Nullable Object o2) { + if (o1 == o2) + return true; + else if (o1 == null || o2 == null) + return false; + else if (o1.getClass() != o2.getClass()) + return false; + else { + Class<?> cls = o1.getClass(); + + assert o2.getClass() == cls; + + for (Field f : cls.getDeclaredFields()) { + f.setAccessible(true); + + Object v1; + Object v2; + + try { + v1 = f.get(o1); + v2 = f.get(o2); + } + catch (IllegalAccessException e) { + throw new AssertionError(e); + } + + if (!Objects.deepEquals(v1, v2)) + return false; + } + + return true; + } + } + + /** + * Converts integer permission mode into set of {@link PosixFilePermission}. + * + * @param mode File mode. + * @return Set of {@link PosixFilePermission}. + */ + public static Set<PosixFilePermission> modeToPermissionSet(int mode) { + Set<PosixFilePermission> res = EnumSet.noneOf(PosixFilePermission.class); + + if ((mode & 0400) > 0) + res.add(PosixFilePermission.OWNER_READ); + + if ((mode & 0200) > 0) + res.add(PosixFilePermission.OWNER_WRITE); + + if ((mode & 0100) > 0) + res.add(PosixFilePermission.OWNER_EXECUTE); + + if ((mode & 040) > 0) + res.add(PosixFilePermission.GROUP_READ); + + if ((mode & 020) > 0) + res.add(PosixFilePermission.GROUP_WRITE); + + if ((mode & 010) > 0) + res.add(PosixFilePermission.GROUP_EXECUTE); + + if ((mode & 04) > 0) + res.add(PosixFilePermission.OTHERS_READ); + + if ((mode & 02) > 0) + res.add(PosixFilePermission.OTHERS_WRITE); + + if ((mode & 01) > 0) + res.add(PosixFilePermission.OTHERS_EXECUTE); + + return res; + } + + /** + * @return Path to apache ignite. + */ + public static String apacheIgniteTestPath() { + return System.getProperty("IGNITE_TEST_PATH", U.getGridGainHome() + "/target/ignite"); + } +}
http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java b/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java new file mode 100644 index 0000000..67d8a7b --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/config/GridTestProperties.java @@ -0,0 +1,304 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.testframework.config; + +import org.apache.log4j.xml.*; +import org.apache.ignite.testframework.*; +import org.jetbrains.annotations.*; + +import java.io.*; +import java.util.*; +import java.util.Map.*; +import java.util.regex.*; + +/** + * Loads test properties from {@code config} folder under tests. + * The property structure is as follows: + * <ul> + * <li> + * Default properties and log4j.xml configuration is loaded directly from + * {@code ${GRIDGAIN_HOME}/modules/tests/config} folder. Default properties can be + * accessed via {@link #getDefaultProperties()} and {@link #getDefaultProperty(String)} methods. + * </li> + * <li> + * User is able to override any default property and log4j configuration in + * {@code ${GRIDGAIN_HOME}/modules/tests/config/${username}} folder, where {@code username} + * is the system user name. User properties can be accessed via {@link #getProperties()} and + * {@link #getProperties(String)} methods. + * </li> + * <li> + * Any test may utilize its own sub-folder. To access configuration specific to some sub-folder + * use {@link #getProperties(String)} and {@link #getProperty(String, String)} methods. + * </li> + * </ul> + */ +public final class GridTestProperties { + /** */ + public static final String TESTS_PROP_FILE = "tests.properties"; + + /** */ + public static final String TESTS_CFG_PATH = "modules/core/src/test/config"; + + /** */ + private static final Pattern PROP_REGEX = Pattern.compile("[@$]\\{[^@${}]+\\}"); + + /** */ + private static final Map<String, String> dfltProps; + + /** */ + private static final Map<String, Map<String, String>> pathProps = new HashMap<>(); + + /** */ + static { + // Initialize GRIDGAIN_HOME system property. + String ggHome = System.getProperty("GRIDGAIN_HOME"); + + if (ggHome == null || ggHome.isEmpty()) { + ggHome = System.getenv("GRIDGAIN_HOME"); + + if (ggHome != null && !ggHome.isEmpty()) + System.setProperty("GRIDGAIN_HOME", ggHome); + } + + // Load default properties. + File cfgFile = getTestConfigurationFile(null, TESTS_PROP_FILE); + + assert cfgFile.exists(); + assert !cfgFile.isDirectory(); + + dfltProps = Collections.unmodifiableMap(loadFromFile(new HashMap<String, String>(), cfgFile)); + + if ("false".equals(System.getProperty("GRIDGAIN_TEST_PROP_DISABLE_LOG4J", "false"))) { + String user = System.getProperty("user.name"); + + assert user != null; + + // Configure log4j logger. + configureLog4j(user); + } + } + + /** + * Ensure singleton. + */ + private GridTestProperties() { + // No-op. + } + + /** + * @param user User name. + */ + private static void configureLog4j(String user) { + String cfgFile = System.getProperty("GG_TEST_PROP_LOG4J_FILE"); + + if (cfgFile == null) + cfgFile = "log4j-test.xml"; + + File log4jFile = getTestConfigurationFile(user, cfgFile); + + if (log4jFile == null) + log4jFile = getTestConfigurationFile(null, cfgFile); + + DOMConfigurator.configure(log4jFile.getAbsolutePath()); + + System.out.println("Configured log4j from: " + log4jFile); + } + + /** */ + public static void init() { + // No-op. + } + + /** + * @return Default properties. + */ + public static synchronized Map<String, String> getDefaultProperties() { + return dfltProps; + } + + /** + * @param name Default property name. + * @return Default property value. + */ + public static synchronized String getDefaultProperty(String name) { + return dfltProps.get(name); + } + + /** + * @return Properties. + */ + public static synchronized Map<String, String> getProperties() { + String user = System.getProperty("user.name"); + + assert user != null; + + return getProperties(user); + } + + /** + * @param name Property name. + * @return Property value. + */ + public static synchronized String getProperty(String name) { + return getProperties().get(name); + } + + /** + * @param dir Directory path. + * @return Properties. + */ + public static synchronized Map<String, String> getProperties(String dir) { + Map<String, String> props = pathProps.get(dir); + + if (props == null) { + props = new HashMap<>(); + + // Load default properties. + props.putAll(dfltProps); + + // Load properties from specified folder + // potentially overriding defaults. + loadProperties(props, dir); + + // Seal it. + props = Collections.unmodifiableMap(props); + + pathProps.put(dir, props); + } + + return props; + } + + /** + * @param name Property name. + * @param dir Directory path. + * @return Property value. + */ + public static synchronized String getProperty(String name, String dir) { + return getProperties(dir).get(name); + } + + /** + * Substitutes environmental or system properties in the given string. + * + * @param str String to make substitution in. + * @return Substituted string. + */ + private static String substituteProperties(String str) { + str = str.trim(); + + Matcher matcher = PROP_REGEX.matcher(str); + + StringBuffer buf = new StringBuffer(); + + while (matcher.find()) { + String match = matcher.group(); + + if (match.length() >= 4) { + String key = match.substring(2, match.length() - 1); + + String val = System.getenv(key); + + if (val == null) + val = System.getProperty(key); + + if (val != null) { + // Take care of back slashes. + match = val.replaceAll("\\\\", "\\\\\\\\"); + } + else if (match.startsWith("$")) + match = match.replace("$", "\\$"); + } + + matcher.appendReplacement(buf, match); + } + + matcher.appendTail(buf); + + return buf.toString(); + } + + /** + * @param props Initial properties. + * @param dir Directory path. + * @return Loaded properties. + */ + private static Map<String, String> loadProperties(Map<String, String> props, String dir) { + File cfg = getTestConfigurationFile(dir, TESTS_PROP_FILE); + + if (cfg != null) + loadFromFile(props, cfg); + + return props; + } + + /** + * @param user User name. + * @param fileName File name. + * @return Configuration file for given user. + */ + @Nullable private static File getTestConfigurationFile(@Nullable String user, String fileName) { + String path = TESTS_CFG_PATH; + + if (user != null) + path += File.separatorChar + user; + + path += File.separatorChar + fileName; + + File file = GridTestUtils.resolveGridGainPath(path); + + if (file != null && file.exists()) { + assert !file.isDirectory(); + + return file; + } + + return null; + } + + /** + * @param props Initial properties. + * @param file Property file. + * @return Loaded properties. + */ + private static Map<String, String> loadFromFile(Map<String, String> props, File file) { + try { + + try (InputStream in = new FileInputStream(file)) { + Properties fileProps = new Properties(); + + fileProps.load(in); + + for (Entry<Object, Object> prop : fileProps.entrySet()) { + props.put((String) prop.getKey(), (String) prop.getValue()); + } + + for (Entry<String, String> prop : props.entrySet()) { + prop.setValue(substituteProperties(prop.getValue())); + } + } + } + catch (IOException e) { + e.printStackTrace(); + + assert false : "Failed to load test configuration properties: " + file; + } + + return props; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html b/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html new file mode 100644 index 0000000..1f85ff2 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/config/package.html @@ -0,0 +1,23 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + --> +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<body> + <!-- Package description. --> + Contains internal tests or test related classes and interfaces. +</body> +</html> http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java b/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java new file mode 100644 index 0000000..d39282e --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/http/GridEmbeddedHttpServer.java @@ -0,0 +1,189 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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.apache.ignite.testframework.http; + +import com.sun.net.httpserver.*; +import org.apache.ignite.testframework.*; +import org.jetbrains.annotations.*; + +import java.io.*; +import java.net.*; + +/** + * Embedded HTTP/HTTPS server implementation aimed to simplify tests development + * that need to make HTTP(s) interactions. + * <p> + * NOTE: this implementation is NOT thread-safe. + */ +public class GridEmbeddedHttpServer { + /** Default hostname to bind the server to. */ + private static final String HOSTNAME_TO_BIND_SRV = "localhost"; + + /** Simple Oracle HTTP server used as main workhorse. */ + private HttpServer httpSrv; + + /** Store exact protocol (HTTP or HTTPS) which we are running at. */ + private String proto; + + /** + * Private constructor to promote server creation and initialization in <i>Builder pattern</i> style. + */ + private GridEmbeddedHttpServer() { + // No-op + } + + /** + * The class represents a server handler triggered on incoming request. + * <p>The handler checks that a request is a HTTP GET and that url path is the expected one. + * If all checks are passed it writes pre-configured file content to the HTTP response body. + * </p> + */ + private static class FileDownloadingHandler implements HttpHandler { + /** URL path. */ + private final String urlPath; + + /** File to be downloaded. */ + private final File downloadFile; + + /** + * Creates and configures FileDownloadingHandler. + * + * @param urlPath Url path on which a future GET request is going to be executed. + * @param downloadFile File to be written into the HTTP response. + */ + FileDownloadingHandler(String urlPath, File downloadFile) { + this.urlPath = urlPath; + this.downloadFile = downloadFile; + } + + /** + * Handles HTTP requests: checks that a request is a HTTP GET and that url path is the expected one. + * If all checks are passed it writes pre-configured file content to the HTTP response body. + * + * @param exchange Wrapper above the HTTP request and response. + */ + @Override public void handle(HttpExchange exchange) throws IOException { + assert "GET".equalsIgnoreCase(exchange.getRequestMethod()); + assert urlPath == null || urlPath.equals(exchange.getRequestURI().toString()); + + exchange.getResponseHeaders().set("Content-Type", "application/octet-stream"); + exchange.sendResponseHeaders(200, 0); + + try (OutputStream resBody = exchange.getResponseBody()) { + resBody.write(GridTestUtils.readFile(downloadFile)); + } + } + } + + /** + * Creates and starts embedded HTTP server. + * + * @return Started HTTP server instance. + */ + public static GridEmbeddedHttpServer startHttpServer() throws Exception { + return createAndStart(false); + } + + /** + * Creates and starts embedded HTTPS server. + * + * @return Started HTTPS server instance. + */ + public static GridEmbeddedHttpServer startHttpsServer() throws Exception { + return createAndStart(true); + } + + /** + * Configures server with suitable for testing parameters. + * + * @param urlPath Url path on which a future GET request is going to be executed. + * If urlPath is null then no assertions against the requesting url will be done. + * @param fileToBeDownloaded File to be written into the HTTP response. + * @return Configured HTTP(s) server. + */ + public GridEmbeddedHttpServer withFileDownloadingHandler(@Nullable String urlPath, File fileToBeDownloaded) { + assert fileToBeDownloaded.exists(); + + httpSrv.createContext("/", new FileDownloadingHandler(urlPath, fileToBeDownloaded)); + + return this; + } + + /** + * Stops server by closing the listening socket and disallowing any new exchanges + * from being processed. + * + * @param delay - the maximum time in seconds to wait until exchanges have finished. + */ + public void stop(int delay) { + httpSrv.stop(delay); + } + + /** + * Returns base server url in the form <i>protocol://serverHostName:serverPort</i>. + * + * @return Base server url. + */ + public String getBaseUrl() { + return proto + "://" + httpSrv.getAddress().getHostName() + ":" + httpSrv.getAddress().getPort(); + } + + /** + * Internal method which creates and starts the server. + * + * @param httpsMode True if the server to be started is HTTPS, false otherwise. + * @return Started server. + */ + private static GridEmbeddedHttpServer createAndStart(boolean httpsMode) throws Exception { + HttpServer httpSrv; + InetSocketAddress addrToBind = new InetSocketAddress(HOSTNAME_TO_BIND_SRV, getAvailablePort()); + + if (httpsMode) { + HttpsServer httpsSrv = HttpsServer.create(addrToBind, 0); + + httpsSrv.setHttpsConfigurator(new HttpsConfigurator(GridTestUtils.sslContext())); + + httpSrv = httpsSrv; + } + else + httpSrv = HttpServer.create(addrToBind, 0); + + GridEmbeddedHttpServer embeddedHttpSrv = new GridEmbeddedHttpServer(); + + embeddedHttpSrv.proto = httpsMode ? "https" : "http"; + embeddedHttpSrv.httpSrv = httpSrv; + embeddedHttpSrv.httpSrv.start(); + + return embeddedHttpSrv; + } + + /** + * Returns a port number which was available for the moment of the method call. + * + * @return Available port number. + */ + private static int getAvailablePort() throws IOException { + int httpSrvPort; + + try (ServerSocket s = new ServerSocket(0)) { + httpSrvPort = s.getLocalPort(); + } + + return httpSrvPort; + } +} http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/bd28003b/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html b/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html new file mode 100644 index 0000000..b75dcc2 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testframework/http/package.html @@ -0,0 +1,23 @@ +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + --> +<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> +<html> +<body> + <!-- Package description. --> + Contains implementation of embedded HTTP/HTTPS server to use in internal tests only. +</body> +</html>