org.apache.hadoop.fs.azure.MockStorageInterface.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.fs.azure.MockStorageInterface.java

Source

/**
 * 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.hadoop.fs.azure;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TimeZone;

import org.apache.commons.httpclient.URIException;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.commons.lang.NotImplementedException;

import com.microsoft.azure.storage.CloudStorageAccount;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.RetryPolicyFactory;
import com.microsoft.azure.storage.StorageCredentials;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageUri;
import com.microsoft.azure.storage.blob.BlobListingDetails;
import com.microsoft.azure.storage.blob.BlobProperties;
import com.microsoft.azure.storage.blob.BlobRequestOptions;
import com.microsoft.azure.storage.blob.CloudBlob;
import com.microsoft.azure.storage.blob.CloudBlobContainer;
import com.microsoft.azure.storage.blob.CloudBlobDirectory;
import com.microsoft.azure.storage.blob.CopyState;
import com.microsoft.azure.storage.blob.ListBlobItem;
import com.microsoft.azure.storage.blob.PageRange;

import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriBuilderException;

/**
 * A mock implementation of the Azure Storage interaction layer for unit tests.
 * Just does in-memory storage.
 */
public class MockStorageInterface extends StorageInterface {
    private InMemoryBlockBlobStore backingStore;
    private final ArrayList<PreExistingContainer> preExistingContainers = new ArrayList<MockStorageInterface.PreExistingContainer>();
    private String baseUriString;

    public InMemoryBlockBlobStore getBackingStore() {
        return backingStore;
    }

    /**
     * Mocks the situation where a container already exists before WASB comes in,
     * i.e. the situation where a user creates a container then mounts WASB on the
     * pre-existing container.
     * 
     * @param uri
     *          The URI of the container.
     * @param metadata
     *          The metadata on the container.
     */
    public void addPreExistingContainer(String uri, HashMap<String, String> metadata) {
        preExistingContainers.add(new PreExistingContainer(uri, metadata));
    }

    @Override
    public void setRetryPolicyFactory(final RetryPolicyFactory retryPolicyFactory) {
    }

    @Override
    public void setTimeoutInMs(int timeoutInMs) {
    }

    @Override
    public void createBlobClient(CloudStorageAccount account) {
        backingStore = new InMemoryBlockBlobStore();
    }

    @Override
    public void createBlobClient(URI baseUri) {
        backingStore = new InMemoryBlockBlobStore();
    }

    @Override
    public void createBlobClient(URI baseUri, StorageCredentials credentials) {
        this.baseUriString = baseUri.toString();
        backingStore = new InMemoryBlockBlobStore();
    }

    @Override
    public StorageCredentials getCredentials() {
        // Not implemented for mock interface.
        return null;
    }

    /**
     * Utility function used to convert a given URI to a decoded string
     * representation sent to the backing store. URIs coming as input
     * to this class will be encoded by the URI class, and we want
     * the underlying storage to store keys in their original UTF-8 form.
     */
    private static String convertUriToDecodedString(URI uri) {
        try {
            String result = URIUtil.decode(uri.toString());
            return result;
        } catch (URIException e) {
            throw new AssertionError("Failed to decode URI: " + uri.toString());
        }
    }

    private static URI convertKeyToEncodedUri(String key) {
        try {
            String encodedKey = URIUtil.encodePath(key);
            URI uri = new URI(encodedKey);
            return uri;
        } catch (URISyntaxException e) {
            throw new AssertionError("Failed to encode key: " + key);
        } catch (URIException e) {
            throw new AssertionError("Failed to encode key: " + key);
        }
    }

