Skip to content

Commit caf7ee5

Browse files
feat: Dual Region Support (#1814)
* feat: * feat: Custom Dual Region Support * test: Custom Dual Region Tests with logging * feat: Add Custom Dual Region sample * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * chore: typo * test: Add tests for `location` as an Array * refactor: Rename - drop 'Custom' from 'Dual Regions' * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * style: Drop 'Custom' prefix * revert: Remove tuple support Avoids confusion as the returned value from the backend is a string * refactor: Rename variables from `locationX` to `regionX` * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * test: Update metadata assertions * test: Update tests to avoid name collision * test: debug test * test: fix test * chore: test immediately deleting a specified dual-region bucket * chore: undo delete debug * docs: 'dual region' -> 'dual-region' * docs: Fix `bucket-locations` -> `locations` link Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 91fe5ed commit caf7ee5

File tree

7 files changed

+152
-17
lines changed

7 files changed

+152
-17
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-storage/tre
114114
| Configure Retries | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/configureRetries.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/configureRetries.js,samples/README.md) |
115115
| Copy File | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/copyFile.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/copyFile.js,samples/README.md) |
116116
| Copy Old Version Of File. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/copyOldVersionOfFile.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/copyOldVersionOfFile.js,samples/README.md) |
117+
| Create a Dual-Region Bucket | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/createBucketWithDualRegion.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/createBucketWithDualRegion.js,samples/README.md) |
117118
| Create Bucket With Storage Class and Location. | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/createBucketWithStorageClassAndLocation.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/createBucketWithStorageClassAndLocation.js,samples/README.md) |
118119
| Create Bucket With Turbo Replication | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/createBucketWithTurboReplication.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/createBucketWithTurboReplication.js,samples/README.md) |
119120
| Create New Bucket | [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/createNewBucket.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/createNewBucket.js,samples/README.md) |

