IGNITE-3670 IGFS: Improved symlink handling for delete operation and added more tests. This closes #975.
Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/b5757642 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/b5757642 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/b5757642 Branch: refs/heads/master Commit: b5757642e135908d9baa027a605035dd0d4acfc9 Parents: 92f18bf Author: tledkov-gridgain <[email protected]> Authored: Fri Aug 26 15:47:02 2016 +0300 Committer: vozerov-gridgain <[email protected]> Committed: Fri Aug 26 15:47:02 2016 +0300 ---------------------------------------------------------------------- .../local/LocalIgfsSecondaryFileSystem.java | 38 +- .../igfs/IgfsAbstractBaseSelfTest.java | 1067 ++++++++++++++++++ .../processors/igfs/IgfsAbstractSelfTest.java | 1012 +---------------- ...SecondaryFileSystemDualAbstractSelfTest.java | 143 +++ 4 files changed, 1239 insertions(+), 1021 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/b5757642/modules/core/src/main/java/org/apache/ignite/igfs/secondary/local/LocalIgfsSecondaryFileSystem.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/igfs/secondary/local/LocalIgfsSecondaryFileSystem.java b/modules/core/src/main/java/org/apache/ignite/igfs/secondary/local/LocalIgfsSecondaryFileSystem.java index 3d3a350..519f472 100644 --- a/modules/core/src/main/java/org/apache/ignite/igfs/secondary/local/LocalIgfsSecondaryFileSystem.java +++ b/modules/core/src/main/java/org/apache/ignite/igfs/secondary/local/LocalIgfsSecondaryFileSystem.java @@ -41,6 +41,8 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.attribute.BasicFileAttributes; import java.util.Collection; import java.util.Collections; import java.util.Map; @@ -108,35 +110,43 @@ public class LocalIgfsSecondaryFileSystem implements IgfsSecondaryFileSystem, Li @Override public boolean delete(IgfsPath path, boolean recursive) { File f = fileForPath(path); - if (!recursive || !f.isDirectory()) + if (!recursive) return f.delete(); else - return deleteDirectory(f); + return deleteRecursive(f); } /** * Delete directory recursively. * - * @param dir Directory. + * @param f Directory. * @return {@code true} if successful. */ - private boolean deleteDirectory(File dir) { - File[] entries = dir.listFiles(); + private boolean deleteRecursive(File f) { + BasicFileAttributes attrs; + + try { + attrs = Files.readAttributes(f.toPath(), BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + } + catch (IOException ignore) { + return false; + } + + if (!attrs.isDirectory() || attrs.isSymbolicLink()) + return f.delete(); + + File[] entries = f.listFiles(); if (entries != null) { for (File entry : entries) { - if (entry.isDirectory()) - deleteDirectory(entry); - else if (entry.isFile()) { - if (!entry.delete()) - return false; - } - else - throw new UnsupportedOperationException("Symlink deletion is not yet supported: " + entry); + boolean res = deleteRecursive(entry); + + if (!res) + return false; } } - return dir.delete(); + return f.delete(); } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/b5757642/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractBaseSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractBaseSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractBaseSelfTest.java new file mode 100644 index 0000000..9575bd0 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractBaseSelfTest.java @@ -0,0 +1,1067 @@ +/* + * 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.internal.processors.igfs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteFileSystem; +import org.apache.ignite.Ignition; +import org.apache.ignite.cache.CacheMemoryMode; +import org.apache.ignite.cache.CacheWriteSynchronizationMode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.FileSystemConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.igfs.IgfsGroupDataBlocksKeyMapper; +import org.apache.ignite.igfs.IgfsInputStream; +import org.apache.ignite.igfs.IgfsIpcEndpointConfiguration; +import org.apache.ignite.igfs.IgfsIpcEndpointType; +import org.apache.ignite.igfs.IgfsMode; +import org.apache.ignite.igfs.IgfsOutputStream; +import org.apache.ignite.igfs.IgfsPath; +import org.apache.ignite.igfs.secondary.IgfsSecondaryFileSystem; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.IgniteKernal; +import org.apache.ignite.internal.processors.cache.GridCacheAdapter; +import org.apache.ignite.internal.processors.cache.GridCacheEntryEx; +import org.apache.ignite.internal.util.future.GridFutureAdapter; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.X; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgniteUuid; +import org.apache.ignite.marshaller.optimized.OptimizedMarshaller; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; +import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; +import static org.apache.ignite.cache.CacheMemoryMode.ONHEAP_TIERED; +import static org.apache.ignite.cache.CacheMode.PARTITIONED; +import static org.apache.ignite.cache.CacheMode.REPLICATED; +import static org.apache.ignite.igfs.IgfsMode.PRIMARY; +import static org.apache.ignite.igfs.IgfsMode.PROXY; + +/** + * Test fo regular igfs operations. + */ +@SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ConstantConditions"}) +public abstract class IgfsAbstractBaseSelfTest extends IgfsCommonAbstractTest { + /** IGFS block size. */ + protected static final int IGFS_BLOCK_SIZE = 512 * 1024; + + /** Default block size (32Mb). */ + protected static final long BLOCK_SIZE = 32 * 1024 * 1024; + + /** Default repeat count. */ + protected static final int REPEAT_CNT = 5; // Diagnostic: up to 500; Regression: 5 + + /** Concurrent operations count. */ + protected static final int OPS_CNT = 16; + + /** Renames count. */ + protected static final int RENAME_CNT = OPS_CNT; + + /** Deletes count. */ + protected static final int DELETE_CNT = OPS_CNT; + + /** Updates count. */ + protected static final int UPDATE_CNT = OPS_CNT; + + /** Mkdirs count. */ + protected static final int MKDIRS_CNT = OPS_CNT; + + /** Create count. */ + protected static final int CREATE_CNT = OPS_CNT; + + /** Time to wait until the caches get empty after format. */ + private static final long CACHE_EMPTY_TIMEOUT = 30_000L; + + /** Seed to generate random numbers. */ + protected static final long SEED = System.currentTimeMillis(); + + /** Amount of blocks to prefetch. */ + protected static final int PREFETCH_BLOCKS = 1; + + /** Amount of sequential block reads before prefetch is triggered. */ + protected static final int SEQ_READS_BEFORE_PREFETCH = 2; + + /** Primary file system REST endpoint configuration map. */ + protected static final IgfsIpcEndpointConfiguration PRIMARY_REST_CFG; + + /** Secondary file system REST endpoint configuration map. */ + protected static final IgfsIpcEndpointConfiguration SECONDARY_REST_CFG; + + /** Directory. */ + protected static final IgfsPath DIR = new IgfsPath("/dir"); + + /** Sub-directory. */ + protected static final IgfsPath SUBDIR = new IgfsPath(DIR, "subdir"); + + /** Another sub-directory in the same directory. */ + protected static final IgfsPath SUBDIR2 = new IgfsPath(DIR, "subdir2"); + + /** Sub-directory of the sub-directory. */ + protected static final IgfsPath SUBSUBDIR = new IgfsPath(SUBDIR, "subsubdir"); + + /** File. */ + protected static final IgfsPath FILE = new IgfsPath(SUBDIR, "file"); + + /** Another file in the same directory. */ + protected static final IgfsPath FILE2 = new IgfsPath(SUBDIR, "file2"); + + /** Other directory. */ + protected static final IgfsPath DIR_NEW = new IgfsPath("/dirNew"); + + /** Other subdirectory. */ + protected static final IgfsPath SUBDIR_NEW = new IgfsPath(DIR_NEW, "subdirNew"); + + /** Other sub-directory of the sub-directory. */ + protected static final IgfsPath SUBSUBDIR_NEW = new IgfsPath(SUBDIR_NEW, "subsubdirNew"); + + /** Other file. */ + protected static final IgfsPath FILE_NEW = new IgfsPath(SUBDIR_NEW, "fileNew"); + + /** Default data chunk (128 bytes). */ + protected static final byte[] chunk = createChunk(128); + + /** Primary IGFS. */ + protected static IgfsImpl igfs; + + /** Secondary IGFS */ + protected static IgfsSecondaryFileSystem igfsSecondaryFileSystem; + + /** Secondary file system lower layer "backdoor" wrapped in UniversalFileSystemAdapter: */ + protected static IgfsSecondaryFileSystemTestAdapter igfsSecondary; + + /** IGFS mode. */ + protected final IgfsMode mode; + + /** Dual mode flag. */ + protected final boolean dual; + + /** Memory mode. */ + protected final CacheMemoryMode memoryMode; + + /** IP finder for primary topology. */ + protected final TcpDiscoveryVmIpFinder primaryIpFinder = new TcpDiscoveryVmIpFinder(true); + + /** IP finder for secondary topology. */ + protected final TcpDiscoveryVmIpFinder secondaryIpFinder = new TcpDiscoveryVmIpFinder(true); + + /** Ignite nodes of cluster, excluding the secondary file system node, if any. */ + protected Ignite[] nodes; + + static { + PRIMARY_REST_CFG = new IgfsIpcEndpointConfiguration(); + + PRIMARY_REST_CFG.setType(IgfsIpcEndpointType.TCP); + PRIMARY_REST_CFG.setPort(10500); + + SECONDARY_REST_CFG = new IgfsIpcEndpointConfiguration(); + + SECONDARY_REST_CFG.setType(IgfsIpcEndpointType.TCP); + SECONDARY_REST_CFG.setPort(11500); + } + + /** + * Constructor. + * + * @param mode IGFS mode. + */ + protected IgfsAbstractBaseSelfTest(IgfsMode mode) { + this(mode, ONHEAP_TIERED); + } + + /** + * Constructor. + * + * @param mode IGFS mode. + * @param memoryMode Memory mode. + */ + protected IgfsAbstractBaseSelfTest(IgfsMode mode, CacheMemoryMode memoryMode) { + assert mode != null && mode != PROXY; + + this.mode = mode; + this.memoryMode = memoryMode; + + dual = mode != PRIMARY; + } + + /** + * @return Relaxed consistency flag. + */ + protected boolean relaxedConsistency() { + return false; + } + + /** + * @return Relaxed consistency flag. + */ + protected boolean initializeDefaultPathModes() { + return false; + } + + /** + * @return Client flag. + */ + protected boolean client() { + return false; + } + + /** + * @return Use optimized marshaller flag. + */ + protected boolean useOptimizedMarshaller() { + return false; + } + + /** + * @return Whether append is supported. + */ + protected boolean appendSupported() { + return true; + } + + /** + * @return Whether permissions are supported. + */ + protected boolean permissionsSupported() { + return true; + } + + /** + * @return Whether properties are supported. + */ + protected boolean propertiesSupported() { + return true; + } + + /** + * @return Whether times are supported. + */ + protected boolean timesSupported() { + return true; + } + + /** + * @return Amount of nodes to start. + */ + protected int nodeCount() { + return 1; + } + + /** + * Data chunk. + * + * @param len Length. + * @return Data chunk. + */ + static byte[] createChunk(int len) { + byte[] chunk = new byte[len]; + + for (int i = 0; i < chunk.length; i++) + chunk[i] = (byte)i; + + return chunk; + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + igfsSecondaryFileSystem = createSecondaryFileSystemStack(); + + nodes = new Ignite[nodeCount()]; + + for (int i = 0; i < nodes.length; i++) { + String nodeName = i == 0 ? "ignite" : "ignite" + i; + + nodes[i] = startGridWithIgfs(nodeName, "igfs", mode, igfsSecondaryFileSystem, PRIMARY_REST_CFG, + primaryIpFinder); + } + + igfs = (IgfsImpl) nodes[0].fileSystem("igfs"); + + if (client()) { + // Start client. + Ignition.setClientMode(true); + + try { + Ignite ignite = startGridWithIgfs("ignite-client", "igfs", mode, igfsSecondaryFileSystem, + PRIMARY_REST_CFG, primaryIpFinder); + + igfs = (IgfsImpl) ignite.fileSystem("igfs"); + } + finally { + Ignition.setClientMode(false); + } + } + } + + /** + * Creates secondary file system stack. + * + * @return The secondary file system. + * @throws Exception On error. + */ + protected IgfsSecondaryFileSystem createSecondaryFileSystemStack() throws Exception { + Ignite igniteSecondary = startGridWithIgfs("ignite-secondary", "igfs-secondary", PRIMARY, null, + SECONDARY_REST_CFG, secondaryIpFinder); + + IgfsEx secondaryIgfsImpl = (IgfsEx) igniteSecondary.fileSystem("igfs-secondary"); + + igfsSecondary = new DefaultIgfsSecondaryFileSystemTestAdapter(secondaryIgfsImpl); + + return secondaryIgfsImpl.asSecondary(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + clear(igfs, igfsSecondary); + + assert igfs.listFiles(new IgfsPath("/")).isEmpty(); + } + + /** {@inheritDoc} */ + @Override protected void afterTestsStopped() throws Exception { + G.stopAll(true); + } + + /** + * Start grid with IGFS. + * + * @param gridName Grid name. + * @param igfsName IGFS name + * @param mode IGFS mode. + * @param secondaryFs Secondary file system (optional). + * @param restCfg Rest configuration string (optional). + * @param ipFinder IP finder. + * @return Started grid instance. + * @throws Exception If failed. + */ + @SuppressWarnings("unchecked") + protected Ignite startGridWithIgfs(String gridName, String igfsName, IgfsMode mode, + @Nullable IgfsSecondaryFileSystem secondaryFs, @Nullable IgfsIpcEndpointConfiguration restCfg, + TcpDiscoveryIpFinder ipFinder) throws Exception { + FileSystemConfiguration igfsCfg = new FileSystemConfiguration(); + + igfsCfg.setDataCacheName("dataCache"); + igfsCfg.setMetaCacheName("metaCache"); + igfsCfg.setName(igfsName); + igfsCfg.setBlockSize(IGFS_BLOCK_SIZE); + igfsCfg.setDefaultMode(mode); + igfsCfg.setIpcEndpointConfiguration(restCfg); + igfsCfg.setSecondaryFileSystem(secondaryFs); + igfsCfg.setPrefetchBlocks(PREFETCH_BLOCKS); + igfsCfg.setSequentialReadsBeforePrefetch(SEQ_READS_BEFORE_PREFETCH); + igfsCfg.setRelaxedConsistency(relaxedConsistency()); + + igfsCfg.setInitializeDefaultPathModes(initializeDefaultPathModes()); + + CacheConfiguration dataCacheCfg = defaultCacheConfiguration(); + + dataCacheCfg.setName("dataCache"); + dataCacheCfg.setNearConfiguration(null); + dataCacheCfg.setCacheMode(PARTITIONED); + dataCacheCfg.setNearConfiguration(null); + dataCacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); + dataCacheCfg.setAffinityMapper(new IgfsGroupDataBlocksKeyMapper(2)); + dataCacheCfg.setBackups(0); + dataCacheCfg.setAtomicityMode(TRANSACTIONAL); + dataCacheCfg.setMemoryMode(memoryMode); + dataCacheCfg.setOffHeapMaxMemory(0); + + CacheConfiguration metaCacheCfg = defaultCacheConfiguration(); + + metaCacheCfg.setName("metaCache"); + metaCacheCfg.setNearConfiguration(null); + metaCacheCfg.setCacheMode(REPLICATED); + metaCacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); + metaCacheCfg.setAtomicityMode(TRANSACTIONAL); + + IgniteConfiguration cfg = new IgniteConfiguration(); + + if (useOptimizedMarshaller()) + cfg.setMarshaller(new OptimizedMarshaller()); + + cfg.setGridName(gridName); + + TcpDiscoverySpi discoSpi = new TcpDiscoverySpi(); + + discoSpi.setIpFinder(ipFinder); + + prepareCacheConfigurations(dataCacheCfg, metaCacheCfg); + + cfg.setDiscoverySpi(discoSpi); + cfg.setCacheConfiguration(dataCacheCfg, metaCacheCfg); + cfg.setFileSystemConfiguration(igfsCfg); + + cfg.setLocalHost("127.0.0.1"); + cfg.setConnectorConfiguration(null); + + return G.start(cfg); + } + + /** + * Prepare cache configuration. + * + * @param dataCacheCfg Data cache configuration. + * @param metaCacheCfg Meta cache configuration. + */ + protected void prepareCacheConfigurations(CacheConfiguration dataCacheCfg, CacheConfiguration metaCacheCfg) { + // Noop + } + + /** + * Execute provided task in a separate thread. + * + * @param task Task to execute. + * @return Result. + */ + protected static <T> IgniteInternalFuture<T> execute(final Callable<T> task) { + final GridFutureAdapter<T> fut = new GridFutureAdapter<>(); + + new Thread(new Runnable() { + @Override public void run() { + try { + fut.onDone(task.call()); + } + catch (Throwable e) { + fut.onDone(e); + } + } + }).start(); + + return fut; + } + + + /** + * Create the given directories and files in the given IGFS. + * + * @param igfs IGFS. + * @param dirs Directories. + * @param files Files. + * @throws Exception If failed. + */ + @SuppressWarnings("EmptyTryBlock") + public static void create(IgfsImpl igfs, @Nullable IgfsPath[] dirs, @Nullable IgfsPath[] files) throws Exception { + if (dirs != null) { + for (IgfsPath dir : dirs) + igfs.mkdirs(dir); + } + + if (files != null) { + for (IgfsPath file : files) { + try (OutputStream ignored = igfs.create(file, true)) { + // No-op. + } + + igfs.await(file); + } + } + } + + /** + * Creates specified files/directories + * + * @param uni The file system to operate on. + * @param dirs The directories to create. + * @param files The files to create. + * @throws Exception On error. + */ + @SuppressWarnings("EmptyTryBlock") + public void create(IgfsSecondaryFileSystemTestAdapter uni, @Nullable IgfsPath[] dirs, @Nullable IgfsPath[] files) + throws Exception { + if (dirs != null) { + for (IgfsPath dir : dirs) + uni.mkdirs(dir.toString()); + } + + if (files != null) { + for (IgfsPath file : files) + try (OutputStream ignore = uni.openOutputStream(file.toString(), false)) { + // No-op + } + } + } + + /** + * Create the file in the given IGFS and write provided data chunks to it. + * + * @param igfs IGFS. + * @param file File. + * @param overwrite Overwrite flag. + * @param chunks Data chunks. + * @throws IOException In case of IO exception. + */ + protected static void createFile(IgfsEx igfs, IgfsPath file, boolean overwrite, @Nullable byte[]... chunks) + throws IOException { + OutputStream os = null; + + try { + os = igfs.create(file, overwrite); + + writeFileChunks(os, chunks); + } + finally { + U.closeQuiet(os); + + awaitFileClose(igfs, file); + } + } + + /** + * Create the file in the given IGFS and write provided data chunks to it. + * + * @param file File. + * @param chunks Data chunks. + * @throws IOException In case of IO exception. + */ + protected static void createFile(IgfsSecondaryFileSystemTestAdapter uni, IgfsPath file, @Nullable byte[]... chunks) + throws IOException { + OutputStream os = null; + + try { + os = uni.openOutputStream(file.toString(), false); + + writeFileChunks(os, chunks); + } + finally { + U.closeQuiet(os); + + IgfsEx igfsEx = uni.igfs(); + + if (igfsEx != null) + awaitFileClose(igfsEx, file); + } + } + + /** + * Create the file in the given IGFS and write provided data chunks to it. + * + * @param igfs IGFS. + * @param file File. + * @param overwrite Overwrite flag. + * @param blockSize Block size. + * @param chunks Data chunks. + * @throws Exception If failed. + */ + protected static void createFile(IgfsImpl igfs, IgfsPath file, boolean overwrite, long blockSize, + @Nullable byte[]... chunks) throws Exception { + IgfsOutputStream os = null; + + try { + os = igfs.create(file, 256, overwrite, null, 0, blockSize, null); + + writeFileChunks(os, chunks); + } + finally { + U.closeQuiet(os); + + awaitFileClose(igfs, file); + } + } + + /** + * Append to the file in the given IGFS provided data chunks. + * + * @param igfs IGFS. + * @param file File. + * @param chunks Data chunks. + * @throws Exception If failed. + */ + protected static void appendFile(IgfsImpl igfs, IgfsPath file, @Nullable byte[]... chunks) + throws Exception { + IgfsOutputStream os = null; + + try { + os = igfs.append(file, false); + + writeFileChunks(os, chunks); + } + finally { + U.closeQuiet(os); + + awaitFileClose(igfs, file); + } + } + + /** + * Write provided data chunks to the file output stream. + * + * @param os Output stream. + * @param chunks Data chunks. + * @throws IOException If failed. + */ + protected static void writeFileChunks(OutputStream os, @Nullable byte[]... chunks) throws IOException { + if (chunks != null && chunks.length > 0) { + for (byte[] chunk : chunks) + os.write(chunk); + } + } + + /** + * Await for previously opened output stream to close. This is achieved by requesting dummy update on the file. + * + * @param igfs IGFS. + * @param file File. + */ + public static void awaitFileClose(IgfsSecondaryFileSystem igfs, IgfsPath file) { + try { + igfs.update(file, Collections.singletonMap("prop", "val")); + } + catch (IgniteException ignore) { + // No-op. + } + } + + /** + * Await for previously opened output stream to close. + * + * @param igfs IGFS. + * @param file File. + */ + public static void awaitFileClose(@Nullable IgfsEx igfs, IgfsPath file) { + igfs.await(file); + } + + /** + * Ensure that the given paths exist in the given IGFSs. + * + * @param igfs First IGFS. + * @param igfsSecondary Second IGFS. + * @param paths Paths. + * @throws Exception If failed. + */ + protected void checkExist(IgfsImpl igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary, IgfsPath... paths) + throws Exception { + checkExist(igfs, paths); + + if (dual) + checkExist(igfsSecondary, paths); + } + + /** + * Ensure that the given paths exist in the given IGFS. + * + * @param igfs IGFS. + * @param paths Paths. + * @throws IgniteCheckedException If failed. + */ + protected static void checkExist(IgfsImpl igfs, IgfsPath... paths) throws IgniteCheckedException { + for (IgfsPath path : paths) + assert igfs.exists(path) : "Path doesn't exist [igfs=" + igfs.name() + ", path=" + path + ']'; + } + + /** + * Ensure that the given paths exist in the given IGFS. + * + * @param uni filesystem. + * @param paths Paths. + * @throws IgniteCheckedException If failed. + */ + protected void checkExist(IgfsSecondaryFileSystemTestAdapter uni, IgfsPath... paths) throws IgniteCheckedException { + IgfsEx ex = uni.igfs(); + + for (IgfsPath path : paths) { + if (ex != null) + assert ex.context().meta().fileId(path) != null : "Path doesn't exist [igfs=" + ex.name() + + ", path=" + path + ']'; + + try { + assert uni.exists(path.toString()) : "Path doesn't exist [igfs=" + uni.name() + ", path=" + path + ']'; + } + catch (IOException ioe) { + throw new IgniteCheckedException(ioe); + } + } + } + + /** + * Ensure that the given paths don't exist in the given IGFSs. + * + * @param igfs First IGFS. + * @param igfsSecondary Second IGFS. + * @param paths Paths. + * @throws Exception If failed. + */ + protected void checkNotExist(IgfsImpl igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary, IgfsPath... paths) + throws Exception { + checkNotExist(igfs, paths); + + if (dual) + checkNotExist(igfsSecondary, paths); + } + + /** + * Ensure that the given paths don't exist in the given IGFS. + * + * @param igfs IGFS. + * @param paths Paths. + * @throws Exception If failed. + */ + protected void checkNotExist(IgfsImpl igfs, IgfsPath... paths) throws Exception { + for (IgfsPath path : paths) + assert !igfs.exists(path) : "Path exists [igfs=" + igfs.name() + ", path=" + path + ']'; + } + + /** + * Ensure that the given paths don't exist in the given IGFS. + * + * @param uni secondary FS. + * @param paths Paths. + * @throws Exception If failed. + */ + protected void checkNotExist(IgfsSecondaryFileSystemTestAdapter uni, IgfsPath... paths) throws Exception { + IgfsEx ex = uni.igfs(); + + for (IgfsPath path : paths) { + if (ex != null) + assert !ex.exists(path) : "Path exists [igfs=" + ex.name() + ", path=" + path + ']'; + + assert !uni.exists(path.toString()) : "Path exists [igfs=" + uni.name() + ", path=" + path + ']'; + } + } + + /** + * Ensure that the given file exists in the given IGFSs and that it has exactly the same content as provided in the + * "data" parameter. + * + * @param igfs First IGFS. + * @param igfsSecondary Second IGFS. + * @param file File. + * @param chunks Expected data. + * @throws Exception If failed. + */ + protected void checkFile(@Nullable IgfsImpl igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary, IgfsPath file, + @Nullable byte[]... chunks) throws Exception { + if (igfs != null) { + checkExist(igfs, file); + checkFileContent(igfs, file, chunks); + } + + if (dual) { + checkExist(igfsSecondary, file); + checkFileContent(igfsSecondary, file.toString(), chunks); + } + } + + /** + * Ensure that the given file has exactly the same content as provided in the "data" parameter. + * + * @param igfs IGFS. + * @param file File. + * @param chunks Expected data. + * @throws IOException In case of IO exception. + * @throws IgniteCheckedException In case of Grid exception. + */ + protected static void checkFileContent(IgfsImpl igfs, IgfsPath file, @Nullable byte[]... chunks) + throws IOException, IgniteCheckedException { + if (chunks != null && chunks.length > 0) { + IgfsInputStream is = null; + + try { + is = igfs.open(file); + + int chunkIdx = 0; + int pos = 0; + + for (byte[] chunk : chunks) { + byte[] buf = new byte[chunk.length]; + + is.readFully(pos, buf); + + assert Arrays.equals(chunk, buf) : "Bad chunk [igfs=" + igfs.name() + ", chunkIdx=" + chunkIdx + + ", expected=" + Arrays.toString(chunk) + ", actual=" + Arrays.toString(buf) + ']'; + + chunkIdx++; + pos += chunk.length; + } + + is.close(); + } + finally { + U.closeQuiet(is); + } + } + } + + /** + * Ensure that the given file has exactly the same content as provided in the "data" parameter. + * + * @param uni FS. + * @param path File. + * @param chunks Expected data. + * @throws IOException In case of IO exception. + * @throws IgniteCheckedException In case of Grid exception. + */ + protected void checkFileContent(IgfsSecondaryFileSystemTestAdapter uni, String path, @Nullable byte[]... chunks) + throws IOException, IgniteCheckedException { + if (chunks != null && chunks.length > 0) { + InputStream is = null; + + try { + is = uni.openInputStream(path); + + int chunkIdx = 0; + + int read; + for (byte[] chunk: chunks) { + byte[] buf = new byte[chunk.length]; + + read = 0; + + while (true) { + int r = is.read(buf, read, buf.length - read); + + read += r; + + if (read == buf.length || r <= 0) + break; + } + + assert read == chunk.length : "Chunk #" + chunkIdx + " was not read fully:" + + " read=" + read + ", expected=" + chunk.length; + assert Arrays.equals(chunk, buf) : "Bad chunk [igfs=" + uni.name() + ", chunkIdx=" + chunkIdx + + ", expected=" + Arrays.toString(chunk) + ", actual=" + Arrays.toString(buf) + ']'; + + chunkIdx++; + } + + is.close(); + } + finally { + U.closeQuiet(is); + } + } + } + + /** + * Create map with properties. + * + * @param username User name. + * @param grpName Group name. + * @param perm Permission. + * @return Map with properties. + */ + protected Map<String, String> properties(@Nullable String username, @Nullable String grpName, + @Nullable String perm) { + Map<String, String> props = new HashMap<>(); + + if (username != null) + props.put(IgfsUtils.PROP_USER_NAME, username); + + if (grpName != null) + props.put(IgfsUtils.PROP_GROUP_NAME, grpName); + + if (perm != null) + props.put(IgfsUtils.PROP_PERMISSION, perm); + + return props; + } + + /** + * Convenient method to group paths. + * + * @param paths Paths to group. + * @return Paths as array. + */ + protected static IgfsPath[] paths(IgfsPath... paths) { + return paths; + } + + /** + * Safely clear IGFSs. + * + * @param igfs First IGFS. + * @param igfsSecondary Second IGFS. + * @throws Exception If failed. + */ + protected void clear(IgniteFileSystem igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary) throws Exception { + clear(igfs); + + if (dual) + clear(igfsSecondary); + } + + /** + * Gets the data cache instance for this IGFS instance. + * + * @param igfs The IGFS unstance. + * @return The data cache. + */ + protected static GridCacheAdapter<IgfsBlockKey, byte[]> getDataCache(IgniteFileSystem igfs) { + String dataCacheName = igfs.configuration().getDataCacheName(); + + IgniteEx igniteEx = ((IgfsEx)igfs).context().kernalContext().grid(); + + return ((IgniteKernal)igniteEx).internalCache(dataCacheName); + } + + /** + * Gets meta cache. + * + * @param igfs The IGFS instance. + * @return The data cache. + */ + protected static GridCacheAdapter<IgniteUuid, IgfsEntryInfo> getMetaCache(IgniteFileSystem igfs) { + String dataCacheName = igfs.configuration().getMetaCacheName(); + + IgniteEx igniteEx = ((IgfsEx)igfs).context().kernalContext().grid(); + + return ((IgniteKernal)igniteEx).internalCache(dataCacheName); + } + + /** + * Clear particular IGFS. + * + * @param igfs IGFS. + * @throws Exception If failed. + */ + @SuppressWarnings("unchecked") + public static void clear(IgniteFileSystem igfs) throws Exception { + Field workerMapFld = IgfsImpl.class.getDeclaredField("workerMap"); + + workerMapFld.setAccessible(true); + + // Wait for all workers to finish. + Map<IgfsPath, IgfsFileWorkerBatch> workerMap = (Map<IgfsPath, IgfsFileWorkerBatch>)workerMapFld.get(igfs); + + for (Map.Entry<IgfsPath, IgfsFileWorkerBatch> entry : workerMap.entrySet()) { + entry.getValue().cancel(); + + try { + entry.getValue().await(); + } + catch (IgniteCheckedException e) { + if (!(e instanceof IgfsFileWorkerBatchCancelledException)) + throw e; + } + } + + // Clear igfs. + igfs.format(); + + int prevDifferentSize = Integer.MAX_VALUE; // Previous different size. + int constCnt = 0, totalCnt = 0; + final int constThreshold = 20; + final long sleepPeriod = 500L; + final long totalThreshold = CACHE_EMPTY_TIMEOUT / sleepPeriod; + + while (true) { + int metaSize = 0; + + for (IgniteUuid metaId : getMetaCache(igfs).keySet()) { + if (!IgfsUtils.isRootOrTrashId(metaId)) + metaSize++; + } + + int dataSize = getDataCache(igfs).size(); + + int size = metaSize + dataSize; + + if (size <= 2) + return; // Caches are cleared, we're done. (2 because ROOT & TRASH always exist). + + X.println("Sum size: " + size); + + if (size > prevDifferentSize) { + X.println("Summary cache size has grown unexpectedly: size=" + size + ", prevSize=" + prevDifferentSize); + + break; + } + + if (totalCnt > totalThreshold) { + X.println("Timeout exceeded."); + + break; + } + + if (size == prevDifferentSize) { + constCnt++; + + if (constCnt == constThreshold) { + X.println("Summary cache size stays unchanged for too long: size=" + size); + + break; + } + } else { + constCnt = 0; + + prevDifferentSize = size; // renew; + } + + Thread.sleep(sleepPeriod); + + totalCnt++; + } + + dumpCache("MetaCache" , getMetaCache(igfs)); + + dumpCache("DataCache" , getDataCache(igfs)); + + fail("Caches are not empty."); + } + + /** + * Dumps given cache for diagnostic purposes. + * + * @param cacheName Name. + * @param cache The cache. + */ + private static void dumpCache(String cacheName, GridCacheAdapter<?,?> cache) { + X.println("=============================== " + cacheName + " cache dump: "); + + Iterable<? extends GridCacheEntryEx> entries = cache.entries(); + + for (GridCacheEntryEx e: entries) + X.println("Lost " + cacheName + " entry = " + e); + } + + /** + * Clear particular {@link IgfsSecondaryFileSystemTestAdapter}. + * + * @param uni IGFS. + * @throws Exception If failed. + */ + @SuppressWarnings("unchecked") + public static void clear(IgfsSecondaryFileSystemTestAdapter uni) throws Exception { + IgfsEx igfsEx = uni.igfs(); + + if (igfsEx != null) + clear(igfsEx); + + // Clear the filesystem. + uni.format(); + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + clear(igfs, igfsSecondary); + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/b5757642/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractSelfTest.java index 86c2449..c9b08d9 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsAbstractSelfTest.java @@ -17,55 +17,32 @@ package org.apache.ignite.internal.processors.igfs; -import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; -import org.apache.ignite.IgniteFileSystem; -import org.apache.ignite.Ignition; import org.apache.ignite.cache.CacheMemoryMode; import org.apache.ignite.cache.CachePeekMode; -import org.apache.ignite.cache.CacheWriteSynchronizationMode; -import org.apache.ignite.configuration.CacheConfiguration; -import org.apache.ignite.configuration.FileSystemConfiguration; -import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.igfs.IgfsDirectoryNotEmptyException; import org.apache.ignite.igfs.IgfsException; import org.apache.ignite.igfs.IgfsFile; -import org.apache.ignite.igfs.IgfsGroupDataBlocksKeyMapper; import org.apache.ignite.igfs.IgfsInputStream; -import org.apache.ignite.igfs.IgfsIpcEndpointConfiguration; -import org.apache.ignite.igfs.IgfsIpcEndpointType; import org.apache.ignite.igfs.IgfsMode; import org.apache.ignite.igfs.IgfsOutputStream; import org.apache.ignite.igfs.IgfsParentNotDirectoryException; import org.apache.ignite.igfs.IgfsPath; import org.apache.ignite.igfs.IgfsPathNotFoundException; -import org.apache.ignite.igfs.secondary.IgfsSecondaryFileSystem; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInternalFuture; -import org.apache.ignite.internal.IgniteKernal; import org.apache.ignite.internal.processors.cache.GridCacheAdapter; import org.apache.ignite.internal.processors.cache.GridCacheEntryEx; -import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.lang.GridAbsPredicate; import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteBiTuple; import org.apache.ignite.lang.IgniteUuid; -import org.apache.ignite.marshaller.optimized.OptimizedMarshaller; -import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; -import org.apache.ignite.spi.discovery.tcp.ipfinder.TcpDiscoveryIpFinder; -import org.apache.ignite.spi.discovery.tcp.ipfinder.vm.TcpDiscoveryVmIpFinder; import org.apache.ignite.testframework.GridTestUtils; -import org.jetbrains.annotations.Nullable; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.lang.reflect.Field; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; @@ -84,142 +61,18 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import static org.apache.ignite.cache.CacheAtomicityMode.TRANSACTIONAL; -import static org.apache.ignite.cache.CacheMemoryMode.ONHEAP_TIERED; -import static org.apache.ignite.cache.CacheMode.PARTITIONED; -import static org.apache.ignite.cache.CacheMode.REPLICATED; -import static org.apache.ignite.igfs.IgfsMode.PRIMARY; -import static org.apache.ignite.igfs.IgfsMode.PROXY; - /** * Test fo regular igfs operations. */ @SuppressWarnings({"ThrowableResultOfMethodCallIgnored", "ConstantConditions"}) -public abstract class IgfsAbstractSelfTest extends IgfsCommonAbstractTest { - /** IGFS block size. */ - protected static final int IGFS_BLOCK_SIZE = 512 * 1024; - - /** Default block size (32Mb). */ - protected static final long BLOCK_SIZE = 32 * 1024 * 1024; - - /** Default repeat count. */ - protected static final int REPEAT_CNT = 5; // Diagnostic: up to 500; Regression: 5 - - /** Concurrent operations count. */ - protected static final int OPS_CNT = 16; - - /** Renames count. */ - protected static final int RENAME_CNT = OPS_CNT; - - /** Deletes count. */ - protected static final int DELETE_CNT = OPS_CNT; - - /** Updates count. */ - protected static final int UPDATE_CNT = OPS_CNT; - - /** Mkdirs count. */ - protected static final int MKDIRS_CNT = OPS_CNT; - - /** Create count. */ - protected static final int CREATE_CNT = OPS_CNT; - - /** Time to wait until the caches get empty after format. */ - private static final long CACHE_EMPTY_TIMEOUT = 30_000L; - - /** Seed to generate random numbers. */ - protected static final long SEED = System.currentTimeMillis(); - - /** Amount of blocks to prefetch. */ - protected static final int PREFETCH_BLOCKS = 1; - - /** Amount of sequential block reads before prefetch is triggered. */ - protected static final int SEQ_READS_BEFORE_PREFETCH = 2; - - /** Primary file system REST endpoint configuration map. */ - protected static final IgfsIpcEndpointConfiguration PRIMARY_REST_CFG; - - /** Secondary file system REST endpoint configuration map. */ - protected static final IgfsIpcEndpointConfiguration SECONDARY_REST_CFG; - - /** Directory. */ - protected static final IgfsPath DIR = new IgfsPath("/dir"); - - /** Sub-directory. */ - protected static final IgfsPath SUBDIR = new IgfsPath(DIR, "subdir"); - - /** Another sub-directory in the same directory. */ - protected static final IgfsPath SUBDIR2 = new IgfsPath(DIR, "subdir2"); - - /** Sub-directory of the sub-directory. */ - protected static final IgfsPath SUBSUBDIR = new IgfsPath(SUBDIR, "subsubdir"); - - /** File. */ - protected static final IgfsPath FILE = new IgfsPath(SUBDIR, "file"); - - /** Another file in the same directory. */ - protected static final IgfsPath FILE2 = new IgfsPath(SUBDIR, "file2"); - - /** Other directory. */ - protected static final IgfsPath DIR_NEW = new IgfsPath("/dirNew"); - - /** Other subdirectory. */ - protected static final IgfsPath SUBDIR_NEW = new IgfsPath(DIR_NEW, "subdirNew"); - - /** Other sub-directory of the sub-directory. */ - protected static final IgfsPath SUBSUBDIR_NEW = new IgfsPath(SUBDIR_NEW, "subsubdirNew"); - - /** Other file. */ - protected static final IgfsPath FILE_NEW = new IgfsPath(SUBDIR_NEW, "fileNew"); - - /** Default data chunk (128 bytes). */ - protected static final byte[] chunk = createChunk(128); - - /** Primary IGFS. */ - protected static IgfsImpl igfs; - - /** Secondary IGFS */ - protected static IgfsSecondaryFileSystem igfsSecondaryFileSystem; - - /** Secondary file system lower layer "backdoor" wrapped in UniversalFileSystemAdapter: */ - protected static IgfsSecondaryFileSystemTestAdapter igfsSecondary; - - /** IGFS mode. */ - protected final IgfsMode mode; - - /** Dual mode flag. */ - protected final boolean dual; - - /** Memory mode. */ - protected final CacheMemoryMode memoryMode; - - /** IP finder for primary topology. */ - protected final TcpDiscoveryVmIpFinder primaryIpFinder = new TcpDiscoveryVmIpFinder(true); - - /** IP finder for secondary topology. */ - protected final TcpDiscoveryVmIpFinder secondaryIpFinder = new TcpDiscoveryVmIpFinder(true); - - /** Ignite nodes of cluster, excluding the secondary file system node, if any. */ - protected Ignite[] nodes; - - static { - PRIMARY_REST_CFG = new IgfsIpcEndpointConfiguration(); - - PRIMARY_REST_CFG.setType(IgfsIpcEndpointType.TCP); - PRIMARY_REST_CFG.setPort(10500); - - SECONDARY_REST_CFG = new IgfsIpcEndpointConfiguration(); - - SECONDARY_REST_CFG.setType(IgfsIpcEndpointType.TCP); - SECONDARY_REST_CFG.setPort(11500); - } - +public abstract class IgfsAbstractSelfTest extends IgfsAbstractBaseSelfTest { /** * Constructor. * * @param mode IGFS mode. */ protected IgfsAbstractSelfTest(IgfsMode mode) { - this(mode, ONHEAP_TIERED); + super(mode); } /** @@ -229,258 +82,7 @@ public abstract class IgfsAbstractSelfTest extends IgfsCommonAbstractTest { * @param memoryMode Memory mode. */ protected IgfsAbstractSelfTest(IgfsMode mode, CacheMemoryMode memoryMode) { - assert mode != null && mode != PROXY; - - this.mode = mode; - this.memoryMode = memoryMode; - - dual = mode != PRIMARY; - } - - /** - * @return Relaxed consistency flag. - */ - protected boolean relaxedConsistency() { - return false; - } - - /** - * @return Relaxed consistency flag. - */ - protected boolean initializeDefaultPathModes() { - return false; - } - - /** - * @return Client flag. - */ - protected boolean client() { - return false; - } - - /** - * @return Use optimized marshaller flag. - */ - protected boolean useOptimizedMarshaller() { - return false; - } - - /** - * @return Whether append is supported. - */ - protected boolean appendSupported() { - return true; - } - - /** - * @return Whether permissions are supported. - */ - protected boolean permissionsSupported() { - return true; - } - - /** - * @return Whether properties are supported. - */ - protected boolean propertiesSupported() { - return true; - } - - /** - * @return Whether times are supported. - */ - protected boolean timesSupported() { - return true; - } - - /** - * @return Amount of nodes to start. - */ - protected int nodeCount() { - return 1; - } - - /** - * Data chunk. - * - * @param len Length. - * @return Data chunk. - */ - static byte[] createChunk(int len) { - byte[] chunk = new byte[len]; - - for (int i = 0; i < chunk.length; i++) - chunk[i] = (byte)i; - - return chunk; - } - - /** {@inheritDoc} */ - @Override protected void beforeTestsStarted() throws Exception { - igfsSecondaryFileSystem = createSecondaryFileSystemStack(); - - nodes = new Ignite[nodeCount()]; - - for (int i = 0; i < nodes.length; i++) { - String nodeName = i == 0 ? "ignite" : "ignite" + i; - - nodes[i] = startGridWithIgfs(nodeName, "igfs", mode, igfsSecondaryFileSystem, PRIMARY_REST_CFG, - primaryIpFinder); - } - - igfs = (IgfsImpl) nodes[0].fileSystem("igfs"); - - if (client()) { - // Start client. - Ignition.setClientMode(true); - - try { - Ignite ignite = startGridWithIgfs("ignite-client", "igfs", mode, igfsSecondaryFileSystem, - PRIMARY_REST_CFG, primaryIpFinder); - - igfs = (IgfsImpl) ignite.fileSystem("igfs"); - } - finally { - Ignition.setClientMode(false); - } - } - } - - /** - * Creates secondary file system stack. - * - * @return The secondary file system. - * @throws Exception On error. - */ - protected IgfsSecondaryFileSystem createSecondaryFileSystemStack() throws Exception { - Ignite igniteSecondary = startGridWithIgfs("ignite-secondary", "igfs-secondary", PRIMARY, null, - SECONDARY_REST_CFG, secondaryIpFinder); - - IgfsEx secondaryIgfsImpl = (IgfsEx) igniteSecondary.fileSystem("igfs-secondary"); - - igfsSecondary = new DefaultIgfsSecondaryFileSystemTestAdapter(secondaryIgfsImpl); - - return secondaryIgfsImpl.asSecondary(); - } - - /** {@inheritDoc} */ - @Override protected void afterTest() throws Exception { - clear(igfs, igfsSecondary); - - assert igfs.listFiles(new IgfsPath("/")).isEmpty(); - } - - /** {@inheritDoc} */ - @Override protected void afterTestsStopped() throws Exception { - G.stopAll(true); - } - - /** - * Start grid with IGFS. - * - * @param gridName Grid name. - * @param igfsName IGFS name - * @param mode IGFS mode. - * @param secondaryFs Secondary file system (optional). - * @param restCfg Rest configuration string (optional). - * @param ipFinder IP finder. - * @return Started grid instance. - * @throws Exception If failed. - */ - @SuppressWarnings("unchecked") - protected Ignite startGridWithIgfs(String gridName, String igfsName, IgfsMode mode, - @Nullable IgfsSecondaryFileSystem secondaryFs, @Nullable IgfsIpcEndpointConfiguration restCfg, - TcpDiscoveryIpFinder ipFinder) throws Exception { - FileSystemConfiguration igfsCfg = new FileSystemConfiguration(); - - igfsCfg.setDataCacheName("dataCache"); - igfsCfg.setMetaCacheName("metaCache"); - igfsCfg.setName(igfsName); - igfsCfg.setBlockSize(IGFS_BLOCK_SIZE); - igfsCfg.setDefaultMode(mode); - igfsCfg.setIpcEndpointConfiguration(restCfg); - igfsCfg.setSecondaryFileSystem(secondaryFs); - igfsCfg.setPrefetchBlocks(PREFETCH_BLOCKS); - igfsCfg.setSequentialReadsBeforePrefetch(SEQ_READS_BEFORE_PREFETCH); - igfsCfg.setRelaxedConsistency(relaxedConsistency()); - - igfsCfg.setInitializeDefaultPathModes(initializeDefaultPathModes()); - - CacheConfiguration dataCacheCfg = defaultCacheConfiguration(); - - dataCacheCfg.setName("dataCache"); - dataCacheCfg.setNearConfiguration(null); - dataCacheCfg.setCacheMode(PARTITIONED); - dataCacheCfg.setNearConfiguration(null); - dataCacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); - dataCacheCfg.setAffinityMapper(new IgfsGroupDataBlocksKeyMapper(2)); - dataCacheCfg.setBackups(0); - dataCacheCfg.setAtomicityMode(TRANSACTIONAL); - dataCacheCfg.setMemoryMode(memoryMode); - dataCacheCfg.setOffHeapMaxMemory(0); - - CacheConfiguration metaCacheCfg = defaultCacheConfiguration(); - - metaCacheCfg.setName("metaCache"); - metaCacheCfg.setNearConfiguration(null); - metaCacheCfg.setCacheMode(REPLICATED); - metaCacheCfg.setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC); - metaCacheCfg.setAtomicityMode(TRANSACTIONAL); - - IgniteConfiguration cfg = new IgniteConfiguration(); - - if (useOptimizedMarshaller()) - cfg.setMarshaller(new OptimizedMarshaller()); - - cfg.setGridName(gridName); - - TcpDiscoverySpi discoSpi = new TcpDiscoverySpi(); - - discoSpi.setIpFinder(ipFinder); - - prepareCacheConfigurations(dataCacheCfg, metaCacheCfg); - - cfg.setDiscoverySpi(discoSpi); - cfg.setCacheConfiguration(dataCacheCfg, metaCacheCfg); - cfg.setFileSystemConfiguration(igfsCfg); - - cfg.setLocalHost("127.0.0.1"); - cfg.setConnectorConfiguration(null); - - return G.start(cfg); - } - - /** - * Prepare cache configuration. - * - * @param dataCacheCfg Data cache configuration. - * @param metaCacheCfg Meta cache configuration. - */ - protected void prepareCacheConfigurations(CacheConfiguration dataCacheCfg, CacheConfiguration metaCacheCfg) { - // Noop - } - - /** - * Execute provided task in a separate thread. - * - * @param task Task to execute. - * @return Result. - */ - protected static <T> IgniteInternalFuture<T> execute(final Callable<T> task) { - final GridFutureAdapter<T> fut = new GridFutureAdapter<>(); - - new Thread(new Runnable() { - @Override public void run() { - try { - fut.onDone(task.call()); - } - catch (Throwable e) { - fut.onDone(e); - } - } - }).start(); - - return fut; + super(mode, memoryMode); } /** @@ -1138,8 +740,9 @@ public abstract class IgfsAbstractSelfTest extends IgfsCommonAbstractTest { } /** + * Check root property update. * - * @throws Exception + * @throws Exception If failed. */ private void checkRootPropertyUpdate(String prop, String setVal, String expGetVal) throws Exception { final IgfsPath rootPath = new IgfsPath("/"); @@ -2945,609 +2548,4 @@ public abstract class IgfsAbstractSelfTest extends IgfsCommonAbstractTest { U.joinThreads(threads, null); } - - /** - * Create the given directories and files in the given IGFS. - * - * @param igfs IGFS. - * @param dirs Directories. - * @param files Files. - * @throws Exception If failed. - */ - @SuppressWarnings("EmptyTryBlock") - public static void create(IgfsImpl igfs, @Nullable IgfsPath[] dirs, @Nullable IgfsPath[] files) throws Exception { - if (dirs != null) { - for (IgfsPath dir : dirs) - igfs.mkdirs(dir); - } - - if (files != null) { - for (IgfsPath file : files) { - try (OutputStream os = igfs.create(file, true)) { - // No-op. - } - - igfs.await(file); - } - } - } - - /** - * Creates specified files/directories - * - * @param uni The file system to operate on. - * @param dirs The directories to create. - * @param files The files to create. - * @throws Exception On error. - */ - @SuppressWarnings("EmptyTryBlock") - public void create(IgfsSecondaryFileSystemTestAdapter uni, @Nullable IgfsPath[] dirs, @Nullable IgfsPath[] files) - throws Exception { - if (dirs != null) { - for (IgfsPath dir : dirs) - uni.mkdirs(dir.toString()); - } - - if (files != null) { - for (IgfsPath file : files) - try (OutputStream ignore = uni.openOutputStream(file.toString(), false)) { - // No-op - } - } - } - - /** - * Create the file in the given IGFS and write provided data chunks to it. - * - * @param igfs IGFS. - * @param file File. - * @param overwrite Overwrite flag. - * @param chunks Data chunks. - * @throws IOException In case of IO exception. - */ - protected static void createFile(IgfsEx igfs, IgfsPath file, boolean overwrite, @Nullable byte[]... chunks) - throws IOException { - OutputStream os = null; - - try { - os = igfs.create(file, overwrite); - - writeFileChunks(os, chunks); - } - finally { - U.closeQuiet(os); - - awaitFileClose(igfs, file); - } - } - - /** - * Create the file in the given IGFS and write provided data chunks to it. - * - * @param file File. - * @param chunks Data chunks. - * @throws IOException In case of IO exception. - */ - protected static void createFile(IgfsSecondaryFileSystemTestAdapter uni, IgfsPath file, @Nullable byte[]... chunks) - throws IOException { - OutputStream os = null; - - try { - os = uni.openOutputStream(file.toString(), false); - - writeFileChunks(os, chunks); - } - finally { - U.closeQuiet(os); - - IgfsEx igfsEx = uni.igfs(); - - if (igfsEx != null) - awaitFileClose(igfsEx, file); - } - } - - /** - * Create the file in the given IGFS and write provided data chunks to it. - * - * @param igfs IGFS. - * @param file File. - * @param overwrite Overwrite flag. - * @param blockSize Block size. - * @param chunks Data chunks. - * @throws Exception If failed. - */ - protected static void createFile(IgfsImpl igfs, IgfsPath file, boolean overwrite, long blockSize, - @Nullable byte[]... chunks) throws Exception { - IgfsOutputStream os = null; - - try { - os = igfs.create(file, 256, overwrite, null, 0, blockSize, null); - - writeFileChunks(os, chunks); - } - finally { - U.closeQuiet(os); - - awaitFileClose(igfs, file); - } - } - - /** - * Append to the file in the given IGFS provided data chunks. - * - * @param igfs IGFS. - * @param file File. - * @param chunks Data chunks. - * @throws Exception If failed. - */ - protected static void appendFile(IgfsImpl igfs, IgfsPath file, @Nullable byte[]... chunks) - throws Exception { - IgfsOutputStream os = null; - - try { - os = igfs.append(file, false); - - writeFileChunks(os, chunks); - } - finally { - U.closeQuiet(os); - - awaitFileClose(igfs, file); - } - } - - /** - * Write provided data chunks to the file output stream. - * - * @param os Output stream. - * @param chunks Data chunks. - * @throws IOException If failed. - */ - protected static void writeFileChunks(OutputStream os, @Nullable byte[]... chunks) throws IOException { - if (chunks != null && chunks.length > 0) { - for (byte[] chunk : chunks) - os.write(chunk); - } - } - - /** - * Await for previously opened output stream to close. This is achieved by requesting dummy update on the file. - * - * @param igfs IGFS. - * @param file File. - */ - public static void awaitFileClose(IgfsSecondaryFileSystem igfs, IgfsPath file) { - try { - igfs.update(file, Collections.singletonMap("prop", "val")); - } - catch (IgniteException ignore) { - // No-op. - } - } - - /** - * Await for previously opened output stream to close. - * - * @param igfs IGFS. - * @param file File. - */ - public static void awaitFileClose(@Nullable IgfsEx igfs, IgfsPath file) { - igfs.await(file); - } - - /** - * Ensure that the given paths exist in the given IGFSs. - * - * @param igfs First IGFS. - * @param igfsSecondary Second IGFS. - * @param paths Paths. - * @throws Exception If failed. - */ - protected void checkExist(IgfsImpl igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary, IgfsPath... paths) - throws Exception { - checkExist(igfs, paths); - - if (dual) - checkExist(igfsSecondary, paths); - } - - /** - * Ensure that the given paths exist in the given IGFS. - * - * @param igfs IGFS. - * @param paths Paths. - * @throws IgniteCheckedException If failed. - */ - protected static void checkExist(IgfsImpl igfs, IgfsPath... paths) throws IgniteCheckedException { - for (IgfsPath path : paths) - assert igfs.exists(path) : "Path doesn't exist [igfs=" + igfs.name() + ", path=" + path + ']'; - } - - /** - * Ensure that the given paths exist in the given IGFS. - * - * @param uni filesystem. - * @param paths Paths. - * @throws IgniteCheckedException If failed. - */ - protected void checkExist(IgfsSecondaryFileSystemTestAdapter uni, IgfsPath... paths) throws IgniteCheckedException { - IgfsEx ex = uni.igfs(); - - for (IgfsPath path : paths) { - if (ex != null) - assert ex.context().meta().fileId(path) != null : "Path doesn't exist [igfs=" + ex.name() + - ", path=" + path + ']'; - - try { - assert uni.exists(path.toString()) : "Path doesn't exist [igfs=" + uni.name() + ", path=" + path + ']'; - } - catch (IOException ioe) { - throw new IgniteCheckedException(ioe); - } - } - } - - /** - * Ensure that the given paths don't exist in the given IGFSs. - * - * @param igfs First IGFS. - * @param igfsSecondary Second IGFS. - * @param paths Paths. - * @throws Exception If failed. - */ - protected void checkNotExist(IgfsImpl igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary, IgfsPath... paths) - throws Exception { - checkNotExist(igfs, paths); - - if (dual) - checkNotExist(igfsSecondary, paths); - } - - /** - * Ensure that the given paths don't exist in the given IGFS. - * - * @param igfs IGFS. - * @param paths Paths. - * @throws Exception If failed. - */ - protected void checkNotExist(IgfsImpl igfs, IgfsPath... paths) throws Exception { - for (IgfsPath path : paths) - assert !igfs.exists(path) : "Path exists [igfs=" + igfs.name() + ", path=" + path + ']'; - } - - /** - * Ensure that the given paths don't exist in the given IGFS. - * - * @param uni secondary FS. - * @param paths Paths. - * @throws Exception If failed. - */ - protected void checkNotExist(IgfsSecondaryFileSystemTestAdapter uni, IgfsPath... paths) throws Exception { - IgfsEx ex = uni.igfs(); - - for (IgfsPath path : paths) { - if (ex != null) - assert !ex.exists(path) : "Path exists [igfs=" + ex.name() + ", path=" + path + ']'; - - assert !uni.exists(path.toString()) : "Path exists [igfs=" + uni.name() + ", path=" + path + ']'; - } - } - - /** - * Ensure that the given file exists in the given IGFSs and that it has exactly the same content as provided in the - * "data" parameter. - * - * @param igfs First IGFS. - * @param igfsSecondary Second IGFS. - * @param file File. - * @param chunks Expected data. - * @throws Exception If failed. - */ - protected void checkFile(@Nullable IgfsImpl igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary, IgfsPath file, - @Nullable byte[]... chunks) throws Exception { - if (igfs != null) { - checkExist(igfs, file); - checkFileContent(igfs, file, chunks); - } - - if (dual) { - checkExist(igfsSecondary, file); - checkFileContent(igfsSecondary, file.toString(), chunks); - } - } - - /** - * Ensure that the given file has exactly the same content as provided in the "data" parameter. - * - * @param igfs IGFS. - * @param file File. - * @param chunks Expected data. - * @throws IOException In case of IO exception. - * @throws IgniteCheckedException In case of Grid exception. - */ - protected static void checkFileContent(IgfsImpl igfs, IgfsPath file, @Nullable byte[]... chunks) - throws IOException, IgniteCheckedException { - if (chunks != null && chunks.length > 0) { - IgfsInputStream is = null; - - try { - is = igfs.open(file); - - int chunkIdx = 0; - int pos = 0; - - for (byte[] chunk : chunks) { - byte[] buf = new byte[chunk.length]; - - is.readFully(pos, buf); - - assert Arrays.equals(chunk, buf) : "Bad chunk [igfs=" + igfs.name() + ", chunkIdx=" + chunkIdx + - ", expected=" + Arrays.toString(chunk) + ", actual=" + Arrays.toString(buf) + ']'; - - chunkIdx++; - pos += chunk.length; - } - - is.close(); - } - finally { - U.closeQuiet(is); - } - } - } - - /** - * Ensure that the given file has exactly the same content as provided in the "data" parameter. - * - * @param uni FS. - * @param path File. - * @param chunks Expected data. - * @throws IOException In case of IO exception. - * @throws IgniteCheckedException In case of Grid exception. - */ - protected void checkFileContent(IgfsSecondaryFileSystemTestAdapter uni, String path, @Nullable byte[]... chunks) - throws IOException, IgniteCheckedException { - if (chunks != null && chunks.length > 0) { - InputStream is = null; - - try { - is = uni.openInputStream(path); - - int chunkIdx = 0; - - int read; - for (byte[] chunk: chunks) { - byte[] buf = new byte[chunk.length]; - - read = 0; - - while (true) { - int r = is.read(buf, read, buf.length - read); - - read += r; - - if (read == buf.length || r <= 0) - break; - } - - assert read == chunk.length : "Chunk #" + chunkIdx + " was not read fully:" + - " read=" + read + ", expected=" + chunk.length; - assert Arrays.equals(chunk, buf) : "Bad chunk [igfs=" + uni.name() + ", chunkIdx=" + chunkIdx + - ", expected=" + Arrays.toString(chunk) + ", actual=" + Arrays.toString(buf) + ']'; - - chunkIdx++; - } - - is.close(); - } - finally { - U.closeQuiet(is); - } - } - } - - /** - * Create map with properties. - * - * @param username User name. - * @param grpName Group name. - * @param perm Permission. - * @return Map with properties. - */ - protected Map<String, String> properties(@Nullable String username, @Nullable String grpName, - @Nullable String perm) { - Map<String, String> props = new HashMap<>(); - - if (username != null) - props.put(IgfsUtils.PROP_USER_NAME, username); - - if (grpName != null) - props.put(IgfsUtils.PROP_GROUP_NAME, grpName); - - if (perm != null) - props.put(IgfsUtils.PROP_PERMISSION, perm); - - return props; - } - - /** - * Convenient method to group paths. - * - * @param paths Paths to group. - * @return Paths as array. - */ - protected static IgfsPath[] paths(IgfsPath... paths) { - return paths; - } - - /** - * Safely clear IGFSs. - * - * @param igfs First IGFS. - * @param igfsSecondary Second IGFS. - * @throws Exception If failed. - */ - protected void clear(IgniteFileSystem igfs, IgfsSecondaryFileSystemTestAdapter igfsSecondary) throws Exception { - clear(igfs); - - if (dual) - clear(igfsSecondary); - } - - /** - * Gets the data cache instance for this IGFS instance. - * - * @param igfs The IGFS unstance. - * @return The data cache. - */ - protected static GridCacheAdapter<IgfsBlockKey, byte[]> getDataCache(IgniteFileSystem igfs) { - String dataCacheName = igfs.configuration().getDataCacheName(); - - IgniteEx igniteEx = ((IgfsEx)igfs).context().kernalContext().grid(); - - return ((IgniteKernal)igniteEx).internalCache(dataCacheName); - } - - /** - * Gets meta cache. - * - * @param igfs The IGFS instance. - * @return The data cache. - */ - protected static GridCacheAdapter<IgniteUuid, IgfsEntryInfo> getMetaCache(IgniteFileSystem igfs) { - String dataCacheName = igfs.configuration().getMetaCacheName(); - - IgniteEx igniteEx = ((IgfsEx)igfs).context().kernalContext().grid(); - - return ((IgniteKernal)igniteEx).internalCache(dataCacheName); - } - - /** - * Clear particular IGFS. - * - * @param igfs IGFS. - * @throws Exception If failed. - */ - @SuppressWarnings("unchecked") - public static void clear(IgniteFileSystem igfs) throws Exception { - Field workerMapFld = IgfsImpl.class.getDeclaredField("workerMap"); - - workerMapFld.setAccessible(true); - - // Wait for all workers to finish. - Map<IgfsPath, IgfsFileWorkerBatch> workerMap = (Map<IgfsPath, IgfsFileWorkerBatch>)workerMapFld.get(igfs); - - for (Map.Entry<IgfsPath, IgfsFileWorkerBatch> entry : workerMap.entrySet()) { - entry.getValue().cancel(); - - try { - entry.getValue().await(); - } - catch (IgniteCheckedException e) { - if (!(e instanceof IgfsFileWorkerBatchCancelledException)) - throw e; - } - } - - // Clear igfs. - igfs.format(); - - int prevDifferentSize = Integer.MAX_VALUE; // Previous different size. - int constCnt = 0, totalCnt = 0; - final int constThreshold = 20; - final long sleepPeriod = 500L; - final long totalThreshold = CACHE_EMPTY_TIMEOUT / sleepPeriod; - - while (true) { - int metaSize = 0; - - for (IgniteUuid metaId : getMetaCache(igfs).keySet()) { - if (!IgfsUtils.isRootOrTrashId(metaId)) - metaSize++; - } - - int dataSize = getDataCache(igfs).size(); - - int size = metaSize + dataSize; - - if (size <= 2) - return; // Caches are cleared, we're done. (2 because ROOT & TRASH always exist). - - X.println("Sum size: " + size); - - if (size > prevDifferentSize) { - X.println("Summary cache size has grown unexpectedly: size=" + size + ", prevSize=" + prevDifferentSize); - - break; - } - - if (totalCnt > totalThreshold) { - X.println("Timeout exceeded."); - - break; - } - - if (size == prevDifferentSize) { - constCnt++; - - if (constCnt == constThreshold) { - X.println("Summary cache size stays unchanged for too long: size=" + size); - - break; - } - } else { - constCnt = 0; - - prevDifferentSize = size; // renew; - } - - Thread.sleep(sleepPeriod); - - totalCnt++; - } - - dumpCache("MetaCache" , getMetaCache(igfs)); - - dumpCache("DataCache" , getDataCache(igfs)); - - fail("Caches are not empty."); - } - - /** - * Dumps given cache for diagnostic purposes. - * - * @param cacheName Name. - * @param cache The cache. - */ - private static void dumpCache(String cacheName, GridCacheAdapter<?,?> cache) { - X.println("=============================== " + cacheName + " cache dump: "); - - Iterable<? extends GridCacheEntryEx> entries = cache.entries(); - - for (GridCacheEntryEx e: entries) - X.println("Lost " + cacheName + " entry = " + e); - } - - /** - * Clear particular {@link IgfsSecondaryFileSystemTestAdapter}. - * - * @param uni IGFS. - * @throws Exception If failed. - */ - @SuppressWarnings("unchecked") - public static void clear(IgfsSecondaryFileSystemTestAdapter uni) throws Exception { - IgfsEx igfsEx = uni.igfs(); - - if (igfsEx != null) - clear(igfsEx); - - // Clear the filesystem. - uni.format(); - } - - /** {@inheritDoc} */ - @Override protected void beforeTest() throws Exception { - clear(igfs, igfsSecondary); - } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/b5757642/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsLocalSecondaryFileSystemDualAbstractSelfTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsLocalSecondaryFileSystemDualAbstractSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsLocalSecondaryFileSystemDualAbstractSelfTest.java index c2f5633..1d1ce8d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsLocalSecondaryFileSystemDualAbstractSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/igfs/IgfsLocalSecondaryFileSystemDualAbstractSelfTest.java @@ -17,12 +17,21 @@ package org.apache.ignite.internal.processors.igfs; +import org.apache.ignite.igfs.IgfsFile; import org.apache.ignite.igfs.IgfsMode; +import org.apache.ignite.igfs.IgfsPath; import org.apache.ignite.igfs.secondary.IgfsSecondaryFileSystem; import org.apache.ignite.igfs.secondary.local.LocalIgfsSecondaryFileSystem; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; +import org.jetbrains.annotations.Nullable; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.Files; +import java.util.Collection; /** * Abstract test for Hadoop 1.0 file system stack. @@ -32,6 +41,24 @@ public abstract class IgfsLocalSecondaryFileSystemDualAbstractSelfTest extends I private static final String FS_WORK_DIR = U.getIgniteHome() + File.separatorChar + "work" + File.separatorChar + "fs"; + /** */ + private static final String FS_EXT_DIR = U.getIgniteHome() + File.separatorChar + "work" + + File.separatorChar + "ext"; + + /** */ + private final File dirLinkDest = new File(FS_EXT_DIR + File.separatorChar + "extdir"); + + /** */ + private final File fileLinkDest = + new File(FS_EXT_DIR + File.separatorChar + "extdir" + File.separatorChar + "filedest"); + + /** */ + private final File dirLinkSrc = new File(FS_WORK_DIR + File.separatorChar + "dir"); + + /** */ + private final File fileLinkSrc = new File(FS_WORK_DIR + File.separatorChar + "file"); + + /** Constructor. * @param mode IGFS mode. */ @@ -39,6 +66,19 @@ public abstract class IgfsLocalSecondaryFileSystemDualAbstractSelfTest extends I super(mode); } + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + final File extDir = new File(FS_EXT_DIR); + + if (!extDir.exists()) + assert extDir.mkdirs(); + else + cleanDirectory(extDir); + } + + /** * Creates secondary filesystems. * @return IgfsSecondaryFileSystem @@ -73,4 +113,107 @@ public abstract class IgfsLocalSecondaryFileSystemDualAbstractSelfTest extends I @Override protected boolean timesSupported() { return false; } + + /** + * + * @throws Exception If failed. + */ + @SuppressWarnings("ConstantConditions") + public void testListPathForSymlink() throws Exception { + if (U.isWindows()) + return; + + createSymlinks(); + + assertTrue(igfs.info(DIR).isDirectory()); + + Collection<IgfsPath> pathes = igfs.listPaths(DIR); + Collection<IgfsFile> files = igfs.listFiles(DIR); + + assertEquals(1, pathes.size()); + assertEquals(1, files.size()); + + assertEquals("filedest", F.first(pathes).name()); + assertEquals("filedest", F.first(files).path().name()); + } + + /** + * + * @throws Exception If failed. + */ + public void testDeleteSymlinkDir() throws Exception { + if (U.isWindows()) + return; + + createSymlinks(); + + // Only symlink must be deleted. Destination content must be exist. + igfs.delete(DIR, true); + + assertTrue(fileLinkDest.exists()); + } + + /** + * + * @throws Exception If failed. + */ + public void testSymlinkToFile() throws Exception { + if (U.isWindows()) + return; + + createSymlinks(); + + checkFileContent(igfs, new IgfsPath("/file"), chunk); + } + + /** + * + * @throws Exception If failed. + */ + private void createSymlinks() throws Exception { + assert dirLinkDest.mkdir(); + + createFile(fileLinkDest, true, chunk); + + Files.createSymbolicLink(dirLinkSrc.toPath(), dirLinkDest.toPath()); + Files.createSymbolicLink(fileLinkSrc.toPath(), fileLinkDest.toPath()); + } + + /** + * @param dir Directory to clean. + */ + private static void cleanDirectory(File dir){ + File[] entries = dir.listFiles(); + + if (entries != null) { + for (File entry : entries) { + if (entry.isDirectory()) { + cleanDirectory(entry); + + assert entry.delete(); + } + else + assert entry.delete(); + } + } + } + + /** + * @param f File object. + * @param overwrite Overwrite flag. + * @param chunks File content. + * @throws IOException If failed. + */ + private static void createFile(File f, boolean overwrite, @Nullable byte[]... chunks) throws IOException { + OutputStream os = null; + + try { + os = new FileOutputStream(f, overwrite); + + writeFileChunks(os, chunks); + } + finally { + U.closeQuiet(os); + } + } } \ No newline at end of file
