Skip to content

Commit

Permalink
Additional support for storage classes
Browse files Browse the repository at this point in the history
- String constants to avoid clients having to hard-code names
- Remove restriction on CopyObject against copying an object to itself (it works and can be useful)
- Add the ability to specify extra metadata when copying an object, e.g. to change storage class
- A test demonstrating various aspects of storage classes
  • Loading branch information
jskeet committed Feb 9, 2017
1 parent 4d634e5 commit aafa37a
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
using System;
using System.Linq;
using Xunit;
using Object = Google.Apis.Storage.v1.Data.Object;

namespace Google.Cloud.Storage.V1.IntegrationTests
{
Expand All @@ -30,14 +31,6 @@ public CopyObjectTest(StorageFixture fixture)
_fixture = fixture;
}

[Fact]
public void DestinationEqualsSourceIsProhibited()
{
var bucket = _fixture.ReadBucket;
var name = _fixture.SmallObject;
Assert.Throws<ArgumentException>(() => _fixture.Client.CopyObject(bucket, name, bucket, name));
}

[Fact]
public void CopySpecificGeneration()
{
Expand Down Expand Up @@ -70,5 +63,28 @@ public void CopyLatestGeneration()

ValidateData(_fixture.SingleVersionBucket, destName, _fixture.LargeContent);
}

[Fact]
public void NoMetadataOverride()
{
var destName = GenerateName();
_fixture.Client.CopyObject(
_fixture.ReadBucket, _fixture.SmallThenLargeObject,
_fixture.SingleVersionBucket, destName);
Object fetched = _fixture.Client.GetObject(_fixture.SingleVersionBucket, destName);
Assert.Equal("text/plain", fetched.ContentType);
}

