From 05c213522a32627186ad9b474b416c1b9996df1f Mon Sep 17 00:00:00 2001 From: skuruppu Date: Wed, 30 Jun 2021 10:48:10 +1000 Subject: [PATCH] feat: create instances with processing units (#1279) Only one of processing units or nodes should be specified. --- samples/instance-with-processing-units.js | 67 +++++++++++++++++++++++ samples/instance.js | 13 +++++ samples/system-test/spanner.test.js | 25 ++++++++- src/index.ts | 15 ++++- test/index.ts | 27 +++++++++ test/mockserver/mockinstanceadmin.ts | 3 + test/spanner.ts | 23 ++++++++ 7 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 samples/instance-with-processing-units.js diff --git a/samples/instance-with-processing-units.js b/samples/instance-with-processing-units.js new file mode 100644 index 000000000..8d47e87d3 --- /dev/null +++ b/samples/instance-with-processing-units.js @@ -0,0 +1,67 @@ +/** + * Copyright 2021 Google LLC + * 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. + */ + +'use strict'; + +async function createInstanceWithProcessingUnits(instanceId, projectId) { + // [START spanner_create_instance_with_processing_units] + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instance = spanner.instance(instanceId); + + // Creates a new instance + try { + console.log(`Creating instance ${instance.formattedName_}.`); + const [, operation] = await instance.create({ + config: 'regional-us-central1', + processingUnits: 500, + displayName: 'This is a display name.', + labels: { + ['cloud_spanner_samples']: 'true', + }, + }); + + console.log(`Waiting for operation on ${instance.id} to complete...`); + await operation.promise(); + + console.log(`Created instance ${instanceId}.`); + + const [metadata] = await instance.getMetadata({ + fieldNames: ['processingUnits'], + }); + console.log( + `Instance ${instanceId} has ${metadata.processingUnits} ` + + 'processing units.' + ); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance_with_processing_units] +} + +module.exports.createInstanceWithProcessingUnits = + createInstanceWithProcessingUnits; diff --git a/samples/instance.js b/samples/instance.js index 4f12334bd..06a7cd556 100644 --- a/samples/instance.js +++ b/samples/instance.js @@ -56,6 +56,10 @@ async function createInstance(instanceId, projectId) { // [END spanner_create_instance] } +const { + createInstanceWithProcessingUnits, +} = require('./instance-with-processing-units'); + require('yargs') .demand(1) .command( @@ -65,6 +69,15 @@ require('yargs') opts => createInstance(opts.instanceName, opts.projectId) ) .example('node $0 createInstance "my-instance" "my-project-id"') + .command( + 'createInstanceWithProcessingUnits ', + 'Creates an example instance in a Cloud Spanner instance with processing units.', + {}, + opts => createInstanceWithProcessingUnits(opts.instanceName, opts.projectId) + ) + .example( + 'node $0 createInstanceWithProcessingUnits "my-instance" "my-project-id"' + ) .wrap(120) .recommendCommands() .epilogue('For more information, see https://cloud.google.com/spanner/docs') diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index 84645f206..4ca6929b7 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -17,7 +17,7 @@ const {Spanner} = require('@google-cloud/spanner'); const {KeyManagementServiceClient} = require('@google-cloud/kms'); const {assert} = require('chai'); -const {describe, it, before, after} = require('mocha'); +const {describe, it, before, after, afterEach} = require('mocha'); const cp = require('child_process'); const pLimit = require('p-limit'); @@ -227,7 +227,7 @@ describe('Spanner', () => { }); describe('instance', () => { - after(async () => { + afterEach(async () => { const sample_instance = spanner.instance(SAMPLE_INSTANCE_ID); await sample_instance.delete(); }); @@ -248,6 +248,27 @@ describe('Spanner', () => { new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`) ); }); + + // create_instance_with_processing_units + it('should create an example instance with processing units', async () => { + const output = execSync( + `${instanceCmd} createInstanceWithProcessingUnits "${SAMPLE_INSTANCE_ID}" ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for operation on ${SAMPLE_INSTANCE_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp(`Created instance ${SAMPLE_INSTANCE_ID}.`) + ); + assert.match( + output, + new RegExp(`Instance ${SAMPLE_INSTANCE_ID} has 500 processing units.`) + ); + }); }); // check that base instance was created diff --git a/src/index.ts b/src/index.ts index 32172c941..861793b84 100644 --- a/src/index.ts +++ b/src/index.ts @@ -92,6 +92,7 @@ export interface RequestConfig { export interface CreateInstanceRequest { config?: string; nodes?: number; + processingUnits?: number; displayName?: string; labels?: {[k: string]: string} | null; gaxOptions?: CallOptions; @@ -383,12 +384,24 @@ class Spanner extends GrpcService { { name: formattedName, displayName, - nodeCount: config.nodes || 1, + nodeCount: config.nodes, + processingUnits: config.processingUnits, }, config ), }; + if (reqOpts.instance.nodeCount && reqOpts.instance.processingUnits) { + throw new Error( + ['Only one of nodeCount or processingUnits can be specified.'].join('') + ); + } + if (!reqOpts.instance.nodeCount && !reqOpts.instance.processingUnits) { + // If neither nodes nor processingUnits are specified, default to a + // nodeCount of 1. + reqOpts.instance.nodeCount = 1; + } + delete reqOpts.instance.nodes; delete reqOpts.instance.gaxOptions; diff --git a/test/index.ts b/test/index.ts index 4dcf32534..021dc0bcd 100644 --- a/test/index.ts +++ b/test/index.ts @@ -557,6 +557,7 @@ describe('Spanner', () => { name: PATH, displayName: NAME, nodeCount: 1, + processingUnits: undefined, config: `projects/project-id/instanceConfigs/${CONFIG.config}`, }, }); @@ -587,6 +588,32 @@ describe('Spanner', () => { spanner.createInstance(NAME, config, assert.ifError); }); + it('should create an instance with processing units', done => { + const processingUnits = 500; + const config = Object.assign({}, CONFIG, {processingUnits}); + + spanner.request = config => { + assert.strictEqual( + config.reqOpts.instance.processingUnits, + processingUnits + ); + assert.strictEqual(config.reqOpts.instance.nodeCount, undefined); + done(); + }; + + spanner.createInstance(NAME, config, assert.ifError); + }); + + it('should throw if both nodes and processingUnits are given', () => { + const nodeCount = 1; + const processingUnits = 500; + const config = Object.assign({}, CONFIG, {nodeCount, processingUnits}); + + assert.throws(() => { + spanner.createInstance(NAME, config); + }, /Only one of nodeCount or processingUnits can be specified\./); + }); + it('should accept gaxOptions', done => { const cfg = Object.assign({}, CONFIG, {gaxOptions: {}}); spanner.request = config => { diff --git a/test/mockserver/mockinstanceadmin.ts b/test/mockserver/mockinstanceadmin.ts index 2d884aabb..aa9f2616c 100644 --- a/test/mockserver/mockinstanceadmin.ts +++ b/test/mockserver/mockinstanceadmin.ts @@ -209,6 +209,9 @@ export class MockInstanceAdmin { nodeCount: call.request!.instance ? call.request!.instance.nodeCount : undefined, + processingUnits: call.request!.instance + ? call.request!.instance.processingUnits + : undefined, labels: call.request!.instance ? call.request!.instance.labels : undefined, diff --git a/test/spanner.ts b/test/spanner.ts index 223f6e5a8..45941c06d 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -3288,6 +3288,29 @@ describe('Spanner with mock server', () => { ); }); + it('should create an instance with processing units', async () => { + const [createdInstance] = await spanner + .createInstance('new-instance', { + config: 'test-instance-config', + processingUnits: 500, + }) + .then(data => { + const operation = data[1]; + return operation.promise() as Promise< + [Instance, CreateInstanceMetadata, object] + >; + }) + .then(response => { + return response; + }); + assert.strictEqual( + createdInstance.name, + `projects/${spanner.projectId}/instances/new-instance` + ); + assert.strictEqual(createdInstance.processingUnits, 500); + assert.strictEqual(createdInstance.nodeCount, 0); + }); + it('should update an instance', async () => { const instance = spanner.instance(mockInstanceAdmin.PROD_INSTANCE_NAME); const [updatedInstance] = await instance