    @Override
    public CloudBlobContainerWrapper getContainerReference(String name)
            throws URISyntaxException, StorageException {
        String fullUri;
        try {
            fullUri = baseUriString + "/" + URIUtil.encodePath(name);
        } catch (URIException e) {
            throw new RuntimeException("problem encoding fullUri", e);
        }

        MockCloudBlobContainerWrapper container = new MockCloudBlobContainerWrapper(fullUri, name);
        // Check if we have a pre-existing container with that name, and prime
        // the wrapper with that knowledge if it's found.
        for (PreExistingContainer existing : preExistingContainers) {
            if (fullUri.equalsIgnoreCase(existing.containerUri)) {
                // We have a pre-existing container. Mark the wrapper as created and
                // make sure we use the metadata for it.
                container.created = true;
                backingStore.setContainerMetadata(existing.containerMetadata);
                break;
            }
        }
        return container;
    }

    class MockCloudBlobContainerWrapper extends CloudBlobContainerWrapper {
        private boolean created = false;
        private HashMap<String, String> metadata;
        private final String baseUri;
        private final String name;

        public MockCloudBlobContainerWrapper(String baseUri, String name) {
            this.baseUri = baseUri;
            this.name = name;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean exists(OperationContext opContext) throws StorageException {
            return created;
        }

        @Override
        public void create(OperationContext opContext) throws StorageException {
            created = true;
            backingStore.setContainerMetadata(metadata);
        }

        @Override
        public HashMap<String, String> getMetadata() {
            return metadata;
        }

        @Override
        public void setMetadata(HashMap<String, String> metadata) {
            this.metadata = metadata;
        }

        @Override
        public void downloadAttributes(OperationContext opContext) throws StorageException {
            metadata = backingStore.getContainerMetadata();
        }

        @Override
        public void uploadMetadata(OperationContext opContext) throws StorageException {
            backingStore.setContainerMetadata(metadata);
        }

        @Override
        public CloudBlobDirectoryWrapper getDirectoryReference(String relativePath)
                throws URISyntaxException, StorageException {
            return new MockCloudBlobDirectoryWrapper(new URI(fullUriString(relativePath, true)));
        }

        @Override
        public CloudBlockBlobWrapper getBlockBlobReference(String relativePath)
                throws URISyntaxException, StorageException {
            return new MockCloudBlockBlobWrapper(new URI(fullUriString(relativePath, false)), null, 0);
        }

        @Override
        public CloudPageBlobWrapper getPageBlobReference(String blobAddressUri)
                throws URISyntaxException, StorageException {
            return new MockCloudPageBlobWrapper(new URI(blobAddressUri), null, 0);
        }

        // helper to create full URIs for directory and blob.
        // use withTrailingSlash=true to get a good path for a directory.
        private String fullUriString(String relativePath, boolean withTrailingSlash) {
            String fullUri;

            String baseUri = this.baseUri;
            if (!baseUri.endsWith("/")) {
                baseUri += "/";
            }
            if (withTrailingSlash && !relativePath.equals("") && !relativePath.endsWith("/")) {
                relativePath += "/";
            }

            try {
                fullUri = baseUri + URIUtil.encodePath(relativePath);
            } catch (URIException e) {
                throw new RuntimeException("problem encoding fullUri", e);
            }

            return fullUri;
        }
    }

    private static class PreExistingContainer {
        final String containerUri;
        final HashMap<String, String> containerMetadata;

        public PreExistingContainer(String uri, HashMap<String, String> metadata) {
            this.containerUri = uri;
            this.containerMetadata = metadata;
        }
    }

    class MockCloudBlobDirectoryWrapper extends CloudBlobDirectoryWrapper {
        private URI uri;

        public MockCloudBlobDirectoryWrapper(URI uri) {
            this.uri = uri;
        }

        @Override
        public CloudBlobContainer getContainer() throws URISyntaxException, StorageException {
            return null;
        }

        @Override
        public CloudBlobDirectory getParent() throws URISyntaxException, StorageException {
            return null;
        }

        @Override
        public URI getUri() {
            return uri;
        }

