diff --git a/samples/instance-with-autoscaling-config.js b/samples/instance-with-autoscaling-config.js new file mode 100644 index 000000000..b0d4cfbcd --- /dev/null +++ b/samples/instance-with-autoscaling-config.js @@ -0,0 +1,93 @@ +/** + * Copyright 2023 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. + */ + +// [START spanner_create_instance_with_autoscaling_config] + +'use strict'; + +async function createInstanceWithAutoscalingConfig(instanceId, projectId) { + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instance = spanner.instance(instanceId); + const autoscalingConfig = + protos.google.spanner.admin.instance.v1.AutoscalingConfig.create({ + // Only one of minNodes/maxNodes or minProcessingUnits/maxProcessingUnits + // can be set. Both min and max need to be set and + // maxNodes/maxProcessingUnits can be at most 10X of + // minNodes/minProcessingUnits. + autoscalingLimits: + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingLimits.create( + { + minNodes: 1, + maxNodes: 2, + } + ), + // highPriorityCpuUtilizationPercent and storageUtilizationPercent are both + // percentages and must lie between 0 and 100. + autoscalingTargets: + protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingTargets.create( + { + highPriorityCpuUtilizationPercent: 65, + storageUtilizationPercent: 95, + } + ), + }); + // Creates a new instance with autoscalingConfig + try { + console.log(`Creating instance ${instance.formattedName_}.`); + const [, operation] = await instance.create({ + config: 'regional-us-west1', + autoscalingConfig: autoscalingConfig, + 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: ['autoscalingConfig'], + }); + console.log( + `Autoscaling configurations of ${instanceId} are: ` + + '\n' + + `Min nodes: ${metadata.autoscalingConfig.autoscalingLimits.minNodes} ` + + 'nodes.' + + '\n' + + `Max nodes: ${metadata.autoscalingConfig.autoscalingLimits.maxNodes}` + + ' nodes.' + + '\n' + + `High priority cpu utilization percent: ${metadata.autoscalingConfig.autoscalingTargets.highPriorityCpuUtilizationPercent}.` + + '\n' + + `Storage utilization percent: ${metadata.autoscalingConfig.autoscalingTargets.storageUtilizationPercent}.` + ); + } catch (err) { + console.error('ERROR:', err); + } +} + +// [END spanner_create_instance_with_autoscaling_config] +module.exports.createInstanceWithAutoscalingConfig = + createInstanceWithAutoscalingConfig; diff --git a/samples/instance.js b/samples/instance.js index cd9281488..3dc3170b6 100644 --- a/samples/instance.js +++ b/samples/instance.js @@ -60,6 +60,10 @@ const { createInstanceWithProcessingUnits, } = require('./instance-with-processing-units'); +const { + createInstanceWithAutoscalingConfig, +} = require('./instance-with-autoscaling-config'); + require('yargs') .demand(1) .command( @@ -78,6 +82,16 @@ require('yargs') .example( 'node $0 createInstanceWithProcessingUnits "my-instance" "my-project-id"' ) + .command( + 'createInstanceWithAutoscalingConfig ', + 'Creates an example instance in a Cloud Spanner instance with autoscaling configs.', + {}, + opts => + createInstanceWithAutoscalingConfig(opts.instanceName, opts.projectId) + ) + .example( + 'node $0 createInstanceWithAutoscalingConfig "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 04645c9ff..1da94b0f4 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -296,6 +296,34 @@ describe('Spanner', () => { }); }); + // create_instance_with_autoscaling_units + it('should create an example instance with autoscaling configs', async () => { + const output = execSync( + `${instanceCmd} createInstanceWithAutoscalingConfig "${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( + `Autoscaling configurations of ${SAMPLE_INSTANCE_ID} are: ` + + '\n' + + 'Min nodes: 1 nodes.' + + '\n' + + 'Max nodes: 2 nodes.' + + '\n' + + 'High priority cpu utilization percent: 65.' + + '\n' + + 'Storage utilization percent: 95.' + ) + ); + }); + // check that base instance was created it('should have created an instance', async () => { const [exists] = await instance.exists(); diff --git a/src/index.ts b/src/index.ts index 2f2b1db3e..ee356191d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -134,6 +134,7 @@ export interface CreateInstanceRequest { config?: string; nodes?: number; processingUnits?: number; + autoscalingConfig?: google.spanner.admin.instance.v1.IAutoscalingConfig; displayName?: string; labels?: {[k: string]: string} | null; gaxOptions?: CallOptions; @@ -481,17 +482,21 @@ class Spanner extends GrpcService { displayName, nodeCount: config.nodes, processingUnits: config.processingUnits, + autoscalingConfig: config.autoscalingConfig, }, config ), }; - if (reqOpts.instance.nodeCount && reqOpts.instance.processingUnits) { throw new GoogleError( ['Only one of nodeCount or processingUnits can be specified.'].join('') ); } - if (!reqOpts.instance.nodeCount && !reqOpts.instance.processingUnits) { + if ( + !reqOpts.instance.nodeCount && + !reqOpts.instance.processingUnits && + !reqOpts.instance.autoscalingConfig + ) { // If neither nodes nor processingUnits are specified, default to a // nodeCount of 1. reqOpts.instance.nodeCount = 1; @@ -503,6 +508,7 @@ class Spanner extends GrpcService { if (config.config!.indexOf('/') === -1) { reqOpts.instance.config = `projects/${this.projectId}/instanceConfigs/${config.config}`; } + this.request( { client: 'InstanceAdminClient', diff --git a/test/index.ts b/test/index.ts index 70f68857e..1040bb6d2 100644 --- a/test/index.ts +++ b/test/index.ts @@ -646,6 +646,7 @@ describe('Spanner', () => { name: PATH, displayName: NAME, nodeCount: 1, + autoscalingConfig: undefined, processingUnits: undefined, config: `projects/project-id/instanceConfigs/${CONFIG.config}`, }, @@ -692,6 +693,38 @@ describe('Spanner', () => { spanner.createInstance(NAME, config, assert.ifError); }); + it('should create an instance with autoscaling configurations', done => { + const autoscalingConfig = + spnr.protos.google.spanner.admin.instance.v1.AutoscalingConfig.create({ + autoscalingLimits: + spnr.protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingLimits.create( + { + minNodes: 1, + maxNodes: 2, + } + ), + autoscalingTargets: + spnr.protos.google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingTargets.create( + { + highPriorityCpuUtilizationPercent: 65, + storageUtilizationPercent: 95, + } + ), + }); + const config = Object.assign({}, CONFIG, {autoscalingConfig}); + + spanner.request = config => { + assert.strictEqual( + config.reqOpts.instance.autoscalingConfig, + autoscalingConfig + ); + assert.strictEqual(config.reqOpts.instance.nodeCount, undefined); + assert.strictEqual(config.reqOpts.instance.processingUnits, undefined); + done(); + }; + + spanner.createInstance(NAME, config, assert.ifError); + }); it('should throw if both nodes and processingUnits are given', () => { const nodeCount = 1; diff --git a/test/mockserver/mockinstanceadmin.ts b/test/mockserver/mockinstanceadmin.ts index aa9f2616c..4ffc3c5a5 100644 --- a/test/mockserver/mockinstanceadmin.ts +++ b/test/mockserver/mockinstanceadmin.ts @@ -212,6 +212,9 @@ export class MockInstanceAdmin { processingUnits: call.request!.instance ? call.request!.instance.processingUnits : undefined, + autoscalingConfig: call.request!.instance + ? call.request!.instance.autoscalingConfig + : undefined, labels: call.request!.instance ? call.request!.instance.labels : undefined, diff --git a/test/spanner.ts b/test/spanner.ts index 32a1b8e58..1723e188f 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -4440,6 +4440,63 @@ describe('Spanner with mock server', () => { assert.strictEqual(createdInstance.nodeCount, 0); }); + it('should create an example instance with autoscaling configs', async () => { + const autoscalingConfig = + google.spanner.admin.instance.v1.AutoscalingConfig.create({ + autoscalingLimits: + google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingLimits.create( + { + minNodes: 1, + maxNodes: 2, + } + ), + autoscalingTargets: + google.spanner.admin.instance.v1.AutoscalingConfig.AutoscalingTargets.create( + { + highPriorityCpuUtilizationPercent: 65, + storageUtilizationPercent: 95, + } + ), + }); + const [createdInstance] = await spanner + .createInstance('new-instance', { + config: 'test-instance-config', + autoscalingConfig: autoscalingConfig, + }) + .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.autoscalingConfig.autoscalingLimits.minNodes, + 1 + ); + assert.strictEqual( + createdInstance.autoscalingConfig.autoscalingLimits.maxNodes, + 2 + ); + assert.strictEqual( + createdInstance.autoscalingConfig.autoscalingTargets + .highPriorityCpuUtilizationPercent, + 65 + ); + assert.strictEqual( + createdInstance.autoscalingConfig.autoscalingTargets + .storageUtilizationPercent, + 95 + ); + assert.strictEqual(createdInstance.nodeCount, 0); + }); + it('should update an instance', async () => { const instance = spanner.instance(mockInstanceAdmin.PROD_INSTANCE_NAME); const [updatedInstance] = await instance