From 119ae60d59194bc6ce5f0ac3e51c58b033469fc5 Mon Sep 17 00:00:00 2001 From: Alka Trivedi Date: Thu, 7 Dec 2023 15:23:39 +0530 Subject: [PATCH] feat: add code changes for managed autoscaler feat: code changes for managed autoscaler code changes for managed autoscaler add license lint changes remove comments remove .vscode folder modify timeout in monarch fix mismatched region tag change copyright year to 2023 --- samples/instance-with-autoscaling-config.js | 93 +++++++++++++++++++++ samples/instance.js | 14 ++++ samples/system-test/spanner.test.js | 28 +++++++ src/index.ts | 10 ++- test/index.ts | 33 ++++++++ test/mockserver/mockinstanceadmin.ts | 3 + test/spanner.ts | 57 +++++++++++++ 7 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 samples/instance-with-autoscaling-config.js 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 0b73b238d..e5e7efe6c 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 8fb98151b..ed0215124 100644 --- a/src/index.ts +++ b/src/index.ts @@ -130,6 +130,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; @@ -468,17 +469,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; @@ -490,6 +495,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 a37b92404..7d0b6f5a8 100644 --- a/test/index.ts +++ b/test/index.ts @@ -625,6 +625,7 @@ describe('Spanner', () => { name: PATH, displayName: NAME, nodeCount: 1, + autoscalingConfig: undefined, processingUnits: undefined, config: `projects/project-id/instanceConfigs/${CONFIG.config}`, }, @@ -671,6 +672,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 c53a920ac..b6d0edba7 100644 --- a/test/spanner.ts +++ b/test/spanner.ts @@ -4248,6 +4248,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