[Fact]
public void MetadataOverride()
{
var destName = GenerateName();
_fixture.Client.CopyObject(
_fixture.ReadBucket, _fixture.SmallThenLargeObject,
_fixture.SingleVersionBucket, destName,
new CopyObjectOptions { ExtraMetadata = new Object { ContentType = "text/html" } });
Object fetched = _fixture.Client.GetObject(_fixture.SingleVersionBucket, destName);
Assert.Equal("text/html", fetched.ContentType);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Apis.Storage.v1.Data;
using Xunit;
using Object = Google.Apis.Storage.v1.Data.Object;

namespace Google.Cloud.Storage.V1.IntegrationTests
{
[Collection(nameof(StorageFixture))]
public class StorageClassTest
{
private readonly StorageFixture _fixture;

public StorageClassTest(StorageFixture fixture)
{
_fixture = fixture;
}

// Multi-step test to check:
// - Create a bucket with a storage class of regional
// - That storage class is used when creating a new object
// - If we rewrite the object to a different storage class, that change sticks
// - If we update the bucket's default storage class, creating a new object uses that new class
[Fact]
public void CreateBucketAndObjects()
{
// Note: this test may fail if the project location prevents multi-regional storage.
string initialBucketStorageClass = StorageClasses.MultiRegional;
string updatedObjectStorageClass = StorageClasses.Coldline;
string updatedBucketStorageClass = StorageClasses.Nearline;

string bucketName = _fixture.BucketPrefix + "storage-classes";
string objectName = TestHelpers.GenerateName();
var client = _fixture.Client;

client.CreateBucket(_fixture.ProjectId, new Bucket { Name = bucketName, StorageClass = initialBucketStorageClass });
_fixture.RegisterBucketToDelete(bucketName);

var bucket = client.GetBucket(bucketName);
Assert.Equal(initialBucketStorageClass, bucket.StorageClass);

var obj = client.UploadObject(bucketName, objectName, "application/octet-stream", TestHelpers.GenerateData(100));
Assert.Equal(initialBucketStorageClass, obj.StorageClass);

// Change storage class via a rewrite
var options = new CopyObjectOptions
{
ExtraMetadata = new Object { StorageClass = updatedObjectStorageClass }
};
client.CopyObject(bucketName, objectName, bucketName, objectName, options);

// Fetch separately rather than trusting the return value of CopyObject...
obj = client.GetObject(bucketName, objectName);
Assert.Equal(updatedObjectStorageClass, obj.StorageClass);

bucket.StorageClass = updatedBucketStorageClass;
client.UpdateBucket(bucket);
string objectName2 = TestHelpers.GenerateName();

var obj2 = client.UploadObject(bucketName, objectName2, "application/octet-stream", TestHelpers.GenerateData(100));
Assert.Equal(updatedBucketStorageClass, obj2.StorageClass);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System;
using static Google.Apis.Storage.v1.ObjectsResource;
using static Google.Apis.Storage.v1.ObjectsResource.RewriteRequest;
using Object = Google.Apis.Storage.v1.Data.Object;

namespace Google.Cloud.Storage.V1
{
Expand Down Expand Up @@ -90,6 +91,13 @@ public sealed class CopyObjectOptions
/// </summary>
public long? IfSourceMetagenerationNotMatch { get; set; }

/// <summary>
/// Additional object metadata for the new object. This can be used to specify the storage
/// class of the new object, the content type etc. If this property is not set, the existing
/// object metadata will be used unchanged.
/// </summary>
public Object ExtraMetadata { get; set; }

internal void ModifyRequest(RewriteRequest request)
{
// Note the use of ArgumentException here, as this will basically be the result of invalid
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Apis.Storage.v1.Data;

namespace Google.Cloud.Storage.V1
{
/// <summary>
/// String constants for the names of the storage classes, as used in <see cref="Bucket.StorageClass"/>
/// and <see cref="Object.StorageClass"/>. See https://cloud.google.com/storage/docs/storage-classes for details.
/// </summary>
public static class StorageClasses
{
/// <summary>
/// Name for the Multi-Regional storage class.
/// </summary>
public const string MultiRegional = "MULTI_REGIONAL";

/// <summary>
/// Name for the Regional storage class.
/// </summary>
public const string Regional = "REGIONAL";

/// <summary>
/// Name for the Nearline storage class.
/// </summary>
public const string Nearline = "NEARLINE";

/// <summary>
/// Name for the Coldline storage class.
/// </summary>
public const string Coldline = "COLDLINE";

/// <summary>
/// Name of the Standard storage class, which is equivalent to
/// Multi-Regional or Regional depending on the location.
/// </summary>
public const string Standard = "STANDARD";

/// <summary>
/// Name of the Durable Reduced Availability (DRA) storage class.
/// Use of this storage class is not recommended; Regional storage has
/// lower pricing for some operations but otherwise the same pricing structure,
/// and better performance/availability.
/// </summary>
public const string DurableReducedAvailability = "DURABLE_REDUCED_AVAILABILITY";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public abstract partial class StorageClient
/// <param name="destinationObjectName">The name of the object within the destination bucket. Must not be null.</param>
/// <param name="options">Additional options for the fetch operation. May be null, in which case appropriate
/// defaults will be used.</param>
/// <exception cref="ArgumentException">The arguments attempt to copy an object to itself.</exception>
/// <returns>The <see cref="Object"/> representation of the new storage object resulting from the copy.</returns>
public virtual Object CopyObject(
string sourceBucket,
Expand All @@ -53,7 +52,6 @@ public virtual Object CopyObject(
/// <param name="destinationObjectName">The name of the object within the destination bucket. Must not be null.</param>
/// <param name="options">Additional options for the fetch operation. May be null, in which case appropriate
/// defaults will be used.</param>
/// <exception cref="ArgumentException">The arguments attempt to copy an object to itself.</exception>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task representing the asynchronous operation, with a result returning the
/// <see cref="Object"/> representation of the new storage object resulting from the copy.</returns>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,8 @@ private ObjectsResource.RewriteRequest CreateCopyObjectRequest(
GaxPreconditions.CheckNotNull(sourceObjectName, nameof(sourceObjectName));
GaxPreconditions.CheckNotNull(destinationBucket, nameof(destinationBucket));
GaxPreconditions.CheckNotNull(destinationObjectName, nameof(destinationObjectName));
if (destinationBucket == sourceBucket && destinationObjectName == sourceObjectName)
{
throw new ArgumentException("Cannot copy an object to itself. Specify either a different destination bucket or a different destination object name");
}
var request = Service.Objects.Rewrite(new Object(), sourceBucket, sourceObjectName, destinationBucket, destinationObjectName);
Object obj = options?.ExtraMetadata ?? new Object();
var request = Service.Objects.Rewrite(obj, sourceBucket, sourceObjectName, destinationBucket, destinationObjectName);
options?.ModifyRequest(request);
return request;
}
Expand Down

0 comments on commit aafa37a

Please sign in to comment.