        @Override
        public Iterable<ListBlobItem> listBlobs(String prefix, boolean useFlatBlobListing,
                EnumSet<BlobListingDetails> listingDetails, BlobRequestOptions options, OperationContext opContext)
                throws URISyntaxException, StorageException {
            ArrayList<ListBlobItem> ret = new ArrayList<ListBlobItem>();
            URI searchUri = null;
            if (prefix == null) {
                searchUri = uri;
            } else {
                try {
                    searchUri = UriBuilder.fromUri(uri).path(prefix).build();
                } catch (UriBuilderException e) {
                    throw new AssertionError("Failed to encode path: " + prefix);
                }
            }

            String fullPrefix = convertUriToDecodedString(searchUri);
            boolean includeMetadata = listingDetails.contains(BlobListingDetails.METADATA);
            HashSet<String> addedDirectories = new HashSet<String>();
            for (InMemoryBlockBlobStore.ListBlobEntry current : backingStore.listBlobs(fullPrefix,
                    includeMetadata)) {
                int indexOfSlash = current.getKey().indexOf('/', fullPrefix.length());
                if (useFlatBlobListing || indexOfSlash < 0) {
                    if (current.isPageBlob()) {
                        ret.add(new MockCloudPageBlobWrapper(convertKeyToEncodedUri(current.getKey()),
                                current.getMetadata(), current.getContentLength()));
                    } else {
                        ret.add(new MockCloudBlockBlobWrapper(convertKeyToEncodedUri(current.getKey()),
                                current.getMetadata(), current.getContentLength()));
                    }
                } else {
                    String directoryName = current.getKey().substring(0, indexOfSlash);
                    if (!addedDirectories.contains(directoryName)) {
                        addedDirectories.add(current.getKey());
                        ret.add(new MockCloudBlobDirectoryWrapper(new URI(directoryName + "/")));
                    }
                }
            }
            return ret;
        }

        @Override
        public StorageUri getStorageUri() {
            throw new NotImplementedException();
        }
    }

    abstract class MockCloudBlobWrapper implements CloudBlobWrapper {
        protected final URI uri;
        protected HashMap<String, String> metadata = new HashMap<String, String>();
        protected BlobProperties properties;

        protected MockCloudBlobWrapper(URI uri, HashMap<String, String> metadata, int length) {
            this.uri = uri;
            this.metadata = metadata;
            this.properties = new BlobProperties();

            this.properties = updateLastModifed(this.properties);
            this.properties = updateLength(this.properties, length);
        }

