diff --git a/storage/bucket.go b/storage/bucket.go index 101f695b5a6a..c3ba89942fd2 100644 --- a/storage/bucket.go +++ b/storage/bucket.go @@ -645,6 +645,13 @@ const ( // SetStorageClassAction changes the storage class of live and/or archived // objects. SetStorageClassAction = "SetStorageClass" + + // AbortIncompleteMPUAction is a lifecycle action that aborts an incomplete + // multipart upload when the multipart upload meets the conditions specified + // in the lifecycle rule. The AgeInDays condition is the only allowed + // condition for this action. AgeInDays is measured from the time the + // multipart upload was created. + AbortIncompleteMPUAction = "AbortIncompleteMultipartUpload" ) // LifecycleRule is a lifecycle configuration rule. @@ -665,9 +672,8 @@ type LifecycleRule struct { type LifecycleAction struct { // Type is the type of action to take on matching objects. // - // Acceptable values are "Delete" to delete matching objects and - // "SetStorageClass" to set the storage class defined in StorageClass on - // matching objects. + // Acceptable values are storage.DeleteAction, storage.SetStorageClassAction, + // and storage.AbortIncompleteMPUAction. Type string // StorageClass is the storage class to set on matching objects if the Action diff --git a/storage/bucket_test.go b/storage/bucket_test.go index f1fc510bd2b0..2accecf9797b 100644 --- a/storage/bucket_test.go +++ b/storage/bucket_test.go @@ -103,6 +103,13 @@ func TestBucketAttrsToRawBucket(t *testing.T) { Condition: LifecycleCondition{ Liveness: Archived, }, + }, { + Action: LifecycleAction{ + Type: AbortIncompleteMPUAction, + }, + Condition: LifecycleCondition{ + AgeInDays: 20, + }, }}, }, } @@ -184,6 +191,13 @@ func TestBucketAttrsToRawBucket(t *testing.T) { Condition: &raw.BucketLifecycleRuleCondition{ IsLive: googleapi.Bool(false), }, + }, { + Action: &raw.BucketLifecycleRuleAction{ + Type: AbortIncompleteMPUAction, + }, + Condition: &raw.BucketLifecycleRuleCondition{ + Age: 20, + }, }}, }, } @@ -329,6 +343,10 @@ func TestBucketAttrsToUpdateToRawBucket(t *testing.T) { Action: LifecycleAction{Type: "Delete"}, Condition: LifecycleCondition{AgeInDays: 30}, }, + { + Action: LifecycleAction{Type: AbortIncompleteMPUAction}, + Condition: LifecycleCondition{AgeInDays: 13}, + }, }, }, Logging: &BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, @@ -368,6 +386,10 @@ func TestBucketAttrsToUpdateToRawBucket(t *testing.T) { Action: &raw.BucketLifecycleRuleAction{Type: "Delete"}, Condition: &raw.BucketLifecycleRuleCondition{Age: 30}, }, + { + Action: &raw.BucketLifecycleRuleAction{Type: AbortIncompleteMPUAction}, + Condition: &raw.BucketLifecycleRuleCondition{Age: 13}, + }, }, }, Logging: &raw.BucketLogging{LogBucket: "lb", LogObjectPrefix: "p"}, diff --git a/storage/integration_test.go b/storage/integration_test.go index 955b6d6feda5..a72d5cc466e3 100644 --- a/storage/integration_test.go +++ b/storage/integration_test.go @@ -410,6 +410,49 @@ func TestIntegration_BucketCreateDelete(t *testing.T) { } } +func TestIntegration_BucketLifecycle(t *testing.T) { + ctx := context.Background() + client := testConfig(ctx, t) + defer client.Close() + h := testHelper{t} + + wantLifecycle := Lifecycle{ + Rules: []LifecycleRule{ + { + Action: LifecycleAction{Type: AbortIncompleteMPUAction}, + Condition: LifecycleCondition{AgeInDays: 30}, + }, + }, + } + + bucket := client.Bucket(uidSpace.New()) + + // Create bucket with lifecycle rules + bucket.Create(ctx, testutil.ProjID(), &BucketAttrs{ + Lifecycle: wantLifecycle, + }) + defer h.mustDeleteBucket(bucket) + + attrs := h.mustBucketAttrs(bucket) + if !testutil.Equal(attrs.Lifecycle, wantLifecycle) { + t.Fatalf("got %v, want %v", attrs.Lifecycle, wantLifecycle) + } + + // Remove lifecycle rules + ua := BucketAttrsToUpdate{Lifecycle: &Lifecycle{}} + attrs = h.mustUpdateBucket(bucket, ua, attrs.MetaGeneration) + if !testutil.Equal(attrs.Lifecycle, Lifecycle{}) { + t.Fatalf("got %v, want %v", attrs.Lifecycle, Lifecycle{}) + } + + // Update bucket with a lifecycle rule + ua = BucketAttrsToUpdate{Lifecycle: &wantLifecycle} + attrs = h.mustUpdateBucket(bucket, ua, attrs.MetaGeneration) + if !testutil.Equal(attrs.Lifecycle, wantLifecycle) { + t.Fatalf("got %v, want %v", attrs.Lifecycle, wantLifecycle) + } +} + func TestIntegration_BucketUpdate(t *testing.T) { ctx := context.Background() client := testConfig(ctx, t)