samples/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ objects to users via direct download.
3333
* [Configure Retries](#configure-retries)
3434
* [Copy File](#copy-file)
3535
* [Copy Old Version Of File.](#copy-old-version-of-file.)
36+
* [Create a Dual-Region Bucket](#create-a-dual-region-bucket)
3637
* [Create Bucket With Storage Class and Location.](#create-bucket-with-storage-class-and-location.)
3738
* [Create Bucket With Turbo Replication](#create-bucket-with-turbo-replication)
3839
* [Create New Bucket](#create-new-bucket)
@@ -413,6 +414,25 @@ __Usage:__
413414

414415

415416

417+
### Create a Dual-Region Bucket
418+
419+
Create a Dual-Region Bucket with provided locations.
420+
421+
View the [source code](https://github.com/googleapis/nodejs-storage/blob/main/samples/createBucketWithDualRegion.js).
422+
423+
[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-storage&page=editor&open_in_editor=samples/createBucketWithDualRegion.js,samples/README.md)
424+
425+
__Usage:__
426+
427+
428+
`node createBucketWithDualRegion.js <BUCKET_NAME> <REGION1> <REGION2>`
429+
430+
431+
-----
432+
433+
434+
435+
416436
### Create Bucket With Storage Class and Location.
417437

418438
Create Bucket With Storage Class and Location.

samples/createBucketWithDualRegion.js

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* Copyright 2022 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
'use strict';
18+
19+
// sample-metadata:
20+
// title: Create a Dual-Region Bucket
21+
// description: Create a Dual-Region Bucket with provided locations.
22+
// usage: node createBucketWithDualRegion.js <BUCKET_NAME> <REGION1> <REGION2>
23+
24+
function main(
25+
bucketName = 'my-bucket',
26+
region1 = 'US-EAST1',
27+
region2 = 'US-WEST1'
28+
) {
29+
// [START storage_create_bucket_dual_region]
30+
/**
31+
* TODO(developer): Uncomment the following lines before running the sample.
32+
*/
33+
// The ID of your GCS bucket
34+
// const bucketName = 'your-unique-bucket-name';
35+
36+
// The bucket's pair of regions. Case-insensitive.
37+
// See this documentation for other valid locations:
38+
// https://cloud.google.com/storage/docs/locations
39+
// const region1 = 'US-EAST1';
40+
// const region2 = 'US-WEST1';
41+
42+
// Imports the Google Cloud client library
43+
const {Storage} = require('@google-cloud/storage');
44+
45+
// Creates a client
46+
// The bucket in the sample below will be created in the project associated with this client.
47+
// For more information, please see https://cloud.google.com/docs/authentication/production or https://googleapis.dev/nodejs/storage/latest/Storage.html
48+
const storage = new Storage();
49+
50+
async function createDualRegionBucket() {
51+
// For regions supporting dual-regions see: https://cloud.google.com/storage/docs/locations
52+
const [bucket] = await storage.createBucket(bucketName, {
53+
location: `${region1}+${region2}`, // e.g. `US-EAST1+US-WEST1`
54+
});
55+
56+
console.log(`${bucket.name} created in '${region1}+${region2}'`);
57+
}
58+
59+
createDualRegionBucket().catch(console.error);
60+
// [END storage_create_bucket_dual_region]
61+
}
62+
63+
process.on('unhandledRejection', err => {
64+
console.error(err.message);
65+
process.exitCode = 1;
66+
});
67+
main(...process.argv.slice(2));

samples/createBucketWithStorageClassAndLocation.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function main(
4040

4141
// The name of a location
4242
// See this documentation for other valid locations:
43-
// http://g.co/cloud/storage/docs/bucket-locations#location-mr
43+
// http://g.co/cloud/storage/docs/locations#location-mr
4444
// const location = 'ASIA';
4545

4646
// Imports the Google Cloud client library

samples/system-test/buckets.test.js

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,17 +26,20 @@ const storage = new Storage();
2626
const samplesTestBucketPrefix = `nodejs-storage-samples-${uuid.v4()}`;
2727
const bucketName = `${samplesTestBucketPrefix}-a`;
2828
const bucketNameDualRegion = `${samplesTestBucketPrefix}-b`;
29-
const bucketNameWithClassAndLocation = `${samplesTestBucketPrefix}-c`;
29+
const bucketNameDualRegionTurbo = `${samplesTestBucketPrefix}-c`;
30+
const bucketNameWithClassAndLocation = `${samplesTestBucketPrefix}-d`;
3031
const defaultKmsKeyName = process.env.GOOGLE_CLOUD_KMS_KEY_ASIA;
3132
const bucket = storage.bucket(bucketName);
3233
const bucketWithClassAndLocation = storage.bucket(
3334
bucketNameWithClassAndLocation
3435
);
3536
const dualRegionBucket = storage.bucket(bucketNameDualRegion);
37+
const dualRegionBucketTurbo = storage.bucket(bucketNameDualRegionTurbo);
3638

3739
const PUBLIC_ACCESS_PREVENTION_INHERITED = 'inherited';
3840
const PUBLIC_ACCESS_PREVENTION_ENFORCED = 'enforced';
3941

42+
const DUAL_REGION = ['US-EAST1', 'US-WEST1'];
4043
const RPO_ASYNC_TURBO = 'ASYNC_TURBO';
4144
const RPO_DEFAULT = 'DEFAULT';
4245

@@ -212,54 +215,73 @@ it('should set public access prevention to inherited', async () => {
212215
);
213216
});
214217

218+
it('should create a dual-region bucket', async () => {
219+
const dualRegion = `${DUAL_REGION[0]}+${DUAL_REGION[1]}`;
220+
221+
const output = execSync(
222+
`node createBucketWithDualRegion.js ${bucketNameDualRegion} ${DUAL_REGION[0]} ${DUAL_REGION[1]}`
223+
);
224+
225+
assert.include(output, `${bucketNameDualRegion} created in '${dualRegion}'`);
226+
227+
const [exists] = await dualRegionBucket.exists();
228+
assert.strictEqual(exists, true);
229+
230+
const [metadata] = await dualRegionBucket.getMetadata();
231+
assert.strictEqual(metadata.location, dualRegion);
232+
assert.strictEqual(metadata.locationType, 'dual-region');
233+
});
234+
215235
it('should create a dual-region bucket with turbo replication enabled', async () => {
216236
const output = execSync(
217-
`node createBucketWithTurboReplication.js ${bucketNameDualRegion}`
237+
`node createBucketWithTurboReplication.js ${bucketNameDualRegionTurbo}`
218238
);
219239
assert.match(
220240
output,
221241
new RegExp(
222-
`${bucketNameDualRegion} created with the recovery point objective \\(RPO\\) set to ASYNC_TURBO in NAM4.`
242+
`${bucketNameDualRegionTurbo} created with the recovery point objective \\(RPO\\) set to ASYNC_TURBO in NAM4.`
223243
)
224244
);
225-
const [exists] = await dualRegionBucket.exists();
245+
const [exists] = await dualRegionBucketTurbo.exists();
226246
assert.strictEqual(exists, true);
227247
});
228248

229249
it("should get a bucket's RPO metadata", async () => {
230-
await storage.bucket(bucketNameDualRegion).setMetadata({
250+
await storage.bucket(bucketNameDualRegionTurbo).setMetadata({
231251
rpo: RPO_ASYNC_TURBO,
232252
});
233253

234-
const output = execSync(`node getRPO.js ${bucketNameDualRegion}`);
254+
const output = execSync(`node getRPO.js ${bucketNameDualRegionTurbo}`);
235255
assert.match(
236256
output,
237-
new RegExp(`RPO is ASYNC_TURBO for ${bucketNameDualRegion}.`)
257+
new RegExp(`RPO is ASYNC_TURBO for ${bucketNameDualRegionTurbo}.`)
238258
);
239259

240-
const metadata = await dualRegionBucket.getMetadata();
260+
const metadata = await dualRegionBucketTurbo.getMetadata();
241261
assert.strictEqual(metadata[0].rpo, RPO_ASYNC_TURBO);
242262
});
243263

244264
it("should set a bucket's RPO to ASYNC_TURBO", async () => {
245-
const output = execSync(`node setRPOAsyncTurbo.js ${bucketNameDualRegion}`);
265+
const output = execSync(
266+
`node setRPOAsyncTurbo.js ${bucketNameDualRegionTurbo}`
267+
);
246268
assert.match(
247269
output,
248-
new RegExp(`Turbo replication enabled for ${bucketNameDualRegion}.`)
270+
new RegExp(`Turbo replication enabled for ${bucketNameDualRegionTurbo}.`)
249271
);
250272

251-
const metadata = await dualRegionBucket.getMetadata();
273+
const metadata = await dualRegionBucketTurbo.getMetadata();
252274
assert.strictEqual(metadata[0].rpo, RPO_ASYNC_TURBO);
253275
});
254276

255277
it("should set a bucket's RPO to DEFAULT", async () => {
256-
const output = execSync(`node setRPODefault.js ${bucketNameDualRegion}`);
278+
const output = execSync(`node setRPODefault.js ${bucketNameDualRegionTurbo}`);
257279
assert.match(
258280
output,
259-
new RegExp(`Turbo replication disabled for ${bucketNameDualRegion}.`)
281+
new RegExp(`Turbo replication disabled for ${bucketNameDualRegionTurbo}.`)
260282
);
261283

262-
const metadata = await dualRegionBucket.getMetadata();
284+
const metadata = await dualRegionBucketTurbo.getMetadata();
263285
assert.strictEqual(metadata[0].rpo, RPO_DEFAULT);
264286
});
265287

src/storage.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -753,15 +753,17 @@ export class Storage extends Service {
753753
* @property {Cors[]} [cors=[]] Specify the CORS configuration to use.
754754
* @property {boolean} [dra=false] Specify the storage class as Durable Reduced
755755
* Availability.
756-
* @property {string} [location] Specify the location / region in which to create the bucket.
756+
* @property {string} [location] Specify the bucket's location(s). If specifying
757+
* a dual-region, can be specified as a string `"US-CENTRAL1+US-WEST1"`.
758+
* For more information, see {@link https://cloud.google.com/storage/docs/locations| Bucket Locations}.
757759
* @property {boolean} [multiRegional=false] Specify the storage class as
758760
* Multi-Regional.
759761
* @property {boolean} [nearline=false] Specify the storage class as Nearline.
760762
* @property {boolean} [regional=false] Specify the storage class as Regional.
761763
* @property {boolean} [requesterPays=false] **Early Access Testers Only**
762764
* Force the use of the User Project metadata field to assign operational
763765
* costs when an operation is made on a Bucket and its objects.
764-
* @property {string} [rpo] For dual region buckets, controls whether turbo
766+
* @property {string} [rpo] For dual-region buckets, controls whether turbo
765767
* replication is enabled (`ASYNC_TURBO`) or disabled (`DEFAULT`).
766768
* @property {boolean} [standard=true] Specify the storage class as Standard.
767769
* @property {string} [storageClass] The new storage class. (`standard`,

system-test/storage.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,29 @@ describe('storage', () => {
929929
});
930930
});
931931

932+
describe('dual-region', () => {
933+
let bucket: Bucket;
934+
935+
const REGION1 = 'US-EAST1';
936+
const REGION2 = 'US-WEST1';
937+
938+
beforeEach(() => {
939+
bucket = storage.bucket(generateName());
940+
});
941+
942+
it('creates a dual-region bucket', async () => {
943+
const dualRegion = `${REGION1}+${REGION2}`;
944+
await bucket.create({location: dualRegion});
945+
946+
const [exists] = await bucket.exists();
947+
assert.strictEqual(exists, true);
948+
949+
const [bucketMetadata] = await bucket.getMetadata();
950+
assert.strictEqual(bucketMetadata.location, dualRegion);
951+
assert.strictEqual(bucketMetadata.locationType, 'dual-region');
952+
});
953+
});
954+
932955
describe('uniform bucket-level access', () => {
933956
let bucket: Bucket;
934957

0 commit comments

Comments
 (0)