        protected BlobProperties updateLastModifed(BlobProperties properties) {
            try {
                Method setLastModified = properties.getClass().getDeclaredMethod("setLastModified", Date.class);
                setLastModified.setAccessible(true);
                setLastModified.invoke(this.properties,
                        Calendar.getInstance(TimeZone.getTimeZone("UTC")).getTime());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return properties;
        }

        protected BlobProperties updateLength(BlobProperties properties, int length) {
            try {
                Method setLength = properties.getClass().getDeclaredMethod("setLength", long.class);
                setLength.setAccessible(true);
                setLength.invoke(this.properties, length);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return properties;
        }

        protected void refreshProperties(boolean getMetadata) {
            if (backingStore.exists(convertUriToDecodedString(uri))) {
                byte[] content = backingStore.getContent(convertUriToDecodedString(uri));
                properties = new BlobProperties();
                this.properties = updateLastModifed(this.properties);
                this.properties = updateLength(this.properties, content.length);
                if (getMetadata) {
                    metadata = backingStore.getMetadata(convertUriToDecodedString(uri));
                }
            }
        }

        @Override
        public CloudBlobContainer getContainer() throws URISyntaxException, StorageException {
            return null;
        }

        @Override
        public CloudBlobDirectory getParent() throws URISyntaxException, StorageException {
            return null;
        }

        @Override
        public URI getUri() {
            return uri;
        }

        @Override
        public HashMap<String, String> getMetadata() {
            return metadata;
        }

        @Override
        public void setMetadata(HashMap<String, String> metadata) {
            this.metadata = metadata;
        }

        @Override
        public void startCopyFromBlob(CloudBlobWrapper sourceBlob, BlobRequestOptions options,
                OperationContext opContext) throws StorageException, URISyntaxException {
            backingStore.copy(convertUriToDecodedString(sourceBlob.getUri()), convertUriToDecodedString(uri));
            //TODO: set the backingStore.properties.CopyState and
            //      update azureNativeFileSystemStore.waitForCopyToComplete
        }

        @Override
        public CopyState getCopyState() {
            return this.properties.getCopyState();
        }

        @Override
        public void delete(OperationContext opContext, SelfRenewingLease lease) throws StorageException {
            backingStore.delete(convertUriToDecodedString(uri));
        }

        @Override
        public boolean exists(OperationContext opContext) throws StorageException {
            return backingStore.exists(convertUriToDecodedString(uri));
        }

        @Override
        public void downloadAttributes(OperationContext opContext) throws StorageException {
            refreshProperties(true);
        }

        @Override
        public BlobProperties getProperties() {
            return properties;
        }

        @Override
        public InputStream openInputStream(BlobRequestOptions options, OperationContext opContext)
                throws StorageException {
            return new ByteArrayInputStream(backingStore.getContent(convertUriToDecodedString(uri)));
        }

        @Override
        public void uploadMetadata(OperationContext opContext) throws StorageException {
            backingStore.setMetadata(convertUriToDecodedString(uri), metadata);
        }

        @Override
        public void downloadRange(long offset, long length, OutputStream os, BlobRequestOptions options,
                OperationContext opContext) throws StorageException {
            throw new NotImplementedException();
        }
    }

    class MockCloudBlockBlobWrapper extends MockCloudBlobWrapper implements CloudBlockBlobWrapper {
        public MockCloudBlockBlobWrapper(URI uri, HashMap<String, String> metadata, int length) {
            super(uri, metadata, length);
        }

        @Override
        public OutputStream openOutputStream(BlobRequestOptions options, OperationContext opContext)
                throws StorageException {
            return backingStore.uploadBlockBlob(convertUriToDecodedString(uri), metadata);
        }

        @Override
        public void setStreamMinimumReadSizeInBytes(int minimumReadSizeBytes) {
        }

        @Override
        public void setWriteBlockSizeInBytes(int writeBlockSizeBytes) {
        }

        @Override
        public StorageUri getStorageUri() {
            return null;
        }

        @Override
        public void uploadProperties(OperationContext context, SelfRenewingLease lease) {
        }

        @Override
        public SelfRenewingLease acquireLease() {
            return null;
        }

        @Override
        public CloudBlob getBlob() {
            return null;
        }
    }

    class MockCloudPageBlobWrapper extends MockCloudBlobWrapper implements CloudPageBlobWrapper {
        public MockCloudPageBlobWrapper(URI uri, HashMap<String, String> metadata, int length) {
            super(uri, metadata, length);
        }

        @Override
        public void create(long length, BlobRequestOptions options, OperationContext opContext)
                throws StorageException {
            throw new NotImplementedException();
        }

        @Override
        public void uploadPages(InputStream sourceStream, long offset, long length, BlobRequestOptions options,
                OperationContext opContext) throws StorageException, IOException {
            throw new NotImplementedException();
        }

        @Override
        public ArrayList<PageRange> downloadPageRanges(BlobRequestOptions options, OperationContext opContext)
                throws StorageException {
            throw new NotImplementedException();
        }

        @Override
        public void setStreamMinimumReadSizeInBytes(int minimumReadSize) {
        }

        @Override
        public void setWriteBlockSizeInBytes(int writeBlockSizeInBytes) {
        }

        @Override
        public StorageUri getStorageUri() {
            throw new NotImplementedException();
        }

        @Override
        public void uploadProperties(OperationContext opContext, SelfRenewingLease lease) throws StorageException {
        }

        @Override
        public SelfRenewingLease acquireLease() {
            return null;
        }

        @Override
        public CloudBlob getBlob() {
            return null;
        }
    }
}