/*
* Copyright 2014 EMC Corporation. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0.txt
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.emc.atmos.api.test;
import com.emc.atmos.AtmosException;
import com.emc.atmos.StickyThreadAlgorithm;
import com.emc.atmos.api.*;
import com.emc.atmos.api.bean.*;
import com.emc.atmos.api.jersey.AtmosApiBasicClient;
import com.emc.atmos.api.jersey.AtmosApiClient;
import com.emc.atmos.api.multipart.MultipartEntity;
import com.emc.atmos.api.request.*;
import com.emc.atmos.util.AtmosClientFactory;
import com.emc.atmos.util.RandomInputStream;
import com.emc.atmos.util.ReorderedFormDataContentDisposition;
import com.emc.test.util.Concurrent;
import com.emc.test.util.ConcurrentJunitRunner;
import com.emc.util.StreamUtil;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.ClientRequest;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.filter.ClientFilter;
import com.sun.jersey.core.header.FormDataContentDisposition;
import com.sun.jersey.multipart.BodyPart;
import com.sun.jersey.multipart.FormDataMultiPart;
import org.apache.commons.codec.binary.Base64;
import org.apache.log4j.Logger;
import org.junit.*;
import org.junit.runner.RunWith;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.core.MediaType;
import java.io.*;
import java.net.URI;
import java.net.URL;
import java.net.URLEncoder;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@RunWith(ConcurrentJunitRunner.class)
@Concurrent
public class AtmosApiClientTest {
public static Logger l4j = Logger.getLogger( AtmosApiClientTest.class );
/**
* Use this as a prefix for namespace object paths and you won't have to clean up after yourself.
* This also keeps all test objects under one folder, which is easy to delete should something go awry.
*/
protected static final String TEST_DIR_PREFIX = "/test_" + AtmosApiClientTest.class.getSimpleName();
protected AtmosConfig config;
protected AtmosApi api;
protected boolean isVipr = false;
protected List<ObjectIdentifier> cleanup = Collections.synchronizedList( new ArrayList<ObjectIdentifier>() );
protected List<ObjectPath> cleanupDirs = Collections.synchronizedList( new ArrayList<ObjectPath>() );
public AtmosApiClientTest() throws Exception {
config = AtmosClientFactory.getAtmosConfig();
Assume.assumeTrue("Could not load Atmos configuration", config != null);
config.setDisableSslValidation( false );
config.setEnableExpect100Continue( false );
config.setEnableRetry( false );
config.setLoadBalancingAlgorithm( new StickyThreadAlgorithm() );
api = new AtmosApiClient( config );
isVipr = AtmosClientFactory.atmosIsVipr();
}
@After
public void tearDown() {
for ( ObjectIdentifier cleanItem : cleanup ) {
try {
api.delete( cleanItem );
} catch ( Throwable t ) {
System.out.println( "Failed to delete " + cleanItem + ": " + t.getMessage() );
}
}
try { // if test directories exists, recursively delete them
for ( ObjectPath testDir : cleanupDirs ) {
deleteRecursively( testDir );
}
} catch ( AtmosException e ) {
if ( e.getHttpCode() != 404 ) {
l4j.warn( "Could not delete test dir: ", e );
}
}
if(!isVipr) {
try {
ListAccessTokensResponse response = this.api.listAccessTokens( new ListAccessTokensRequest() );
if ( response.getTokens() != null ) {
for ( AccessToken token : response.getTokens() ) {
this.api.deleteAccessToken( token.getId() );
}
}
} catch ( Exception e ) {
System.out.println( "Failed to delete access tokens: " + e.getMessage() );
}
}
}
protected void deleteRecursively( ObjectPath path ) {
if ( path.isDirectory() ) {
ListDirectoryRequest request = new ListDirectoryRequest().path( path );
do {
for ( DirectoryEntry entry : this.api.listDirectory( request ).getEntries() ) {
deleteRecursively( new ObjectPath( path, entry ) );
}
} while ( request.getToken() != null );
}
this.api.delete( path );
}
protected ObjectPath createTestDir( String name ) {
if (!name.endsWith("/")) name = name + "/";
ObjectPath path = new ObjectPath( TEST_DIR_PREFIX + "_" + name );
this.api.createDirectory( path );
cleanupDirs.add( path );
return path;
}
//
// TESTS START HERE
//
@Test
public void testUtf8JavaEncoding() throws Exception {
String oneByteCharacters = "Hello";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String twoByteEscaped = "%D0%90%D0%91%D0%92%D0%93";
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
String fourByteEscaped = "%F0%A0%9C%8E%F0%A0%9C%B1%F0%A0%9D%B9%F0%A0%B1%93";
Assert.assertEquals( "2-byte characters failed",
URLEncoder.encode( twoByteCharacters, "UTF-8" ),
twoByteEscaped );
Assert.assertEquals( "4-byte characters failed",
URLEncoder.encode( fourByteCharacters, "UTF-8" ),
fourByteEscaped );
Assert.assertEquals( "2-byte/4-byte mix failed",
URLEncoder.encode( twoByteCharacters + fourByteCharacters, "UTF-8" ),
twoByteEscaped + fourByteEscaped );
Assert.assertEquals( "1-byte/2-byte mix failed",
URLEncoder.encode( oneByteCharacters + twoByteCharacters, "UTF-8" ),
oneByteCharacters + twoByteEscaped );
Assert.assertEquals( "1-4 byte mix failed",
URLEncoder.encode( oneByteCharacters + twoByteCharacters + fourByteCharacters, "UTF-8" ),
oneByteCharacters + twoByteEscaped + fourByteEscaped );
}
/**
* Test creating one empty object. No metadata, no content.
*/
@Test
public void testCreateEmptyObject() throws Exception {
ObjectId id = this.api.createObject( null, null );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "", content );
}
/**
* Test creating one empty object on a path. No metadata, no content.
*/
@Test
public void testCreateEmptyObjectOnPath() throws Exception {
ObjectPath op = new ObjectPath( "/" + rand8char() );
ObjectId id = this.api.createObject( op, null, null );
cleanup.add( op );
l4j.debug( "Path: " + op + " ID: " + id );
Assert.assertNotNull( id );
// Read back the content
String content = new String( this.api.readObject( op, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "", content );
content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong when reading by id", "", content );
}
/**
* Tests using some extended characters when creating on a path. This particular test
* uses one cryllic, one accented, and one japanese character.
*/
@Test
public void testUnicodePath() throws Exception {
String dirName = rand8char();
ObjectPath path = new ObjectPath( "/" + dirName + "/бöシ.txt" );
ObjectId id = this.api.createObject( path, null, null );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
ObjectPath parent = new ObjectPath( "/" + dirName + "/" );
ListDirectoryResponse response = this.api.listDirectory( new ListDirectoryRequest().path( parent ) );
boolean found = false;
for ( DirectoryEntry ent : response.getEntries() ) {
if ( new ObjectPath( parent, ent.getFilename() ).equals( path ) ) {
found = true;
}
}
Assert.assertTrue( "Did not find unicode file in dir", found );
// Check read
this.api.readObject( path, null, byte[].class );
}
/**
* Tests using some extra characters that might break URIs
*/
@Test
public void testExtraPath() throws Exception {
ObjectPath path = new ObjectPath( "/" + rand8char() + "/a+=- _!#$%^&*(),.z.txt" );
//ObjectPath path = new ObjectPath("/zimbramailbox/c8b4/511a-63c4-4ac9-8ff7+1c578de044be/stage/3r0sFrgUgL2ApCSkl3pobSX9D+k-1");
byte[] data = "Hello World".getBytes( "UTF-8" );
InputStream in = new ByteArrayInputStream( data );
CreateObjectRequest request = new CreateObjectRequest().identifier( path ).content( in )
.contentLength( data.length );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
}
@Test
public void testUtf8Path() throws Exception {
String oneByteCharacters = "Hello! ,";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
String crazyName = oneByteCharacters + twoByteCharacters + fourByteCharacters;
byte[] content = "Crazy name creation test.".getBytes( "UTF-8" );
ObjectPath parent = createTestDir( "Utf8Path" );
ObjectPath path = new ObjectPath( parent, crazyName );
// create crazy-name object
this.api.createObject( path, content, "text/plain" );
cleanup.add( path );
// verify name in directory list
boolean found = false;
ListDirectoryRequest request = new ListDirectoryRequest().path( parent );
for ( DirectoryEntry entry : this.api.listDirectory( request ).getEntries() ) {
if ( new ObjectPath( parent, entry.getFilename() ).equals( path ) ) {
found = true;
break;
}
}
Assert.assertTrue( "crazyName not found in directory listing", found );
// verify content
Assert.assertTrue( "content does not match",
Arrays.equals( content, this.api.readObject( path, null, byte[].class ) ) );
}
@Test
public void testUtf8Content() throws Exception {
String oneByteCharacters = "Hello! ,";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
byte[] content = (oneByteCharacters + twoByteCharacters + fourByteCharacters).getBytes( "UTF-8" );
// create object with multi-byte UTF-8 content
ObjectId oid = api.createObject( content, "text/plain" );
cleanup.add( oid );
byte[] readContent = this.api.readObject( oid, null, byte[].class );
// verify content
Assert.assertTrue( "content does not match", Arrays.equals( content, readContent ) );
}
/**
* Test creating an object with content but without metadata
*/
@Test
public void testCreateObjectWithContent() throws Exception {
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
}
@Test
public void testCreateObjectWithSegment() throws Exception {
byte[] content = "hello".getBytes( "UTF-8" );
ObjectId id = api.createObject( new BufferSegment( content, 0, content.length ), null );
cleanup.add( id );
// Read back the content
String result = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", result );
}
@Test
public void testCreateObjectWithContentStream() throws Exception {
InputStream in = new ByteArrayInputStream( "hello".getBytes( "UTF-8" ) );
CreateObjectRequest request = new CreateObjectRequest().content( in ).contentLength( 5 )
.contentType( "text/plain" );
ObjectId id = this.api.createObject( request ).getObjectId();
in.close();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
}
@Test
public void testCreateObjectWithContentStreamOnPath() throws Exception {
ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" );
InputStream in = new ByteArrayInputStream( "hello".getBytes( "UTF-8" ) );
CreateObjectRequest request = new CreateObjectRequest();
request.identifier( op ).content( in ).contentLength( 5 ).contentType( "text/plain" );
ObjectId id = this.api.createObject( request ).getObjectId();
in.close();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
}
/**
* Test creating an object with metadata but no content.
*/
@Test
public void testCreateObjectWithMetadataOnPath() {
ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" );
CreateObjectRequest request = new CreateObjectRequest().identifier( op );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
//this.esu.updateObject( op, null, mlist, null, null, null );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( op );
// Read and validate the metadata
Map<String, Metadata> meta = this.api.getUserMetadata( op );
Assert.assertNotNull( "value of 'listable' missing", meta.get( "listable" ) );
Assert.assertNotNull( "value of 'listable2' missing", meta.get( "listable2" ) );
Assert.assertNotNull( "value of 'unlistable' missing", meta.get( "unlistable" ) );
Assert.assertNotNull( "value of 'unlistable2' missing", meta.get( "unlistable2" ) );
Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() );
Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.get( "listable2" ).getValue() );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() );
Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", meta.get( "unlistable2" ).getValue() );
// Check listable flags
Assert.assertEquals( "'listable' is not listable", true, meta.get( "listable" ).isListable() );
Assert.assertEquals( "'listable2' is not listable", true, meta.get( "listable2" ).isListable() );
Assert.assertEquals( "'unlistable' is listable", false, meta.get( "unlistable" ).isListable() );
Assert.assertEquals( "'unlistable2' is listable", false, meta.get( "unlistable2" ).isListable() );
}
/**
* Test creating an object with metadata but no content.
*/
@Test
public void testCreateObjectWithMetadata() {
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
Metadata listable3 = new Metadata( "listable3", null, true );
Metadata quotes = new Metadata( "ST_modalities", "\\US\\", false );
//Metadata withCommas = new Metadata( "withcommas", "I, Robot", false );
//Metadata withEquals = new Metadata( "withequals", "name=value", false );
request.userMetadata( listable, unlistable, listable2, unlistable2, listable3, quotes );
//request.addUserMetadata( withCommas );
//request.addUserMetadata( withEquals );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read and validate the metadata
Map<String, Metadata> meta = this.api.getUserMetadata( id );
Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() );
Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.get( "listable2" ).getValue() );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() );
Assert.assertEquals( "value of 'unlistable2' wrong",
"bar2 bar2",
meta.get( "unlistable2" ).getValue() );
Assert.assertNotNull( "listable3 missing", meta.get( "listable3" ) );
Assert.assertTrue( "Value of listable3 should be empty",
meta.get( "listable3" ).getValue() == null
|| meta.get( "listable3" ).getValue().length() == 0 );
//Assert.assertEquals( "Value of withcommas wrong", "I, Robot", meta.get( "withcommas" ).getValue() );
//Assert.assertEquals( "Value of withequals wrong", "name=value", meta.get( "withequals" ).getValue() );
// Check listable flags
Assert.assertEquals( "'listable' is not listable", true, meta.get( "listable" ).isListable() );
Assert.assertEquals( "'listable2' is not listable", true, meta.get( "listable2" ).isListable() );
Assert.assertEquals( "'listable3' is not listable", true, meta.get( "listable3" ).isListable() );
Assert.assertEquals( "'unlistable' is listable", false, meta.get( "unlistable" ).isListable() );
Assert.assertEquals( "'unlistable2' is listable", false, meta.get( "unlistable2" ).isListable() );
}
/**
* Test creating an object with metadata but no content.
*/
@Test
public void testMetadataNormalizeSpace() {
CreateObjectRequest request = new CreateObjectRequest();
Metadata unlistable = new Metadata( "unlistable", "bar bar bar bar", false );
Metadata leadingSpacesOdd = new Metadata( "leadingodd", " spaces", false );
Metadata trailingSpacesOdd = new Metadata( "trailingodd", "spaces ", false );
Metadata leadingSpacesEven = new Metadata( "leadingeven", " SPACES", false );
Metadata trailingSpacesEven = new Metadata( "trailingeven", "spaces ", false );
request.userMetadata( unlistable, leadingSpacesOdd, trailingSpacesOdd, leadingSpacesEven, trailingSpacesEven );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read and validate the metadata
Map<String, Metadata> meta = this.api.getUserMetadata( id );
Assert.assertEquals( "value of 'unlistable' wrong",
"bar bar bar bar",
meta.get( "unlistable" ).getValue() );
// Check listable flags
Assert.assertEquals( "'unlistable' is listable", false, meta.get( "unlistable" ).isListable() );
}
/**
* Test reading an object's content
*/
@Test
public void testReadObject() throws Exception {
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
// Read back only 2 bytes
Range range = new Range( 1, 2 );
content = new String( this.api.readObject( id, range, byte[].class ), "UTF-8" );
Assert.assertEquals( "partial object content wrong", "el", content );
}
@Test
public void testResponseProperties() throws Exception {
// Subtract a second since the HTTP dates only have 1s precision.
Date now = new Date();
CreateObjectRequest request = new CreateObjectRequest().content( "hello".getBytes( "UTF-8" ) )
.contentType( "text/plain" );
CreateObjectResponse response = this.api.createObject( request );
Assert.assertNotNull( "null ID returned", response.getObjectId() );
Assert.assertEquals( "location wrong", "/rest/objects/" + response.getObjectId(), response.getLocation() );
cleanup.add( response.getObjectId() );
// Read back the content
ReadObjectResponse<String> readResponse = api.readObject( new ReadObjectRequest().identifier( response.getObjectId() ),
String.class );
Assert.assertEquals( "object content wrong", "hello", readResponse.getObject() );
Assert.assertEquals( "HTTP status wrong", 200, readResponse.getHttpStatus() );
Assert.assertEquals( "HTTP message wrong", "OK", readResponse.getHttpMessage() );
Assert.assertFalse( "HTTP headers empty", readResponse.getHeaders().isEmpty() );
Assert.assertTrue( "HTTP content-type wrong",
readResponse.getContentType().matches( "text/plain(; charset=UTF-8)?" ) );
Assert.assertEquals( "HTTP content-length wrong", 5, readResponse.getContentLength() );
Assert.assertTrue( "HTTP response date wrong", Math.abs( response.getDate().getTime() - now.getTime() ) < (1000 * 60 * 5) );
// apparently last-modified isn't included in GET requests
// Assert.assertTrue( "HTTP last modified date wrong", readResponse.getLastModified().after( now ) );
}
/**
* Test reading an ACL back
*/
@Test
public void testReadAcl() {
// Create an object with an ACL
Acl acl = new Acl();
acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL );
acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ );
ObjectId id = this.api.createObject( new CreateObjectRequest().acl( acl ) ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the ACL and make sure it matches
Acl newacl = this.api.getAcl( id );
l4j.info( "Comparing " + newacl + " with " + acl );
Assert.assertEquals( "ACLs don't match", acl, newacl );
}
/**
* Inside an ACL, you use the UID only, not SubtenantID/UID
*/
private String stripUid( String uid ) {
int slash = uid.indexOf( '/' );
if ( slash != -1 ) {
return uid.substring( slash + 1 );
} else {
return uid;
}
}
@Test
public void testReadAclByPath() {
ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" );
// Create an object with an ACL
Acl acl = new Acl();
acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL );
acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ );
ObjectId id = this.api.createObject( new CreateObjectRequest().identifier( op ).acl( acl ) ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( op );
// Read back the ACL and make sure it matches
Acl newacl = this.api.getAcl( op );
l4j.info( "Comparing " + newacl + " with " + acl );
Assert.assertEquals( "ACLs don't match", acl, newacl );
}
/**
* Test reading back user metadata
*/
@Test
public void testGetUserMetadata() {
// Create an object with user metadata
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read only part of the metadata
Map<String, Metadata> meta = this.api.getUserMetadata( id, "listable", "unlistable" );
Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() );
Assert.assertNull( "value of 'listable2' should not have been returned", meta.get( "listable2" ) );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() );
Assert.assertNull( "value of 'unlistable2' should not have been returned", meta.get( "unlistable2" ) );
}
/**
* Test deleting user metadata
*/
@Test
public void testDeleteUserMetadata() {
// Create an object with metadata
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Delete a couple of the metadata entries
this.api.deleteUserMetadata( id, "listable2", "unlistable2" );
// Read back the metadata for the object and ensure the deleted
// entries don't exist
Map<String, Metadata> meta = this.api.getUserMetadata( id );
Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() );
Assert.assertNull( "value of 'listable2' should not have been returned", meta.get( "listable2" ) );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() );
Assert.assertNull( "value of 'unlistable2' should not have been returned", meta.get( "unlistable2" ) );
}
/**
* Test creating object versions
*/
@Test
public void testVersionObject() throws Exception {
Assume.assumeFalse(isVipr);
// Create an object
String content = "Version Test";
CreateObjectRequest request = new CreateObjectRequest().content( content );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Version the object
ObjectId vid = this.api.createVersion( id );
cleanup.add( vid );
Assert.assertNotNull( "null version ID returned", vid );
Assert.assertFalse( "Version ID shoudn't be same as original ID", id.equals( vid ) );
// Fetch the version and read its data
Assert.assertEquals( "Version content wrong", content, this.api.readObject( vid, null, String.class ) );
Map<String, Metadata> meta = this.api.getUserMetadata( vid );
Assert.assertEquals( "value of 'listable' wrong", "foo", meta.get( "listable" ).getValue() );
Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", meta.get( "listable2" ).getValue() );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() );
Assert.assertEquals( "value of 'unlistable2' wrong",
"bar2 bar2",
meta.get( "unlistable2" ).getValue() );
}
/**
* Test listing the versions of an object
*/
@Test
public void testListVersions() {
Assume.assumeFalse(isVipr);
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
Assert.assertNotNull( "null ID returned", id );
// Version the object
ObjectId vid1 = this.api.createVersion( id );
cleanup.add( vid1 );
Assert.assertNotNull( "null version ID returned", vid1 );
ObjectId vid2 = this.api.createVersion( id );
cleanup.add( vid2 );
Assert.assertNotNull( "null version ID returned", vid2 );
// List the versions and ensure their IDs are correct
ListVersionsResponse response = this.api.listVersions( new ListVersionsRequest().objectId( id ) );
List<ObjectVersion> versions = response.getVersions();
Assert.assertEquals( "Wrong number of versions returned", 2, versions.size() );
Assert.assertTrue( "Version number less than zero", versions.get( 0 ).getVersionNumber() >= 0 );
Assert.assertNotNull( "Version itime is null", versions.get( 0 ).getItime() );
Assert.assertTrue( "Version number less than zero", versions.get( 1 ).getVersionNumber() >= 0 );
Assert.assertNotNull( "Version itime is null", versions.get( 1 ).getItime() );
List<ObjectId> versionIds = response.getVersionIds();
Assert.assertEquals( "Wrong number of versions returned", 2, versionIds.size() );
Assert.assertTrue( "version 1 not found in version list", versionIds.contains( vid1 ) );
Assert.assertTrue( "version 2 not found in version list", versionIds.contains( vid2 ) );
}
/**
* Test listing the versions of an object
*/
@Test
public void testListVersionsLong() {
Assume.assumeFalse(isVipr);
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
Assert.assertNotNull( "null ID returned", id );
// Version the object
ObjectId vid1 = this.api.createVersion( id );
ObjectVersion v1 = new ObjectVersion( 0, vid1, null );
cleanup.add( vid1 );
Assert.assertNotNull( "null version ID returned", vid1 );
ObjectId vid2 = this.api.createVersion( id );
cleanup.add( vid2 );
ObjectVersion v2 = new ObjectVersion( 1, vid2, null );
Assert.assertNotNull( "null version ID returned", vid2 );
// List the versions and ensure their IDs are correct
ListVersionsRequest vRequest = new ListVersionsRequest();
vRequest.objectId( id ).setLimit( 1 );
List<ObjectVersion> versions = new ArrayList<ObjectVersion>();
do {
ListVersionsResponse response = this.api.listVersions( vRequest );
if ( response.getVersions() != null ) versions.addAll( response.getVersions() );
} while ( vRequest.getToken() != null );
Assert.assertEquals( "Wrong number of versions returned", 2, versions.size() );
Assert.assertTrue( "version 1 not found in version list", versions.contains( v1 ) );
Assert.assertTrue( "version 2 not found in version list", versions.contains( v2 ) );
for ( ObjectVersion v : versions ) {
Assert.assertNotNull( "oid null in version", v.getVersionId() );
Assert.assertTrue( "Invalid version number in version", v.getVersionNumber() > -1 );
Assert.assertNotNull( "itime null in version", v.getItime() );
}
}
/**
* Test listing the versions of an object
*/
@Test
public void testDeleteVersion() {
Assume.assumeFalse(isVipr);
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
Assert.assertNotNull( "null ID returned", id );
// Version the object
ObjectId vid1 = this.api.createVersion( id );
Assert.assertNotNull( "null version ID returned", vid1 );
ObjectId vid2 = this.api.createVersion( id );
cleanup.add( vid2 );
Assert.assertNotNull( "null version ID returned", vid2 );
// List the versions and ensure their IDs are correct
List<ObjectId> versions = this.api.listVersions( new ListVersionsRequest().objectId( id ) ).getVersionIds();
Assert.assertEquals( "Wrong number of versions returned", 2, versions.size() );
Assert.assertTrue( "version 1 not found in version list", versions.contains( vid1 ) );
Assert.assertTrue( "version 2 not found in version list", versions.contains( vid2 ) );
// Delete a version
this.api.deleteVersion( vid1 );
versions = this.api.listVersions( new ListVersionsRequest().objectId( id ) ).getVersionIds();
Assert.assertEquals( "Wrong number of versions returned", 1, versions.size() );
Assert.assertFalse( "version 1 found in version list", versions.contains( vid1 ) );
Assert.assertTrue( "version 2 not found in version list", versions.contains( vid2 ) );
}
@Test
public void testRestoreVersion() throws IOException {
Assume.assumeFalse(isVipr);
ObjectId id = this.api.createObject( "Base Version Content".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Version the object
ObjectId vId = this.api.createVersion( id );
// Update the object content
this.api.updateObject( id, "Child Version Content -- You should never see me".getBytes( "UTF-8" ) );
// Restore the original version
this.api.restoreVersion( id, vId );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "Base Version Content", content );
}
/**
* Test listing the system metadata on an object
*/
@Test
public void testGetSystemMetadata() {
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read only part of the metadata
Map<String, Metadata> meta = this.api.getSystemMetadata( id, "atime", "ctime" );
Assert.assertNotNull( "value of 'atime' missing", meta.get( "atime" ) );
Assert.assertNull( "value of 'mtime' should not have been returned", meta.get( "mtime" ) );
Assert.assertNotNull( "value of 'ctime' missing", meta.get( "ctime" ) );
Assert.assertNull( "value of 'gid' should not have been returned", meta.get( "gid" ) );
Assert.assertNull( "value of 'listable' should not have been returned", meta.get( "listable" ) );
}
@Test
public void testObjectExists() {
ObjectId oid = api.createObject("Hello exists!", "text/plain");
Assert.assertTrue("object exists!", api.objectExists(oid));
api.delete(oid);
Assert.assertFalse("object does not exist!", api.objectExists(oid));
}
/**
* Test listing objects by a tag that doesn't exist
*/
@Test
public void testListObjectsNoExist() {
ListObjectsRequest request = new ListObjectsRequest().metadataName( "this_tag_should_not_exist" );
List<ObjectEntry> objects = this.api.listObjects( request ).getEntries();
Assert.assertNotNull( "object list should be not null", objects );
Assert.assertEquals( "No objects should be returned", 0, objects.size() );
}
/**
* Test listing objects by a tag
*/
@Test
public void testListObjects() {
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// List the objects. Make sure the one we created is in the list
ListObjectsRequest lRequest = new ListObjectsRequest().metadataName( "listable" );
List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries();
ObjectEntry toFind = new ObjectEntry();
toFind.setObjectId( id );
Assert.assertTrue( "No objects returned", objects.size() > 0 );
Assert.assertTrue( "object not found in list", objects.contains( toFind ) );
}
/**
* Test listing objects by a tag
*/
@Test
public void testListObjectsWithMetadata() {
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// List the objects. Make sure the one we created is in the list
ListObjectsRequest lRequest = new ListObjectsRequest().metadataName( "listable" ).includeMetadata( true );
List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries();
Assert.assertTrue( "No objects returned", objects.size() > 0 );
// Find the item.
boolean found = false;
for ( ObjectEntry or : objects ) {
if ( or.getObjectId().equals( id ) ) {
found = true;
// check metadata
Assert.assertEquals( "Wrong value on metadata",
or.getUserMetadataMap().get( "listable" ).getValue(), "foo" );
}
}
Assert.assertTrue( "object not found in list", found );
}
/**
* Test listing objects by a tag, with only some of the metadata
*/
@Test
public void testListObjectsWithSomeMetadata() {
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// List the objects. Make sure the one we created is in the list
ListObjectsRequest lRequest = new ListObjectsRequest();
lRequest.metadataName( "listable" ).includeMetadata( true )
.userMetadataNames( "listable" );
List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries();
Assert.assertTrue( "No objects returned", objects.size() > 0 );
// Find the item.
boolean found = false;
for ( ObjectEntry or : objects ) {
if ( or.getObjectId().equals( id ) ) {
found = true;
// check metadata
Assert.assertEquals( "Wrong value on metadata",
or.getUserMetadataMap().get( "listable" ).getValue(), "foo" );
// Other metadata should not be present
Assert.assertNull( "unlistable should be missing",
or.getUserMetadataMap().get( "unlistable" ) );
}
}
Assert.assertTrue( "object not found in list", found );
}
/**
* Test listing objects by a tag, paging the results
*/
@Test
public void testListObjectsPaged() {
// Create two objects.
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
request.userMetadata( listable );
ObjectId id1 = this.api.createObject( request ).getObjectId();
ObjectId id2 = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id1 );
Assert.assertNotNull( "null ID returned", id2 );
cleanup.add( id1 );
cleanup.add( id2 );
// List the objects. Make sure the one we created is in the list
ListObjectsRequest lRequest = new ListObjectsRequest().metadataName( "listable" );
lRequest.setIncludeMetadata( true );
lRequest.setLimit( 1 );
List<ObjectEntry> objects = this.api.listObjects( lRequest ).getEntries();
Assert.assertTrue( "No objects returned", objects.size() > 0 );
Assert.assertNotNull( "Token should be present", lRequest.getToken() );
l4j.debug( "listObjectsPaged, Token: " + lRequest.getToken() );
while ( lRequest.getToken() != null ) {
// Subsequent pages
objects.addAll( this.api.listObjects( lRequest ).getEntries() );
l4j.debug( "listObjectsPaged, Token: " + lRequest.getToken() );
}
// Ensure our IDs exist
ObjectEntry toFind1 = new ObjectEntry(), toFind2 = new ObjectEntry();
toFind1.setObjectId( id1 );
toFind2.setObjectId( id2 );
Assert.assertTrue( "First object not found", objects.contains( toFind1 ) );
Assert.assertTrue( "Second object not found", objects.contains( toFind2 ) );
}
/**
* Test fetching listable tags
*/
@Test
public void testGetListableTags() {
// Create an object
ObjectId id = this.api.createObject( null, null );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
UpdateObjectRequest request = new UpdateObjectRequest().identifier( id );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
this.api.updateObject( request );
// List tags. Ensure our object's tags are in the list.
Set<String> tags = this.api.listMetadata( null );
Assert.assertTrue( "listable tag not returned", tags.contains( "listable" ) );
Assert.assertTrue( "list/able/2 root tag not returned", tags.contains( "list" ) );
Assert.assertFalse( "list/able/not tag returned", tags.contains( "list/able/not" ) );
// List child tags
tags = this.api.listMetadata( "list/able" );
Assert.assertFalse( "non-child returned", tags.contains( "listable" ) );
Assert.assertTrue( "list/able/2 tag not returned", tags.contains( "2" ) );
Assert.assertFalse( "list/able/not tag returned", tags.contains( "not" ) );
}
/**
* Test listing the user metadata tags on an object
*/
@Test
public void testListUserMetadataTags() {
// Create an object
CreateObjectRequest request = new CreateObjectRequest();
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// List tags
Map<String, Boolean> metaNames = this.api.getUserMetadataNames( id );
Assert.assertTrue( "listable tag not returned", metaNames.containsKey( "listable" ) );
Assert.assertTrue( "list/able/2 tag not returned", metaNames.containsKey( "list/able/2" ) );
Assert.assertTrue( "unlistable tag not returned", metaNames.containsKey( "unlistable" ) );
Assert.assertTrue( "list/able/not tag not returned", metaNames.containsKey( "list/able/not" ) );
Assert.assertFalse( "unknown tag returned", metaNames.containsKey( "unknowntag" ) );
// Check listable flag
Assert.assertEquals( "'listable' is not listable", true, metaNames.get( "listable" ) );
Assert.assertEquals( "'list/able/2' is not listable", true, metaNames.get( "list/able/2" ) );
Assert.assertEquals( "'unlistable' is listable", false, metaNames.get( "unlistable" ) );
Assert.assertEquals( "'list/able/not' is listable", false, metaNames.get( "list/able/not" ) );
}
/**
* Tests updating an object's metadata
*/
@Test
public void testUpdateObjectMetadata() throws Exception {
// Create an object
CreateObjectRequest request = new CreateObjectRequest().content( "hello".getBytes( "UTF-8" ) );
Metadata unlistable = new Metadata( "unlistable", "foo", false );
request.userMetadata( unlistable );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Update the metadata
unlistable.setValue( "bar" );
this.api.setUserMetadata( id,
request.getUserMetadata().toArray( new Metadata[request.getUserMetadata().size()] ) );
// Re-read the metadata
Map<String, Metadata> meta = this.api.getUserMetadata( id );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", meta.get( "unlistable" ).getValue() );
// Check that content was not modified
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
}
@Test
public void testUpdateObjectAcl() throws Exception {
// Create an object with an ACL
Acl acl = new Acl();
acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL );
acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ );
ObjectId id = this.api.createObject( new CreateObjectRequest().acl( acl ) ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the ACL and make sure it matches
Acl newacl = this.api.getAcl( id );
l4j.info( "Comparing " + newacl + " with " + acl );
Assert.assertEquals( "ACLs don't match", acl, newacl );
// Change the ACL and update the object.
acl.removeGroupGrant( Acl.GROUP_OTHER );
acl.addGroupGrant( Acl.GROUP_OTHER, Permission.NONE );
this.api.setAcl( id, acl );
// Read the ACL back and check it
newacl = this.api.getAcl( id );
l4j.info( "Comparing " + newacl + " with " + acl );
Assert.assertEquals( "ACLs don't match", acl, newacl );
}
/**
* Tests updating an object's contents
*/
@Test
public void testUpdateObjectContent() throws Exception {
// Create an object
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Update part of the content
Range range = new Range( 1, 1 );
this.api.updateObject( id, "u".getBytes( "UTF-8" ), range );
// Read back the content and check it
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hullo", content );
}
@Test
public void testUpdateObjectContentStream() throws Exception {
// Create an object
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Update part of the content
InputStream in = new ByteArrayInputStream( "u".getBytes( "UTF-8" ) );
UpdateObjectRequest request = new UpdateObjectRequest().identifier( id );
request.range( new Range( 1, 1 ) ).content( in );
request.setContentLength( 1 );
this.api.updateObject( request );
in.close();
// Read back the content and check it
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hullo", content );
}
/**
* Test replacing an object's entire contents
*/
@Test
public void testReplaceObjectContent() throws Exception {
// Create an object
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Update all of the content
this.api.updateObject( id, "bonjour".getBytes( "UTF-8" ) );
// Read back the content and check it
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "bonjour", content );
}
@Test
public void testListDirectory() throws Exception {
String dir = rand8char();
String file = rand8char();
String dir2 = rand8char();
ObjectPath dirPath = new ObjectPath( "/" + dir + "/" );
ObjectPath op = new ObjectPath( "/" + dir + "/" + file );
ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" );
ObjectId dirId = this.api.createDirectory( dirPath );
ObjectId id = this.api.createObject( op, null, null );
this.api.createDirectory( dirPath2 );
cleanup.add( op );
cleanup.add( dirPath2 );
cleanup.add( dirPath );
l4j.debug( "Path: " + op + " ID: " + id );
Assert.assertNotNull( id );
Assert.assertNotNull( dirId );
// Read back the content
String content = new String( this.api.readObject( op, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "", content );
content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong when reading by id", "", content );
// List the parent path
List<DirectoryEntry> dirList = api.listDirectory( new ListDirectoryRequest().path( dirPath ) ).getEntries();
l4j.debug( "Dir content: " + content );
Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) );
Assert.assertTrue( "subdirectory not found in directory",
directoryContains( dirList, dirPath2.getFilename() ) );
}
@Test
public void testListDirectoryPaged() throws Exception {
String dir = rand8char();
String file = rand8char();
String dir2 = rand8char();
ObjectPath dirPath = new ObjectPath( "/" + dir + "/" );
ObjectPath op = new ObjectPath( "/" + dir + "/" + file );
ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" );
ObjectId dirId = this.api.createDirectory( dirPath );
ObjectId id = this.api.createObject( op, null, null );
this.api.createDirectory( dirPath2 );
cleanup.add( op );
cleanup.add( dirPath2 );
cleanup.add( dirPath );
l4j.debug( "Path: " + op + " ID: " + id );
Assert.assertNotNull( id );
Assert.assertNotNull( dirId );
// List the parent path
ListDirectoryRequest request = new ListDirectoryRequest().path( dirPath );
request.setLimit( 1 );
List<DirectoryEntry> dirList = api.listDirectory( request ).getEntries();
Assert.assertNotNull( "Token should have been returned", request.getToken() );
l4j.debug( "listDirectoryPaged, token: " + request.getToken() );
while ( request.getToken() != null ) {
dirList.addAll( api.listDirectory( request ).getEntries() );
}
Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) );
Assert.assertTrue( "subdirectory not found in directory",
directoryContains( dirList, dirPath2.getFilename() ) );
}
@Test
public void testListDirectoryWithMetadata() throws Exception {
String dir = rand8char();
String file = rand8char();
String dir2 = rand8char();
ObjectPath dirPath = new ObjectPath( "/" + dir + "/" );
ObjectPath op = new ObjectPath( "/" + dir + "/" + file );
ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" );
CreateObjectRequest request = new CreateObjectRequest().identifier( op );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId dirId = this.api.createDirectory( dirPath );
ObjectId id = this.api.createObject( request ).getObjectId();
this.api.createDirectory( dirPath2 );
cleanup.add( op );
cleanup.add( dirPath2 );
cleanup.add( dirPath );
l4j.debug( "Path: " + op + " ID: " + id );
Assert.assertNotNull( id );
Assert.assertNotNull( dirId );
// List the parent path
ListDirectoryRequest lRequest = new ListDirectoryRequest().path( dirPath ).includeMetadata( true );
List<DirectoryEntry> dirList = api.listDirectory( lRequest ).getEntries();
Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) );
Assert.assertTrue( "subdirectory not found in directory",
directoryContains( dirList, dirPath2.getFilename() ) );
for ( DirectoryEntry de : dirList ) {
if ( new ObjectPath( dirPath, de.getFilename() ).equals( op ) ) {
// Check the metadata
Assert.assertNotNull("missing metadata 'listable'", de.getUserMetadataMap().get( "listable" ));
Assert.assertEquals( "Wrong value on metadata",
de.getUserMetadataMap().get( "listable" ).getValue(), "foo" );
}
}
Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) );
Assert.assertTrue( "subdirectory not found in directory",
directoryContains( dirList, dirPath2.getFilename() ) );
}
@Test
public void testListDirectoryWithSomeMetadata() throws Exception {
String dir = rand8char();
String file = rand8char();
String dir2 = rand8char();
ObjectPath dirPath = new ObjectPath( "/" + dir + "/" );
ObjectPath op = new ObjectPath( "/" + dir + "/" + file );
ObjectPath dirPath2 = new ObjectPath( "/" + dir + "/" + dir2 + "/" );
CreateObjectRequest request = new CreateObjectRequest().identifier( op );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "list/able/2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "list/able/not", "bar2 bar2", false );
request.userMetadata( listable, unlistable, listable2, unlistable2 );
ObjectId dirId = this.api.createDirectory( dirPath );
ObjectId id = this.api.createObject( request ).getObjectId();
this.api.createDirectory( dirPath2 );
cleanup.add( op );
cleanup.add( dirPath2 );
cleanup.add( dirPath );
l4j.debug( "Path: " + op + " ID: " + id );
Assert.assertNotNull( id );
Assert.assertNotNull( dirId );
// List the parent path
ListDirectoryRequest lRequest = new ListDirectoryRequest().path( dirPath ).includeMetadata( true );
lRequest.userMetadataNames( "listable" );
List<DirectoryEntry> dirList = api.listDirectory( lRequest ).getEntries();
Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) );
Assert.assertTrue( "subdirectory not found in directory",
directoryContains( dirList, dirPath2.getFilename() ) );
for ( DirectoryEntry de : dirList ) {
if ( new ObjectPath( dirPath, de.getFilename() ).equals( op ) ) {
// Check the metadata
Assert.assertNotNull("Missing metadata 'listable'",
de.getUserMetadataMap().get( "listable" ));
Assert.assertEquals( "Wrong value on metadata",
de.getUserMetadataMap().get( "listable" ).getValue(), "foo" );
// Other metadata should not be present
Assert.assertNull( "unlistable should be missing",
de.getUserMetadataMap().get( "unlistable" ) );
}
}
Assert.assertTrue( "File not found in directory", directoryContains( dirList, op.getFilename() ) );
Assert.assertTrue( "subdirectory not found in directory",
directoryContains( dirList, dirPath2.getFilename() ) );
}
private boolean directoryContains( List<DirectoryEntry> dir, String filename ) {
for ( DirectoryEntry de : dir ) {
if ( de.getFilename().equals( filename ) ) {
return true;
}
}
return false;
}
/**
* This method tests various legal and illegal pathnames
*
* @throws Exception
*/
@Test
public void testPathNaming() throws Exception {
ObjectPath path = new ObjectPath( "/some/file" );
Assert.assertFalse( "File should not be directory", path.isDirectory() );
path = new ObjectPath( "/some/file.txt" );
Assert.assertFalse( "File should not be directory", path.isDirectory() );
ObjectPath path2 = new ObjectPath( "/some/file.txt" );
Assert.assertEquals( "Equal paths should be equal", path, path2 );
path = new ObjectPath( "/some/file/with/long.path/extra.stuff.here.zip" );
Assert.assertFalse( "File should not be directory", path.isDirectory() );
path = new ObjectPath( "/" );
Assert.assertTrue( "Directory should be directory", path.isDirectory() );
path = new ObjectPath( "/long/path/with/lots/of/elements/" );
Assert.assertTrue( "Directory should be directory", path.isDirectory() );
}
/**
* Tests dot directories (you should be able to create them even though they break the URL specification.)
*
* @throws Exception
*/
@Test
public void testDotDirectories() throws Exception {
ObjectPath parentPath = createTestDir("DotDirectories");
ObjectPath dotPath = new ObjectPath( parentPath, "./" );
ObjectPath dotdotPath = new ObjectPath( parentPath, "../" );
String filename = rand8char();
byte[] content = "Hello World!".getBytes("UTF-8");
// test single dot path (./)
ObjectId dirId = this.api.createDirectory( dotPath );
Assert.assertNotNull( "null ID returned on dot path creation", dirId );
ObjectId fileId = this.api.createObject( new ObjectPath( dotPath, filename ), content, "text/plain" );
cleanup.add( fileId );
cleanup.add( dirId );
// make sure we only see one file (the "." path is its own directory and not a synonym for the current directory)
List<DirectoryEntry> entries = this.api
.listDirectory( new ListDirectoryRequest().path( dotPath ) ).getEntries();
Assert.assertEquals( "dot path listing was not 1", entries.size(), 1 );
Assert.assertEquals( "dot path listing did not contain test file",
entries.get( 0 ).getFilename(),
filename );
// test double dot path (../)
dirId = this.api.createDirectory( dotdotPath );
Assert.assertNotNull( "null ID returned on dotdot path creation", dirId );
fileId = this.api.createObject( new ObjectPath( dotdotPath, filename ), content, "text/plain" );
cleanup.add( fileId );
cleanup.add( dirId );
// make sure we only see one file (the ".." path is its own directory and not a synonym for the parent directory)
entries = this.api
.listDirectory( new ListDirectoryRequest().path( dotdotPath ) ).getEntries();
Assert.assertEquals( "dotdot path listing was not 1", entries.size(), 1 );
Assert.assertEquals("dotdot path listing did not contain test file",
entries.get(0).getFilename(),
filename);
}
/**
* Tests the 'get all metadata' call using a path
*
* @throws Exception
*/
@Test
public void testGetAllMetadataByPath() throws Exception {
ObjectPath op = new ObjectPath( "/" + rand8char() + ".tmp" );
String mimeType = "test/mimetype";
// Create an object with an ACL
Acl acl = new Acl();
acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL );
acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ );
CreateObjectRequest request = new CreateObjectRequest().identifier( op ).acl( acl );
request.content( "test".getBytes( "UTF-8" ) ).contentType( mimeType );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( op );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
this.api.updateObject( new UpdateObjectRequest().identifier( op )
.userMetadata( listable, unlistable, listable2, unlistable2 )
.contentType( mimeType ) );
// Read it back with HEAD call
ObjectMetadata om = this.api.getObjectMetadata( op );
Assert.assertNotNull( "value of 'listable' missing", om.getMetadata().get( "listable" ) );
Assert.assertNotNull( "value of 'unlistable' missing", om.getMetadata().get( "unlistable" ) );
Assert.assertNotNull( "value of 'atime' missing", om.getMetadata().get( "atime" ) );
Assert.assertNotNull( "value of 'ctime' missing", om.getMetadata().get( "ctime" ) );
Assert.assertEquals( "value of 'listable' wrong", "foo", om.getMetadata().get( "listable" ).getValue() );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", om.getMetadata().get( "unlistable" ).getValue() );
Assert.assertEquals( "Mimetype incorrect", mimeType, om.getContentType() );
// Check the ACL
// not checking this by path because an extra groupid is added
// during the create calls by path.
//Assert.assertEquals( "ACLs don't match", acl, om.getAcl() );
}
@Test
public void testGetAllMetadataById() throws Exception {
// Create an object with an ACL
CreateObjectRequest request = new CreateObjectRequest();
Acl acl = new Acl();
acl.addUserGrant( stripUid( config.getTokenId() ), Permission.FULL_CONTROL );
acl.addGroupGrant( Acl.GROUP_OTHER, Permission.READ );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
String mimeType = "test/mimetype";
request.acl( acl ).userMetadata( listable, unlistable, listable2, unlistable2 );
request.content( "test".getBytes( "UTF-8" ) ).contentType( mimeType );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read it back with HEAD call
ObjectMetadata om = this.api.getObjectMetadata( id );
Assert.assertNotNull( "value of 'listable' missing", om.getMetadata().get( "listable" ) );
Assert.assertNotNull( "value of 'unlistable' missing", om.getMetadata().get( "unlistable" ) );
Assert.assertNotNull( "value of 'atime' missing", om.getMetadata().get( "atime" ) );
Assert.assertNotNull( "value of 'ctime' missing", om.getMetadata().get( "ctime" ) );
Assert.assertEquals( "value of 'listable' wrong", "foo", om.getMetadata().get( "listable" ).getValue() );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", om.getMetadata().get( "unlistable" ).getValue() );
Assert.assertEquals( "Mimetype incorrect", mimeType, om.getContentType() );
// Check the ACL
Assert.assertEquals( "ACLs don't match", acl, om.getAcl() );
}
/**
* Tests getting object replica information.
*/
@Test
public void testGetObjectReplicaInfo() throws Exception {
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
Map<String, Metadata> meta = this.api.getUserMetadata( id, "user.maui.lso" );
Assert.assertNotNull( meta.get( "user.maui.lso" ) );
l4j.debug( "Replica info: " + meta.get( "user.maui.lso" ) );
}
@Test
public void testGetShareableUrl() throws Exception {
Assume.assumeFalse(isVipr);
// Create an object with content.
String str = "Four score and twenty years ago";
ObjectId id = this.api.createObject( str.getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
Calendar c = Calendar.getInstance();
c.add( Calendar.HOUR, 4 );
Date expiration = c.getTime();
URL u = api.getShareableUrl( id, expiration );
l4j.debug( "Sharable URL: " + u );
InputStream stream = (InputStream) u.getContent();
BufferedReader br = new BufferedReader( new InputStreamReader( stream ) );
String content = br.readLine();
l4j.debug( "Content: " + content );
Assert.assertEquals( "URL does not contain proper content",
str, content );
}
@Test
public void testGetShareableUrlWithPath() throws Exception {
Assume.assumeFalse(isVipr);
// Create an object with content.
String str = "Four score and twenty years ago";
ObjectPath op = new ObjectPath( "/" + rand8char() + ".txt" );
ObjectId id = this.api.createObject( op, str.getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( op );
Calendar c = Calendar.getInstance();
c.add( Calendar.HOUR, 4 );
Date expiration = c.getTime();
URL u = api.getShareableUrl( op, expiration );
l4j.debug( "Sharable URL: " + u );
InputStream stream = (InputStream) u.getContent();
BufferedReader br = new BufferedReader( new InputStreamReader( stream ) );
String content = br.readLine();
l4j.debug( "Content: " + content );
Assert.assertEquals( "URL does not contain proper content",
str, content );
}
@Test
public void testExpiredSharableUrl() throws Exception {
Assume.assumeFalse(isVipr);
// Create an object with content.
String str = "Four score and twenty years ago";
ObjectId id = this.api.createObject( str.getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
Calendar c = Calendar.getInstance();
c.add( Calendar.HOUR, -4 );
Date expiration = c.getTime();
URL u = api.getShareableUrl( id, expiration );
l4j.debug( "Sharable URL: " + u );
try {
InputStream stream = (InputStream) u.getContent();
BufferedReader br = new BufferedReader( new InputStreamReader( stream ) );
String content = br.readLine();
l4j.debug( "Content: " + content );
Assert.fail( "Request should have failed" );
} catch ( Exception e ) {
l4j.debug( "Error (expected): " + e );
}
}
@Test
public void testReadObjectStream() throws Exception {
ObjectId id = this.api.createObject( "hello".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read back the content
InputStream in = this.api.readObjectStream( id, null ).getObject();
BufferedReader br = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
String content = br.readLine();
br.close();
Assert.assertEquals( "object content wrong", "hello", content );
// Read back only 2 bytes
Range range = new Range( 1, 2 );
in = this.api.readObjectStream( id, range ).getObject();
br = new BufferedReader( new InputStreamReader( in, "UTF-8" ) );
content = br.readLine();
br.close();
Assert.assertEquals( "partial object content wrong", "el", content );
}
@Test
public void testCreateChecksum() throws Exception {
byte[] data = "hello".getBytes( "UTF-8" );
RunningChecksum ck = new RunningChecksum( ChecksumAlgorithm.SHA0 );
ck.update( data, 0, data.length );
CreateObjectRequest request = new CreateObjectRequest().content( data ).contentType( "text/plain" );
request.wsChecksum( ck );
CreateObjectResponse response = this.api.createObject( request );
cleanup.add( response.getObjectId() );
Assert.assertNotNull( "Null object ID returned", response.getObjectId() );
Assert.assertEquals( "Checksum doesn't match", ck, response.getWsChecksum() );
}
/**
* Note, to test read checksums, see comment in testReadChecksum
*
* @throws Exception
*/
@Test
public void testUploadDownloadChecksum() throws Exception {
// Create a byte array to test
int totalSize = 10 * 1024 * 1024; // 10MB
int chunkSize = 4 * 1024 * 1024; // 4MB
byte[] testData = new byte[totalSize]; // 10MB
for ( int i = 0; i < testData.length; i++ ) {
testData[i] = (byte) (i % 0x93);
}
RunningChecksum sha0 = new RunningChecksum( ChecksumAlgorithm.SHA0 );
BufferSegment segment = new BufferSegment( testData, 0, chunkSize );
// upload in chunks
sha0.update( segment );
l4j.debug( "Create checksum: " + sha0 );
CreateObjectRequest request = new CreateObjectRequest();
request.content( segment ).userMetadata( new Metadata( "policy", "erasure", false ) ).setWsChecksum( sha0 );
ObjectId id = api.createObject( request ).getObjectId();
cleanup.add( id );
while ( segment.getOffset() + segment.getSize() < totalSize ) {
segment.setOffset( segment.getOffset() + chunkSize );
if ( segment.getOffset() + chunkSize > totalSize ) segment.setSize( totalSize - segment.getOffset() );
Range range = new Range( segment.getOffset(), segment.getOffset() + segment.getSize() - 1 );
sha0.update( segment.getBuffer(), segment.getOffset(), segment.getSize() );
l4j.debug( "Update checksum: " + sha0 );
api.updateObject( new UpdateObjectRequest().identifier( id ).range( range )
.content( segment ).wsChecksum( sha0 ) );
}
// download in chunks
totalSize = Integer.parseInt( api.getSystemMetadata( id ).get( "size" ).getValue() );
ByteArrayOutputStream out = new ByteArrayOutputStream();
int first = 0, last = chunkSize - 1;
Range range = new Range( first, last );
ReadObjectResponse<byte[]> response;
RunningChecksum readSha0 = new RunningChecksum( ChecksumAlgorithm.SHA0 );
do {
response = api.readObject( new ReadObjectRequest().identifier( id ).ranges( range ),
byte[].class );
readSha0.update( response.getObject(), 0, response.getObject().length );
out.write( response.getObject() );
first += chunkSize;
last += chunkSize;
if ( last >= totalSize ) last = totalSize - 1;
range = new Range( first, last );
} while ( first < totalSize );
byte[] outData = out.toByteArray();
// verify checksum
Assert.assertEquals( "Write checksum doesn't match read checksum", sha0, readSha0 );
Assert.assertEquals( "Read checksum doesn't match", readSha0, response.getWsChecksum() );
// Check the files
Assert.assertEquals( "File lengths differ", testData.length, outData.length );
Assert.assertArrayEquals( "Data contents differ", testData, outData );
}
@Ignore("TODO: Figure out why this fails")
@Test
public void testUtf8WhiteSpaceValues() throws Exception {
String utf8String = "Hello ,\u0080 \r \u000B \t \n \t";
CreateObjectRequest request = new CreateObjectRequest();
request.userMetadata( new Metadata( "utf8Key", utf8String, false ) );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
// get the user metadata and make sure all UTF8 characters are accurate
Map<String, Metadata> metaMap = this.api.getUserMetadata( id );
Assert.assertEquals( "UTF8 value does not match", utf8String, metaMap.get( "utf8Key" ).getValue() );
// test set metadata with UTF8
this.api.setUserMetadata( id, new Metadata( "newKey", utf8String + "2", false ) );
// verify set metadata call (also testing getAllMetadata)
ObjectMetadata objMeta = this.api.getObjectMetadata( id );
metaMap = objMeta.getMetadata();
//Assert.assertEquals( "UTF8 key does not match", meta.getName(), whiteSpaceString + "2" );
//Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "newValue" );
Assert.assertEquals( "UTF8 value does not match",
utf8String + "2",
metaMap.get( "newKey" ).getValue() );
}
@Test
public void testUnicodeMetadata() throws Exception {
CreateObjectRequest request = new CreateObjectRequest();
Metadata nbspValue = new Metadata( "nbspvalue", "Nobreak\u00A0Value", false );
Metadata nbspName = new Metadata( "Nobreak\u00A0Name", "regular text here", false );
Metadata cryllic = new Metadata( "cryllic", "спасибо", false );
l4j.debug( "NBSP Value: " + nbspValue );
l4j.debug( "NBSP Name: " + nbspName );
request.userMetadata( nbspValue, nbspName, cryllic );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read and validate the metadata
Map<String, Metadata> meta = this.api.getUserMetadata( id );
l4j.debug( "Read Back:" );
l4j.debug( "NBSP Value: " + meta.get( "nbspvalue" ) );
l4j.debug( "NBSP Name: " + meta.get( "Nobreak\u00A0Name" ) );
Assert.assertEquals( "value of 'nobreakvalue' wrong",
"Nobreak\u00A0Value",
meta.get( "nbspvalue" ).getValue() );
Assert.assertEquals( "Value of cryllic wrong", "спасибо", meta.get( "cryllic" ).getValue() );
}
@Test
public void testUtf8Metadata() throws Exception {
String oneByteCharacters = "Hello! ";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters;
CreateObjectRequest request = new CreateObjectRequest();
request.userMetadata( new Metadata( "utf8Key", utf8String, false ),
new Metadata( utf8String, "utf8Value", false ) );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
// list all tags and make sure the UTF8 tag is in the list
Map<String, Boolean> tags = this.api.getUserMetadataNames( id );
Assert.assertTrue( "UTF8 key not found in tag list", tags.containsKey( utf8String ) );
// get the user metadata and make sure all UTF8 characters are accurate
Map<String, Metadata> metaMap = this.api.getUserMetadata( id );
Metadata meta = metaMap.get( utf8String );
Assert.assertEquals( "UTF8 key does not match", meta.getName(), utf8String );
Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "utf8Value" );
Assert.assertEquals( "UTF8 value does not match", metaMap.get( "utf8Key" ).getValue(), utf8String );
// test set metadata with UTF8
this.api.setUserMetadata( id, new Metadata( "newKey", utf8String + "2", false ),
new Metadata( utf8String + "2", "newValue", false ) );
// verify set metadata call (also testing getAllMetadata)
ObjectMetadata objMeta = this.api.getObjectMetadata( id );
metaMap = objMeta.getMetadata();
meta = metaMap.get( utf8String + "2" );
Assert.assertEquals( "UTF8 key does not match", meta.getName(), utf8String + "2" );
Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "newValue" );
Assert.assertEquals( "UTF8 value does not match",
metaMap.get( "newKey" ).getValue(),
utf8String + "2" );
}
@Test
public void testUtf8MetadataFilter() throws Exception {
String oneByteCharacters = "Hello! ";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters;
CreateObjectRequest request = new CreateObjectRequest();
request.userMetadata( new Metadata( "utf8Key", utf8String, false ) )
.userMetadata( new Metadata( utf8String, "utf8Value", false ) );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
// apply a filter that includes the UTF8 tag
Map<String, Metadata> metaMap = this.api.getUserMetadata( id, utf8String );
Assert.assertEquals( "UTF8 filter was not honored", metaMap.size(), 1 );
Assert.assertNotNull( "UTF8 key was not found in filtered results", metaMap.get( utf8String ) );
}
@Test
public void testUtf8DeleteMetadata() throws Exception {
String oneByteCharacters = "Hello! ";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters;
CreateObjectRequest request = new CreateObjectRequest();
request.userMetadata( new Metadata( "utf8Key", utf8String, false ) )
.userMetadata( new Metadata( utf8String, "utf8Value", false ) );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
// delete the UTF8 tag
this.api.deleteUserMetadata( id, utf8String );
// verify delete was successful
Map<String, Boolean> nameMap = this.api.getUserMetadataNames( id );
Assert.assertFalse( "UTF8 key was not deleted", nameMap.containsKey( utf8String ) );
}
@Test
public void testUtf8ListableMetadata() throws Exception {
String oneByteCharacters = "Hello! ";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
String utf8String = oneByteCharacters + twoByteCharacters + fourByteCharacters;
CreateObjectRequest request = new CreateObjectRequest();
request.userMetadata( new Metadata( utf8String, "utf8Value", true ) );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
Map<String, Metadata> metaMap = this.api.getUserMetadata( id );
Metadata meta = metaMap.get( utf8String );
Assert.assertEquals( "UTF8 key does not match", meta.getName(), utf8String );
Assert.assertEquals( "UTF8 key value does not match", meta.getValue(), "utf8Value" );
Assert.assertTrue( "UTF8 metadata is not listable", meta.isListable() );
// verify we can list the tag and see our object
boolean found = false;
for ( ObjectEntry result : this.api
.listObjects( new ListObjectsRequest().metadataName( utf8String ) ).getEntries() ) {
if ( result.getObjectId().equals( id ) ) {
found = true;
break;
}
}
Assert.assertTrue( "UTF8 tag listing did not contain the correct object ID", found );
// verify we can list child tags of the UTF8 tag
Set<String> tags = this.api.listMetadata( utf8String );
Assert.assertNotNull( "UTF8 child tag listing was null", tags );
}
@Test
public void testUtf8ListableTagWithComma() {
String stringWithComma = "Hello, you!";
CreateObjectRequest request = new CreateObjectRequest();
request.userMetadata( new Metadata( stringWithComma, "value", true ) );
ObjectId id = this.api.createObject( request ).getObjectId();
cleanup.add( id );
Map<String, Metadata> metaMap = this.api.getUserMetadata( id );
Metadata meta = metaMap.get( stringWithComma );
Assert.assertEquals( "key does not match", meta.getName(), stringWithComma );
Assert.assertTrue( "metadata is not listable", meta.isListable() );
boolean found = false;
for ( ObjectEntry result : this.api
.listObjects( new ListObjectsRequest().metadataName( stringWithComma ) ).getEntries() ) {
if ( result.getObjectId().equals( id ) ) {
found = true;
break;
}
}
Assert.assertTrue( "listing did not contain the correct object ID", found );
}
@Test
public void testRename() throws Exception {
ObjectPath op1 = new ObjectPath( "/" + rand8char() + ".tmp" );
ObjectPath op2 = new ObjectPath( "/" + rand8char() + ".tmp" );
ObjectId id = this.api.createObject( op1, "Four score and seven years ago".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Rename
this.api.move( op1, op2, false );
// Read back the content
String content = new String( this.api.readObject( op2, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "Four score and seven years ago", content );
}
@Test
public void testRenameOverwrite() throws Exception {
ObjectPath op1 = new ObjectPath( "/" + rand8char() + ".tmp" );
ObjectPath op2 = new ObjectPath( "/" + rand8char() + ".tmp" );
ObjectId id = this.api.createObject( op1, "Four score and seven years ago".getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
ObjectId id2 = this.api.createObject( op2, "You should not see this".getBytes( "UTF-8" ), "text/plain" );
cleanup.add( id2 );
// Rename
this.api.move( op1, op2, true );
// Wait for overwrite to complete
Thread.sleep( 5000 );
// Read back the content
String content = new String( this.api.readObject( op2, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "Four score and seven years ago", content );
}
/**
* Tests renaming a path to UTF-8 multi-byte characters. This is a separate test from create as the characters are
* passed in the headers instead of the URL itself.
*
* @throws Exception
*/
@Test
public void testUtf8Rename() throws Exception {
ObjectPath parentDir = createTestDir("Utf8Rename");
String oneByteCharacters = "Hello! ,";
String twoByteCharacters = "\u0410\u0411\u0412\u0413"; // Cyrillic letters
String fourByteCharacters = "\ud841\udf0e\ud841\udf31\ud841\udf79\ud843\udc53"; // Chinese symbols
ObjectPath normalName = new ObjectPath( parentDir, rand8char() + ".tmp");
String crazyName = oneByteCharacters + twoByteCharacters + fourByteCharacters;
ObjectPath crazyPath = new ObjectPath( parentDir, crazyName );
byte[] content = "This is a really crazy name.".getBytes( "UTF-8" );
// normal name
this.api.createObject( normalName, content, "text/plain" );
// crazy multi-byte character name
this.api.move( normalName, crazyPath, true );
// Wait for overwrite to complete
Thread.sleep( 5000 );
// verify name in directory list
List<DirectoryEntry> entries = this.api.listDirectory(new ListDirectoryRequest().path(parentDir)).getEntries();
Assert.assertTrue( "crazyName not found in directory listing", directoryContains( entries, crazyName ) );
// Read back the content
Assert.assertTrue( "object content wrong",
Arrays.equals( content,
this.api.readObject( crazyPath, null, byte[].class ) ) );
}
@Test
public void testPositiveChecksumValidation() throws Exception {
byte[] data = "Hello Checksums!".getBytes("UTF-8");
RunningChecksum md5 = new RunningChecksum(ChecksumAlgorithm.MD5);
RunningChecksum sha0 = new RunningChecksum(ChecksumAlgorithm.SHA0);
RunningChecksum sha1 = new RunningChecksum(ChecksumAlgorithm.SHA1);
md5.update(data, 0, data.length);
sha0.update(data, 0, data.length);
sha1.update(data, 0, data.length);
CreateObjectRequest request = new CreateObjectRequest().content(data);
ObjectId md5Id = api.createObject(request.wsChecksum(md5)).getObjectId();
ObjectId sha0Id = api.createObject(request.wsChecksum(sha0)).getObjectId();
ObjectId sha1Id = api.createObject(request.wsChecksum(sha1)).getObjectId();
cleanup.add(md5Id);
cleanup.add(sha0Id);
cleanup.add(sha1Id);
Assert.assertEquals("MD5 checksum was not equal",
md5, api.readObject(new ReadObjectRequest().identifier(md5Id), byte[].class).getWsChecksum());
Assert.assertEquals("SHA0 checksum was not equal",
sha0, api.readObject(new ReadObjectRequest().identifier(sha0Id), byte[].class).getWsChecksum());
Assert.assertEquals("SHA1 checksum was not equal",
sha1, api.readObject(new ReadObjectRequest().identifier(sha1Id), byte[].class).getWsChecksum());
// do a bunch of calls to make sure we don't try to validate
api.getSystemMetadata(md5Id);
api.getObjectMetadata(sha1Id);
api.getObjectInfo(sha0Id);
api.readObject(md5Id, new Range(1, 8), byte[].class);
api.listVersions(new ListVersionsRequest().objectId(sha1Id));
api.getAcl(sha0Id);
Assert.assertTrue("object stream is not a ChecksummedInputStream",
api.readObjectStream(sha0Id, null).getObject() instanceof ChecksummedInputStream);
// test update
byte[] appendData = " and stuff!".getBytes("UTF-8");
md5.update(appendData, 0, appendData.length);
UpdateObjectRequest uRequest = new UpdateObjectRequest().identifier(md5Id).content(appendData).wsChecksum(md5);
uRequest.setRange(new Range(data.length, data.length + appendData.length - 1));
api.updateObject(uRequest);
Assert.assertTrue("object stream is not a ChecksummedInputStream",
api.readObjectStream(md5Id, null).getObject() instanceof ChecksummedInputStream);
api.readObject(md5Id, byte[].class);
}
/**
* Tests readback with checksum verification. In order to test this, create a policy
* with erasure coding and then set a policy selector with "policy=erasure" to invoke
* the erasure coding policy.
*
* @throws Exception
*/
@Test
public void testReadChecksum() throws Exception {
byte[] data = "hello".getBytes( "UTF-8" );
Metadata policy = new Metadata( "policy", "erasure", false );
RunningChecksum wsChecksum = new RunningChecksum( ChecksumAlgorithm.SHA0 );
wsChecksum.update( data, 0, data.length );
CreateObjectRequest request = new CreateObjectRequest().content( data ).contentType( "text/plain" );
request.userMetadata( policy ).wsChecksum( wsChecksum );
CreateObjectResponse response = this.api.createObject( request );
Assert.assertNotNull( "null ID returned", response.getObjectId() );
cleanup.add( response.getObjectId() );
Assert.assertNotNull( "null ID returned", response.getObjectId() );
Assert.assertEquals( "create checksums don't match", wsChecksum, response.getWsChecksum() );
// Read back the content
ReadObjectRequest readRequest = new ReadObjectRequest().identifier( response.getObjectId() );
ReadObjectResponse<byte[]> readResponse = this.api.readObject( readRequest, byte[].class );
Assert.assertArrayEquals( "object content wrong", data, readResponse.getObject() );
Assert.assertEquals( "read checksums don't match", wsChecksum, readResponse.getWsChecksum() );
}
/**
* Tests getting the service information
*/
@Test
public void testGetServiceInformation() throws Exception {
ServiceInformation si = this.api.getServiceInformation();
Assert.assertNotNull( "Atmos version is null", si.getAtmosVersion() );
}
/**
* Test getting object info. Note to fully run this testcase, you should
* create a policy named 'retaindelete' that keys off of the metadata
* policy=retaindelete that includes a retention and deletion criteria.
*/
@Test
public void testGetObjectInfo() throws Exception {
CreateObjectRequest request = new CreateObjectRequest();
request.content( "hello".getBytes( "UTF-8" ) ).contentType( "text/plain" );
ObjectId id = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
api.setUserMetadata( id, new Metadata( "policy", "retain", false ) );
// Read back the content
String content = new String( this.api.readObject( id, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
// Check policyname
Map<String,Metadata> sysmeta = this.api.getSystemMetadata(id, "policyname");
Assert.assertNotNull("Missing system metadata 'policyname'", sysmeta.get("policyname") );
// Get the object info
ObjectInfo oi = this.api.getObjectInfo( id );
Assert.assertNotNull( "ObjectInfo null", oi );
Assert.assertNotNull( "ObjectInfo objectid null", oi.getObjectId() );
Assert.assertTrue( "ObjectInfo numReplicas is 0", oi.getNumReplicas() > 0 );
Assert.assertNotNull( "ObjectInfo replicas null", oi.getReplicas() );
Assert.assertNotNull( "ObjectInfo selection null", oi.getSelection() );
Assert.assertTrue( "ObjectInfo should have at least one replica", oi.getReplicas().size() > 0 );
// only run these tests if the policy configuration is valid
Assume.assumeTrue("policyname != retaindelete", "retaindelete".equals(sysmeta.get("policyname").getValue()));
Assert.assertNotNull( "ObjectInfo expiration null", oi.getExpiration().getEndAt() );
Assert.assertNotNull( "ObjectInfo retention null", oi.getRetention().getEndAt() );
api.setUserMetadata( id, new Metadata( "user.maui.retentionEnable", "false", false ) );
}
@Test
public void testHmac() throws Exception {
// Compute the signature hash
String input = "Hello World";
byte[] secret = Base64.decodeBase64( "D7qsp4j16PBHWSiUbc/bt3lbPBY=".getBytes( "UTF-8" ) );
Mac mac = Mac.getInstance( "HmacSHA1" );
SecretKeySpec key = new SecretKeySpec( secret, "HmacSHA1" );
mac.init( key );
l4j.debug( "Hashing: \n" + input );
byte[] hashData = mac.doFinal( input.getBytes( "ISO-8859-1" ) );
// Encode the hash in Base64.
String hashOut = new String( Base64.encodeBase64( hashData ), "UTF-8" );
l4j.debug( "Hash: " + hashOut );
}
@Test
public void testDirectoryMetadata() throws Exception {
ObjectPath dir = new ObjectPath( "/" + rand8char() + "/" );
Metadata listable = new Metadata( "listable", "foo", true );
Metadata unlistable = new Metadata( "unlistable", "bar", false );
Metadata listable2 = new Metadata( "listable2", "foo2 foo2", true );
Metadata unlistable2 = new Metadata( "unlistable2", "bar2 bar2", false );
Metadata listable3 = new Metadata( "listable3", null, true );
Metadata withCommas = new Metadata( "withcommas", "I, Robot", false );
Metadata withEquals = new Metadata( "withequals", "name=value", false );
ObjectId id = this.api.createDirectory( dir, null, listable, unlistable, listable2, unlistable2, listable3,
withCommas, withEquals );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
// Read and validate the metadata
Map<String, Metadata> metaMap = this.api.getObjectMetadata( dir ).getMetadata();
Assert.assertNotNull("Missing metadata 'listable'", metaMap.get( "listable" ));
Assert.assertNotNull("Missing metadata 'listable2'", metaMap.get( "listable2" ));
Assert.assertNotNull("Missing metadata 'unlistable'", metaMap.get( "unlistable" ));
Assert.assertNotNull("Missing metadata 'unlistable2'", metaMap.get( "unlistable2" ));
Assert.assertEquals( "value of 'listable' wrong", "foo", metaMap.get( "listable" ).getValue() );
Assert.assertEquals( "value of 'listable2' wrong", "foo2 foo2", metaMap.get( "listable2" ).getValue() );
Assert.assertEquals( "value of 'unlistable' wrong", "bar", metaMap.get( "unlistable" ).getValue() );
Assert.assertEquals( "value of 'unlistable2' wrong", "bar2 bar2", metaMap.get( "unlistable2" ).getValue() );
Assert.assertNotNull( "listable3 missing", metaMap.get( "listable3" ) );
Assert.assertTrue( "Value of listable3 should be empty",
metaMap.get( "listable3" ).getValue() == null
|| metaMap.get( "listable3" ).getValue().length() == 0 );
Assert.assertEquals( "Value of withcommas wrong", "I, Robot", metaMap.get( "withcommas" ).getValue() );
Assert.assertEquals( "Value of withequals wrong", "name=value", metaMap.get( "withequals" ).getValue() );
// Check listable flags
Assert.assertEquals( "'listable' is not listable", true, metaMap.get( "listable" ).isListable() );
Assert.assertEquals( "'listable2' is not listable", true, metaMap.get( "listable2" ).isListable() );
Assert.assertEquals( "'listable3' is not listable", true, metaMap.get( "listable3" ).isListable() );
Assert.assertEquals( "'unlistable' is listable", false, metaMap.get( "unlistable" ).isListable() );
Assert.assertEquals( "'unlistable2' is listable", false, metaMap.get( "unlistable2" ).isListable() );
}
/**
* Tests fetching data with multiple ranges.
*/
@Test
public void testMultipleRanges() throws Exception {
String input = "Four score and seven years ago";
ObjectId id = api.createObject( input.getBytes( "UTF-8" ), "text/plain" );
cleanup.add( id );
Assert.assertNotNull( "Object null", id );
Range[] ranges = new Range[5];
ranges[0] = new Range( 27, 28 ); //ag
ranges[1] = new Range( 9, 9 ); // e
ranges[2] = new Range( 5, 5 ); // s
ranges[3] = new Range( 4, 4 ); // ' '
ranges[4] = new Range( 27, 29 ); // ago
ReadObjectResponse<MultipartEntity> response = api.readObject( new ReadObjectRequest().identifier( id )
.ranges( ranges ),
MultipartEntity.class );
String out = new String( response.getObject().aggregateBytes(), "UTF-8" );
Assert.assertEquals( "Content incorrect", "ages ago", out );
}
//---------- Features supported by the Atmos 2.0 REST API. ----------\\
@Test
public void testGetShareableUrlAndDisposition() throws Exception {
Assume.assumeFalse(isVipr);
// Create an object with content.
String str = "Four score and twenty years ago";
ObjectId id = this.api.createObject( str.getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
cleanup.add( id );
String disposition = "attachment; filename=\"foo bar.txt\"";
Calendar c = Calendar.getInstance();
c.add( Calendar.HOUR, 4 );
Date expiration = c.getTime();
URL u = this.api.getShareableUrl( id, expiration, disposition );
l4j.debug( "Sharable URL: " + u );
InputStream stream = (InputStream) u.getContent();
BufferedReader br = new BufferedReader( new InputStreamReader( stream ) );
String content = br.readLine();
l4j.debug( "Content: " + content );
Assert.assertEquals( "URL does not contain proper content",
str, content );
}
@Test
public void testGetShareableUrlWithPathAndDisposition() throws Exception {
Assume.assumeFalse(isVipr);
// Create an object with content.
String str = "Four score and twenty years ago";
ObjectPath op = new ObjectPath( "/" + rand8char() + ".txt" );
ObjectId id = this.api.createObject( op, str.getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
//cleanup.add( op );
String disposition = "attachment; filename=\"foo bar.txt\"";
Calendar c = Calendar.getInstance();
c.add( Calendar.HOUR, 4 );
Date expiration = c.getTime();
URL u = this.api.getShareableUrl( op, expiration, disposition );
l4j.debug( "Sharable URL: " + u );
InputStream stream = (InputStream) u.getContent();
BufferedReader br = new BufferedReader( new InputStreamReader( stream ) );
String content = br.readLine();
l4j.debug( "Content: " + content );
Assert.assertEquals( "URL does not contain proper content",
str, content );
}
@Test
public void testGetShareableUrlWithPathAndUTF8Disposition() throws Exception {
// Create an object with content.
Assume.assumeFalse(isVipr);
String str = "Four score and twenty years ago";
ObjectPath op = new ObjectPath( "/" + rand8char() + ".txt" );
ObjectId id = this.api.createObject( op, str.getBytes( "UTF-8" ), "text/plain" );
Assert.assertNotNull( "null ID returned", id );
//cleanup.add( op );
// One cryllic, one accented, and one japanese character
// RFC5987
String disposition = "attachment; filename=\"no UTF support.txt\"; filename*=UTF-8''" + URLEncoder.encode(
"бöシ.txt",
"UTF-8" );
Calendar c = Calendar.getInstance();
c.add( Calendar.HOUR, 4 );
Date expiration = c.getTime();
URL u = this.api.getShareableUrl( op, expiration, disposition );
l4j.debug( "Sharable URL: " + u );
InputStream stream = (InputStream) u.getContent();
BufferedReader br = new BufferedReader( new InputStreamReader( stream ) );
String content = br.readLine();
l4j.debug( "Content: " + content );
Assert.assertEquals( "URL does not contain proper content",
str, content );
}
@Test
public void testGetServiceInformationFeatures() throws Exception {
ServiceInformation info = this.api.getServiceInformation();
l4j.info( "Supported features: " + info.getFeatures() );
Assert.assertTrue( "Expected at least one feature", info.getFeatures().size() > 0 );
}
@Test
public void testBug23750() throws Exception {
byte[] data = new byte[1000];
Arrays.fill( data, (byte) 0 );
Metadata meta = new Metadata( "test", null, true );
RunningChecksum sha1 = new RunningChecksum( ChecksumAlgorithm.SHA1 );
sha1.update( data, 0, data.length );
CreateObjectResponse response = this.api.createObject(
new CreateObjectRequest().content( data ).wsChecksum( sha1 ).userMetadata( meta ) );
try {
Range range = new Range( 1000, 1999 );
RunningChecksum sha0 = new RunningChecksum( ChecksumAlgorithm.SHA0 );
sha0.update( data, 0, 1000 );
this.api.updateObject( new UpdateObjectRequest().identifier( response.getObjectId() ).content( data )
.range( range ).wsChecksum( sha0 ).userMetadata( meta ) );
Assert.fail( "Should have triggered an exception" );
} catch ( AtmosException e ) {
// expected
}
}
@Test
public void testCrudKeys() throws Exception {
Assume.assumeFalse(isVipr);
ObjectKey key = new ObjectKey( "Test_key-pool#@!$%^..", "KEY_TEST" );
String content = "Hello World!";
CreateObjectRequest request = new CreateObjectRequest().identifier( key );
request.content( content.getBytes( "UTF-8" ) ).contentType( "text/plain" );
ObjectId oid = this.api.createObject( request ).getObjectId();
Assert.assertNotNull( "Null object ID returned", oid );
cleanup.add( oid );
String readContent = new String( this.api.readObject( key, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "content mismatch", content, readContent );
content = "Hello Waldo!";
this.api.updateObject( new UpdateObjectRequest().identifier( key ).content( content.getBytes( "UTF-8" ) ) );
readContent = new String( this.api.readObject( key, null, byte[].class ), "UTF-8" );
Assert.assertEquals( "content mismatch", content, readContent );
this.api.delete( key );
try {
this.api.readObject( key, null, byte[].class );
Assert.fail( "Object still exists" );
} catch ( AtmosException e ) {
if ( e.getHttpCode() != 404 ) throw e;
}
}
@Test
public void testIssue9() throws Exception {
int threadCount = 10;
final int objectSize = 10 * 1000 * 1000; // not a power of 2
final AtmosApi atmosApi = api;
final List<ObjectIdentifier> cleanupList = new ArrayList<ObjectIdentifier>();
ThreadPoolExecutor executor = new ThreadPoolExecutor( threadCount, threadCount, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>() );
try {
for ( int i = 0; i < threadCount; i++ ) {
executor.execute( new Thread() {
public void run() {
CreateObjectRequest request = new CreateObjectRequest();
request.content( new RandomInputStream( objectSize ) ).contentLength( objectSize )
.userMetadata( new Metadata( "test-data", null, true ) );
ObjectId oid = atmosApi.createObject( request ).getObjectId();
cleanupList.add( oid );
}
} );
}
while ( true ) {
Thread.sleep( 1000 );
if ( executor.getActiveCount() < 1 ) break;
}
} finally {
executor.shutdown();
cleanup.addAll( cleanupList );
if ( cleanupList.size() < threadCount ) Assert.fail( "At least one thread failed" );
}
}
/**
* Test handling signature failures. Should throw an exception with
* error code 1032.
*/
@Test
public void testSignatureFailure() throws Exception {
byte[] goodSecret = config.getSecretKey();
try {
// Fiddle with the secret key
byte[] badSecret = Arrays.copyOf( goodSecret, goodSecret.length );
Arrays.fill( badSecret, 5, 10, (byte) 128 ); // indexes 5-9 will be 10000000 (binary)
config.setSecretKey( badSecret );
testCreateEmptyObject();
Assert.fail( "Expected exception to be thrown" );
} catch ( AtmosException e ) {
Assert.assertEquals( "Expected error code 1032 for signature failure", 1032, e.getErrorCode() );
} finally {
config.setSecretKey( goodSecret );
}
}
/**
* Test general HTTP errors by generating a 404.
*/
@Test
public void testFourOhFour() throws Exception {
try {
// Fiddle with the context
config.setContext( "/restttttttttt" );
testCreateEmptyObject();
Assert.fail( "Expected exception to be thrown" );
} catch ( AtmosException e ) {
Assert.assertEquals( "Expected error code 404 for bad context root", 404, e.getHttpCode() );
} finally {
config.setContext( AtmosConfig.DEFAULT_CONTEXT );
}
}
@Test
public void testServerOffset() throws Exception {
long offset = api.calculateServerClockSkew();
l4j.info( "Server offset: " + offset + " milliseconds" );
testCreateEmptyObject(); // make sure requests still work after setting clock skew
}
/**
* NOTE: This method does not actually test that the custom headers are sent over the wire. Run tcpmon or wireshark
* to verify
*/
@Test
public void testCustomHeaders() throws Exception {
final Map<String, String> customHeaders = new HashMap<String, String>();
customHeaders.put( "myCustomHeader", "Hello World!" );
((AtmosApiClient) api).addClientFilter( new ClientFilter() {
@Override
public ClientResponse handle( ClientRequest clientRequest ) throws ClientHandlerException {
for ( String name : customHeaders.keySet() )
clientRequest.getHeaders().add( name, customHeaders.get( name ) );
return getNext().handle( clientRequest );
}
} );
api.getServiceInformation();
}
@Test
public void testServerGeneratedChecksum() throws Exception {
Assume.assumeFalse(isVipr);
byte[] data = "hello".getBytes( "UTF-8" );
// generate our own checksum
RunningChecksum md5 = new RunningChecksum( ChecksumAlgorithm.MD5 );
md5.update( data, 0, data.length );
CreateObjectRequest request = new CreateObjectRequest().content( data ).contentType( "text/plain" );
request.setServerGeneratedChecksumAlgorithm( ChecksumAlgorithm.MD5 );
CreateObjectResponse response = this.api.createObject( request );
Assert.assertNotNull( "null ID returned", response.getObjectId() );
cleanup.add( response.getObjectId() );
// verify checksum
Assert.assertEquals( md5.toString( false ), response.getServerGeneratedChecksum().toString( false ) );
// Read back the content
ReadObjectRequest readRequest = new ReadObjectRequest().identifier( response.getObjectId() );
ReadObjectResponse<byte[]> readResponse = api.readObject( readRequest, byte[].class );
String content = new String( readResponse.getObject(), "UTF-8" );
Assert.assertEquals( "object content wrong", "hello", content );
// verify checksum
Assert.assertEquals( md5.toString( false ), readResponse.getServerGeneratedChecksum().toString( false ) );
}
@Ignore("Blocked by Bug 30073")
@Test
public void testReadAccessToken() throws Exception {
Assume.assumeFalse(isVipr);
ObjectPath parentDir = createTestDir("ReadAccessToken");
ObjectPath path = new ObjectPath( parentDir, "read_token \n,<x> test" );
ObjectId id = api.createObject( path, "hello", "text/plain" );
Calendar expiration = Calendar.getInstance();
expiration.add( Calendar.MINUTE, 5 ); // 5 minutes from now
AccessTokenPolicy.Source source = new AccessTokenPolicy.Source();
source.setAllowList( Arrays.asList( "10.0.0.0/8" ) );
source.setDenyList( Arrays.asList( "1.1.1.1" ) );
AccessTokenPolicy.ContentLengthRange range = new AccessTokenPolicy.ContentLengthRange();
range.setFrom( 0 );
range.setTo( 1024 ); // 1KB
AccessTokenPolicy policy = new AccessTokenPolicy();
policy.setExpiration( expiration.getTime() );
policy.setSource( source );
policy.setMaxDownloads( 2 );
policy.setMaxUploads( 0 );
policy.setContentLengthRange( range );
CreateAccessTokenRequest request = new CreateAccessTokenRequest().identifier( id ).policy( policy );
CreateAccessTokenResponse response = api.createAccessToken( request );
String content = StreamUtil.readAsString( response.getTokenUrl().openStream() );
Assert.assertEquals( "content from *id* access token doesn't match", content, "hello" );
api.deleteAccessToken( response.getTokenUrl() );
response = api.createAccessToken( new CreateAccessTokenRequest().identifier( path ).policy( policy ) );
content = StreamUtil.readAsString( response.getTokenUrl().openStream() );
Assert.assertEquals( "content from *path* access token doesn't match", content, "hello" );
GetAccessTokenResponse getResponse = api.getAccessToken( response.getTokenUrl() );
AccessToken token = getResponse.getToken();
api.deleteAccessToken( token.getId() );
Assert.assertEquals( "token ID doesn't match",
RestUtil.lastPathElement( response.getTokenUrl().getPath() ),
token.getId() );
policy.setMaxDownloads( policy.getMaxDownloads() - 1 ); // we already used one
Assert.assertEquals( "policy differs", policy, token );
}
@Ignore("Blocked by Bug 30073")
@Test
public void testWriteAccessToken() throws Exception {
Assume.assumeFalse(isVipr);
ObjectPath parentDir = createTestDir("WriteAccessToken");
ObjectPath path = new ObjectPath( parentDir, "write_token_test" );
Calendar expiration = Calendar.getInstance();
expiration.add( Calendar.MINUTE, 10 ); // 10 minutes from now
AccessTokenPolicy.Source source = new AccessTokenPolicy.Source();
source.setAllowList( Arrays.asList( "10.0.0.0/8" ) );
source.setDenyList( Arrays.asList( "1.1.1.1" ) );
AccessTokenPolicy.ContentLengthRange range = new AccessTokenPolicy.ContentLengthRange();
range.setFrom( 0 );
range.setTo( 1024 ); // 1KB
List<AccessTokenPolicy.FormField> formFields = new ArrayList<AccessTokenPolicy.FormField>();
AccessTokenPolicy.FormField formField = new AccessTokenPolicy.FormField();
formField.setName( "x-emc-meta" );
formField.setOptional( true );
formFields.add( formField );
formField = new AccessTokenPolicy.FormField();
formField.setName( "x-emc-listable-meta" );
formField.setOptional( true );
formFields.add( formField );
AccessTokenPolicy policy = new AccessTokenPolicy();
policy.setExpiration( expiration.getTime() );
policy.setSource( source );
policy.setMaxDownloads( 2 );
policy.setMaxUploads( 1 );
policy.setContentLengthRange( range );
policy.setFormFieldList( formFields );
CreateAccessTokenRequest request = new CreateAccessTokenRequest().identifier( path ).policy( policy );
URL tokenUrl = api.createAccessToken( request ).getTokenUrl();
Client client = Client.create();
// prepare upload form
String content = "Anonymous Upload Test";
// note we have to specify content-disposition parameters in a specific order due to bug 27005
FormDataContentDisposition contentDisposition = new ReorderedFormDataContentDisposition(
"form-data; name=\"data\"; filename=\"test.txt\"" );
BodyPart bodyPart = new BodyPart( content, MediaType.TEXT_PLAIN_TYPE ).contentDisposition( contentDisposition );
FormDataMultiPart form = new FormDataMultiPart();
form.field( "x-emc-meta", "color=gray,size=3,foo=bar" )
.field( "x-emc-listable-meta", "listable=" )
.bodyPart( bodyPart );
// upload
ClientResponse clientResponse = client.resource( tokenUrl.toURI() )
.type( MediaType.MULTIPART_FORM_DATA_TYPE )
.post( ClientResponse.class, form );
Assert.assertEquals( "http status from upload is wrong", 201, clientResponse.getStatus() );
ObjectId oid = new ObjectId( RestUtil.lastPathElement( clientResponse.getLocation().getPath() ) );
cleanup.add( oid );
clientResponse = client.resource( tokenUrl.toURI() ).get( ClientResponse.class );
Assert.assertEquals( content, clientResponse.getEntity( String.class ) );
// verify upload/download counts changed
AccessToken token = api.getAccessToken( tokenUrl ).getToken();
Assert.assertEquals( "upload count is wrong", 0, token.getMaxUploads() );
Assert.assertEquals( "download count is wrong", 1, token.getMaxDownloads() );
// read object via standard api (namespace) - just make sure it's there
api.readObject( new ReadObjectRequest().identifier( path ), String.class );
// " " (objectspace)
ReadObjectResponse<String> response = api.readObject( new ReadObjectRequest().identifier( oid ), String.class );
Assert.assertEquals( "content is wrong", content, response.getObject() );
Assert.assertNotNull( "metadata is null", response.getMetadata() );
Assert.assertEquals( "content-type is wrong", "text/plain", response.getMetadata().getContentType() );
Map<String, Metadata> meta = response.getMetadata().getMetadata();
Assert.assertTrue( "color missing from metadata", meta.containsKey( "color" ) );
Assert.assertTrue( "size missing from metadata", meta.containsKey( "size" ) );
Assert.assertTrue( "foo missing from metadata", meta.containsKey( "foo" ) );
api.deleteAccessToken( tokenUrl );
}
@Ignore("Blocked by Bug 30073")
@Test
public void testListAccessTokens() throws Exception {
Assume.assumeFalse(isVipr);
ObjectPath parentDir = createTestDir("ListAccessTokens");
ObjectPath path = new ObjectPath( parentDir, "read_token_test" );
ObjectId id = api.createObject( path, "hello", "text/plain" );
Calendar expiration = Calendar.getInstance();
expiration.add( Calendar.MINUTE, 5 ); // 5 minutes from now
AccessTokenPolicy.Source source = new AccessTokenPolicy.Source();
source.setAllowList( Arrays.asList( "10.0.0.0/8" ) );
source.setDenyList( Arrays.asList( "1.1.1.1" ) );
AccessTokenPolicy.ContentLengthRange range = new AccessTokenPolicy.ContentLengthRange();
range.setFrom( 0 );
range.setTo( 1024 ); // 1KB
AccessTokenPolicy policy = new AccessTokenPolicy();
policy.setExpiration( expiration.getTime() );
policy.setSource( source );
policy.setMaxDownloads( 2 );
policy.setMaxUploads( 0 );
policy.setContentLengthRange( range );
CreateAccessTokenRequest request = new CreateAccessTokenRequest().identifier( id ).policy( policy );
URL tokenUrl1 = api.createAccessToken( request ).getTokenUrl();
request = new CreateAccessTokenRequest().identifier( path ).policy( policy );
URL tokenUrl2 = api.createAccessToken( request ).getTokenUrl();
ListAccessTokensResponse response = api.listAccessTokens( new ListAccessTokensRequest() );
Assert.assertNotNull( "access token list is null", response.getTokens() );
Assert.assertEquals( "access token count wrong", 2, response.getTokens().size() );
AccessToken token = response.getTokens().get( 0 );
Assert.assertEquals( "token ID doesn't match", RestUtil.lastPathElement( tokenUrl1.getPath() ), token.getId() );
Assert.assertEquals( "policy differs", policy, token );
token = response.getTokens().get( 1 );
Assert.assertEquals( "token ID doesn't match", RestUtil.lastPathElement( tokenUrl2.getPath() ), token.getId() );
Assert.assertEquals( "policy differs", policy, token );
}
@Test
public void testDisableSslValidation() throws Exception {
Assume.assumeFalse(isVipr);
config.setDisableSslValidation( true );
api = new AtmosApiClient( config );
List<URI> sslUris = new ArrayList<URI>();
for ( URI uri : config.getEndpoints() ) {
sslUris.add( new URI( "https", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(),
uri.getQuery(), uri.getFragment() ) );
}
config.setEndpoints( sslUris.toArray( new URI[sslUris.size()] ) );
cleanup.add( api.createObject( "Hello SSL!", null ) );
}
@Test
public void testRetryFilter() throws Exception {
final int retries = 3, delay = 500;
final String flagMessage = "XXXXX";
config.setEnableRetry( true );
config.setMaxRetries( retries );
config.setRetryDelayMillis( delay );
api = new AtmosApiClient( config );
CreateObjectRequest request = new CreateObjectRequest().contentLength( 1 ).contentType( "text/plain" );
try {
api.createObject( request.content( new RetryInputStream( config, flagMessage ) ) );
Assert.fail( "Retried more than maxRetries times" );
} catch ( ClientHandlerException e ) {
Assert.assertEquals( "Wrong exception thrown", flagMessage, e.getCause().getMessage() );
}
config.setMaxRetries( retries + 1 );
ObjectId oid = api.createObject( request.content( new RetryInputStream( config, flagMessage ) ) ).getObjectId();
cleanup.add( oid );
byte[] content = api.readObject( oid, null, byte[].class );
Assert.assertEquals( "Content wrong size", 1, content.length );
Assert.assertEquals( "Wrong content", (byte) 65, content[0] );
try {
api.createObject( request.content( new RetryInputStream( null, null ) {
@Override
public int read() throws IOException {
switch ( callCount++ ) {
case 0:
throw new AtmosException( "should not retry", 400 );
case 1:
return 65;
}
return -1;
}
} ) );
Assert.fail( "HTTP 400 was retried and should not be" );
} catch ( ClientHandlerException e ) {
Assert.assertEquals( "Wrong http code", 400, ((AtmosException) e.getCause()).getHttpCode() );
}
try {
api.createObject( request.content( new RetryInputStream( null, null ) {
@Override
public int read() throws IOException {
switch ( callCount++ ) {
case 0:
throw new RuntimeException( flagMessage );
case 1:
return 65;
}
return -1;
}
} ) );
Assert.fail( "RuntimeException was retried and should not be" );
} catch ( ClientHandlerException e ) {
Assert.assertEquals( "Wrong exception message", flagMessage, e.getCause().getMessage() );
}
}
@Test
public void testExpect100Continue() throws Exception {
config.setEnableExpect100Continue( true );
InputStream is = new RandomInputStream( 5 );
CreateObjectRequest request = new CreateObjectRequest().content( is ).contentLength( 5 );
// test success first since some load-balancers screw up the next request after an E: 100-C failure
cleanup.add( api.createObject( request ).getObjectId() );
// now test failure
String tokenId = config.getTokenId();
config.setTokenId( "bogustokenid" );
is = new RandomInputStream( 5 );
try {
api.createObject( request );
} catch ( AtmosException e ) {
Assert.assertEquals( "wrong error code", 1033, e.getErrorCode() );
Assert.assertEquals( "input stream was read", 5, is.available() );
} finally {
config.setTokenId( tokenId );
is.close();
}
}
@Test
public void testMultiThreadedBufferedWriter() throws Exception {
int threadCount = 20;
ThreadPoolExecutor executor = new ThreadPoolExecutor( threadCount, threadCount, 5000, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>() );
// test with String
List<Throwable> errorList = Collections.synchronizedList( new ArrayList<Throwable>() );
for ( int i = 0; i < threadCount; i++ ) {
executor.execute( new ObjectTestThread<String>( "Test thread " + i,
"text/plain",
String.class,
errorList ) );
}
do {
Thread.sleep( 500 );
} while ( executor.getActiveCount() > 0 );
if ( !errorList.isEmpty() ) {
for ( Throwable t : errorList ) t.printStackTrace();
Assert.fail( "At least one thread failed" );
}
// test with JAXB bean
try {
for ( int i = 0; i < threadCount; i++ ) {
executor.execute( new ObjectTestThread<AccessTokenPolicy>( createTestTokenPolicy( "Test thread " + i,
"x.x.x." + i ),
"text/xml",
AccessTokenPolicy.class,
errorList ) );
}
do {
Thread.sleep( 500 );
} while ( executor.getActiveCount() > 0 );
} finally {
executor.shutdown();
}
if ( !errorList.isEmpty() ) {
for ( Throwable t : errorList ) t.printStackTrace();
Assert.fail( "At least one thread failed" );
}
}
@Test
public void testProxyConfiguration() {
AtmosConfig config = AtmosClientFactory.getAtmosConfig();
URI proxyUri = config.getProxyUri();
// don't run this test without a proxy config
Assume.assumeNotNull(proxyUri);
// capture existing system props for safety
String oldProxyHost = System.getProperty("http.proxyHost");
String oldProxyPort = System.getProperty("http.proxyPort");
try {
// just create and delete an object in each scenario
// 1) URLConnection - no proxy
config.setProxyUri(null);
AtmosApi atmos = new AtmosApiBasicClient(config);
ObjectId oid = atmos.createObject("URLConnection with no proxy", "text/plain");
atmos.delete(oid);
// 2) Apache - no proxy
atmos = new AtmosApiClient(config);
oid = atmos.createObject("Apache with no proxy", "text/plain");
atmos.delete(oid);
// 3) URLConnection - with config proxy
config.setProxyUri(proxyUri);
atmos = new AtmosApiBasicClient(config);
oid = atmos.createObject("URLConnection with config proxy", "text/plain");
atmos.delete(oid);
// 4) Apache - with config proxy
atmos = new AtmosApiClient(config);
oid = atmos.createObject("Apache with config proxy", "text/plain");
atmos.delete(oid);
// 5) URLConnection - with system props (old school) proxy
config.setProxyUri(null);
System.setProperty("http.proxyHost", proxyUri.getHost());
System.setProperty("http.proxyPort", ""+proxyUri.getPort());
atmos = new AtmosApiBasicClient(config);
oid = atmos.createObject("URLConnection with sysprop proxy", "text/plain");
atmos.delete(oid);
// 6) Apache - with system props (old school) proxy
// can't specify proxy user/pass in system props
atmos = new AtmosApiClient(config);
oid = atmos.createObject("Apache with sysprop proxy", "text/plain");
atmos.delete(oid);
} finally {
// now play nice and reset old props
if (oldProxyHost != null) System.setProperty("http.proxyHost", oldProxyHost);
if (oldProxyPort != null) System.setProperty("http.proxyPort", oldProxyPort);
}
}
@Test
public void testRetention() throws Exception {
Metadata retention = new Metadata("retentionperiod", "1year", false);
CreateObjectRequest request = new CreateObjectRequest().content(null).userMetadata(retention);
ObjectId oid = api.createObject(request.contentType("text/plain")).getObjectId();
cleanup.add(oid);
Thread.sleep(2000);
// make sure retention is enabled
ObjectInfo info = api.getObjectInfo(oid);
Assume.assumeTrue(info.getRetention().isEnabled());
Calendar newEnd = Calendar.getInstance();
newEnd.setTime(info.getRetainedUntil());
DateFormat iso8601Format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
String retentionEnd = "user.maui.retentionEnd";
newEnd.set(Calendar.HOUR, 0);
newEnd.set(Calendar.MINUTE, 0);
newEnd.set(Calendar.SECOND, 0);
newEnd.set(Calendar.MILLISECOND, 0);
newEnd.add(Calendar.DATE, -1);
try {
api.setUserMetadata(oid, new Metadata(retentionEnd, iso8601Format.format(newEnd.getTime()), false));
Assert.fail("should not be able to shorten retention period");
} catch (AtmosException e) {
Assert.assertEquals("Wrong error code", 1002, e.getErrorCode());
}
// disable retention so we can delete (won't work on compliant subtenants!)
api.setUserMetadata(oid, new Metadata("user.maui.retentionEnable", "false", false));
}
protected String rand8char() {
Random r = new Random();
StringBuilder sb = new StringBuilder( 8 );
for ( int i = 0; i < 8; i++ ) {
sb.append( (char) ('a' + r.nextInt( 26 )) );
}
return sb.toString();
}
private AccessTokenPolicy createTestTokenPolicy( String allow, String deny ) {
AccessTokenPolicy.Source source = new AccessTokenPolicy.Source();
source.setAllowList( Arrays.asList( allow ) );
source.setDenyList( Arrays.asList( deny ) );
AccessTokenPolicy policy = new AccessTokenPolicy();
policy.setExpiration( new Date( 1355897000000L ) );
policy.setMaxDownloads( 5 );
policy.setMaxUploads( 10 );
policy.setSource( source );
return policy;
}
private class RetryInputStream extends InputStream {
protected int callCount = 0;
private long now;
private long lastTime;
private AtmosConfig config;
private String flagMessage;
public RetryInputStream( AtmosConfig config, String flagMessage ) {
this.config = config;
this.flagMessage = flagMessage;
}
@Override
public int read() throws IOException {
switch ( callCount++ ) {
case 0:
lastTime = System.currentTimeMillis();
throw new AtmosException( "foo", 500 );
case 1:
now = System.currentTimeMillis();
Assert.assertTrue( "Retry delay for 500 error was not honored",
now - lastTime >= config.getRetryDelayMillis() );
lastTime = now;
throw new AtmosException( "bar", 500, 1040 );
case 2:
now = System.currentTimeMillis();
Assert.assertTrue( "Retry delay for 1040 error was not honored",
now - lastTime >= config.getRetryDelayMillis() + 300 );
lastTime = now;
throw new IOException( "baz" );
case 3:
now = System.currentTimeMillis();
Assert.assertTrue( "Retry delay for IOException was not honored",
now - lastTime >= config.getRetryDelayMillis() );
lastTime = now;
throw new AtmosException( flagMessage, 500 );
case 4:
return 65;
}
return -1;
}
@Override
public synchronized void reset() throws IOException {
}
@Override
public boolean markSupported() {
return true;
}
}
private class ObjectTestThread<T> implements Runnable {
private T content;
private String contentType;
private Class<T> objectType;
private List<Throwable> errorList;
public ObjectTestThread( T content,
String contentType,
Class<T> objectType,
List<Throwable> errorList ) {
this.content = content;
this.contentType = contentType;
this.objectType = objectType;
this.errorList = errorList;
}
@Override
public void run() {
try {
ObjectId oid = api.createObject( content, contentType );
cleanup.add( oid );
T readContent = api.readObject( oid, null, objectType );
Assert.assertEquals( "Content for object " + oid + " not equal", content, readContent );
} catch ( Throwable t ) {
errorList.add( t );
}
}
}
}