diff --git a/samples/system-test/v2/spanner.test.js b/samples/system-test/v2/spanner.test.js new file mode 100644 index 000000000..e9129d146 --- /dev/null +++ b/samples/system-test/v2/spanner.test.js @@ -0,0 +1,309 @@ +// Copyright 2024 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'; + +const {Spanner} = require('@google-cloud/spanner'); +const pLimit = require('p-limit'); +const {describe, it, before, after, afterEach} = require('mocha'); +const {assert} = require('chai'); +const cp = require('child_process'); + +const execSync = cmd => cp.execSync(cmd, {encoding: 'utf-8'}); +const instanceCmd = 'node v2/instance.js'; + +const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); +const PROJECT_ID = process.env.GCLOUD_PROJECT; +const PREFIX = 'test-instance'; +const SAMPLE_INSTANCE_ID = `${PREFIX}-my-sample-instance-${CURRENT_TIME}`; +const SAMPLE_INSTANCE_CONFIG_ID = `custom-my-sample-instance-config-${CURRENT_TIME}`; +const BASE_INSTANCE_CONFIG_ID = 'regional-us-central1'; +const INSTANCE_ID = + process.env.SPANNERTEST_INSTANCE || `${PREFIX}-${CURRENT_TIME}`; +const DATABASE_ID = `test-database-${CURRENT_TIME}`; +const INSTANCE_ALREADY_EXISTS = !!process.env.SPANNERTEST_INSTANCE; +const PG_DATABASE_ID = `test-pg-database-${CURRENT_TIME}`; +const RESTORE_DATABASE_ID = `test-database-${CURRENT_TIME}-r`; +const ENCRYPTED_RESTORE_DATABASE_ID = `test-database-${CURRENT_TIME}-r-enc`; +const BACKUP_ID = `test-backup-${CURRENT_TIME}`; +const COPY_BACKUP_ID = `test-copy-backup-${CURRENT_TIME}`; +const ENCRYPTED_BACKUP_ID = `test-backup-${CURRENT_TIME}-enc`; +const CANCELLED_BACKUP_ID = `test-backup-${CURRENT_TIME}-c`; +const LOCATION_ID = 'regional-us-central1'; + +const spanner = new Spanner({ + projectId: PROJECT_ID, +}); + +const LABEL = 'node-sample-tests'; +const GAX_OPTIONS = { + retry: { + retryCodes: [4, 8, 14], + backoffSettings: { + initialRetryDelayMillis: 1000, + retryDelayMultiplier: 1.3, + maxRetryDelayMillis: 32000, + initialRpcTimeoutMillis: 60000, + rpcTimeoutMultiplier: 1, + maxRpcTimeoutMillis: 60000, + totalTimeoutMillis: 600000, + }, + }, +}; + +const delay = async test => { + const retries = test.currentRetry(); + // No retry on the first failure. + if (retries === 0) return; + // See: https://cloud.google.com/storage/docs/exponential-backoff + const ms = Math.pow(2, retries) + Math.random() * 1000; + return new Promise(done => { + console.info(`retrying "${test.title}" in ${ms}ms`); + setTimeout(done, ms); + }); +}; + +async function deleteStaleInstances() { + let [instances] = await spanner.getInstances({ + filter: `(labels.${LABEL}:true) OR (labels.cloud_spanner_samples:true)`, + }); + const old = new Date(); + old.setHours(old.getHours() - 4); + + instances = instances.filter(instance => { + return ( + instance.metadata.labels['created'] && + new Date(parseInt(instance.metadata.labels['created']) * 1000) < old + ); + }); + const limit = pLimit(5); + await Promise.all( + instances.map(instance => + limit(() => setTimeout(deleteInstance, delay, instance)) + ) + ); +} + +async function deleteInstance(instance) { + const [backups] = await instance.getBackups(); + await Promise.all(backups.map(backup => backup.delete(GAX_OPTIONS))); + return instance.delete(GAX_OPTIONS); +} + +describe('Autogenerated Admin Clients', () => { + const instance = spanner.instance(INSTANCE_ID); + + before(async () => { + await deleteStaleInstances(); + + if (!INSTANCE_ALREADY_EXISTS) { + const [, operation] = await instance.create({ + config: LOCATION_ID, + nodes: 1, + labels: { + [LABEL]: 'true', + created: CURRENT_TIME, + }, + gaxOptions: GAX_OPTIONS, + }); + return operation.promise(); + } else { + console.log( + `Not creating temp instance, using + ${instance.formattedName_}...` + ); + } + }); + + after(async () => { + const instance = spanner.instance(INSTANCE_ID); + + if (!INSTANCE_ALREADY_EXISTS) { + // Make sure all backups are deleted before an instance can be deleted. + await Promise.all([ + instance.backup(BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(ENCRYPTED_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(COPY_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(CANCELLED_BACKUP_ID).delete(GAX_OPTIONS), + ]); + await instance.delete(GAX_OPTIONS); + } else { + await Promise.all([ + instance.database(DATABASE_ID).delete(), + instance.database(PG_DATABASE_ID).delete(), + instance.database(RESTORE_DATABASE_ID).delete(), + instance.database(ENCRYPTED_RESTORE_DATABASE_ID).delete(), + instance.backup(BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(COPY_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(ENCRYPTED_BACKUP_ID).delete(GAX_OPTIONS), + instance.backup(CANCELLED_BACKUP_ID).delete(GAX_OPTIONS), + ]); + } + await spanner.instance(SAMPLE_INSTANCE_ID).delete(GAX_OPTIONS); + }); + describe('instance', () => { + afterEach(async () => { + const sample_instance = spanner.instance(SAMPLE_INSTANCE_ID); + await sample_instance.delete(); + }); + + // create_instance_using_instance_admin_client + it('should create an example instance', async () => { + const output = execSync( + `${instanceCmd} createInstance "${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}.`) + ); + }); + + // 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.`) + ); + }); + }); + + describe('leader options', () => { + before(async () => { + const instance = spanner.instance(SAMPLE_INSTANCE_ID); + const [, operation] = await instance.create({ + config: 'nam6', + nodes: 1, + displayName: 'Multi-region options test', + labels: { + ['cloud_spanner_samples']: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }); + await operation.promise(); + }); + + after(async () => { + const instance = spanner.instance(SAMPLE_INSTANCE_ID); + await instance.delete(); + }); + + // create_instance_config + it('should create an example custom instance config', async () => { + const output = execSync( + `node v2/instance-config-create.js ${SAMPLE_INSTANCE_CONFIG_ID} ${BASE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for create operation for ${SAMPLE_INSTANCE_CONFIG_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp(`Created instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + ); + }); + + // update_instance_config + it('should update an example custom instance config', async () => { + const output = execSync( + `node v2/instance-config-update.js ${SAMPLE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Waiting for update operation for ${SAMPLE_INSTANCE_CONFIG_ID} to complete...` + ) + ); + assert.match( + output, + new RegExp(`Updated instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + ); + }); + + // delete_instance_config + it('should delete an example custom instance config', async () => { + const output = execSync( + `node instance-config-delete.js ${SAMPLE_INSTANCE_CONFIG_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`Deleting ${SAMPLE_INSTANCE_CONFIG_ID}...`) + ); + assert.match( + output, + new RegExp(`Deleted instance config ${SAMPLE_INSTANCE_CONFIG_ID}.`) + ); + }); + + // list_instance_config_operations + it('should list all instance config operations', async () => { + const output = execSync( + `node v2/instance-config-get-operations.js ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp( + `Getting list of instance config operations on project ${PROJECT_ID}...\n` + ) + ); + assert.match( + output, + new RegExp( + `Available instance config operations for project ${PROJECT_ID}:` + ) + ); + assert.include(output, 'Instance config operation for'); + assert.include( + output, + 'type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata' + ); + }); + + // list_instance_configs + it('should list available instance configs', async () => { + const output = execSync(`node v2/list-instance-configs.js ${PROJECT_ID}`); + assert.match( + output, + new RegExp(`Available instance configs for project ${PROJECT_ID}:`) + ); + assert.include(output, 'Available leader options for instance config'); + }); + + // get_instance_config + // TODO: Enable when the feature has been released. + it.skip('should get a specific instance config', async () => { + const output = execSync(`node v2/get-instance-config.js ${PROJECT_ID}`); + assert.include(output, 'Available leader options for instance config'); + }); + }); +}); diff --git a/samples/v2/get-instance-config.js b/samples/v2/get-instance-config.js new file mode 100644 index 000000000..ce6a70b76 --- /dev/null +++ b/samples/v2/get-instance-config.js @@ -0,0 +1,61 @@ +/** + * Copyright 2024 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. + */ + +// sample-metadata: +// title: Gets the instance config metadata for the configuration nam6 +// usage: node get-instance-config.js + +'use strict'; + +function main(projectId) { + // [START spanner_get_instance_config] + + /** + * TODO(developer): Uncomment the following line before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function getInstanceConfig() { + // Get the instance config for the multi-region North America 6 (NAM6). + // See https://cloud.google.com/spanner/docs/instance-configurations#configuration for a list of all available + // configurations. + const [instanceConfig] = await instanceAdminClient.getInstanceConfig({ + name: instanceAdminClient.instanceConfigPath(projectId, 'nam6'), + }); + console.log( + `Available leader options for instance config ${instanceConfig.name} ('${ + instanceConfig.displayName + }'): + ${instanceConfig.leaderOptions.join()}` + ); + } + getInstanceConfig(); + // [END spanner_get_instance_config] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/v2/instance-config-create.js b/samples/v2/instance-config-create.js new file mode 100644 index 000000000..95686b0d7 --- /dev/null +++ b/samples/v2/instance-config-create.js @@ -0,0 +1,101 @@ +/** + * Copyright 2024 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. + */ + +// sample-metadata: +// title: Creates a user-managed instance configuration. +// usage: node instance-config-create + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + baseInstanceConfigId = 'my-base-instance-config', + projectId = 'my-project-id' +) { + // [START spanner_create_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id' + // const baseInstanceConfigId = 'my-base-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + // Creates a new instance config + async function createInstanceConfig() { + const [baseInstanceConfig] = await instanceAdminClient.getInstanceConfig({ + name: instanceAdminClient.instanceConfigPath( + projectId, + baseInstanceConfigId + ), + }); + try { + console.log( + `Creating instance config ${instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId + )}.` + ); + const [operation] = await instanceAdminClient.createInstanceConfig({ + instanceConfigId: instanceConfigId, + parent: instanceAdminClient.projectPath(projectId), + instanceConfig: { + name: instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId + ), + baseConfig: instanceAdminClient.instanceConfigPath( + projectId, + baseInstanceConfigId + ), + displayName: instanceConfigId, + replicas: baseInstanceConfig.replicas.concat( + baseInstanceConfig.optionalReplicas[0] + ), + }, + }); + console.log( + `Waiting for create operation for ${instanceConfigId} to complete...` + ); + await operation.promise(); + console.log(`Created instance config ${instanceConfigId}.`); + } catch (err) { + console.error( + 'ERROR: Creating instance config ', + instanceConfigId, + ' failed with error message ', + err + ); + } + } + createInstanceConfig(); + // [END spanner_create_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/v2/instance-config-delete.js b/samples/v2/instance-config-delete.js new file mode 100644 index 000000000..a56dab6a8 --- /dev/null +++ b/samples/v2/instance-config-delete.js @@ -0,0 +1,75 @@ +/** + * Copyright 2024 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. + */ + +// sample-metadata: +// title: Deletes a user-managed instance configuration. +// usage: node instance-config-delete + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + projectId = 'my-project-id' +) { + // [START spanner_delete_instance_config] + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function deleteInstanceConfig() { + // Deletes an instance config. + + try { + // Delete the instance config. + console.log(`Deleting ${instanceConfigId}...\n`); + await instanceAdminClient.deleteInstanceConfig({ + name: instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId + ), + }); + console.log(`Deleted instance config ${instanceConfigId}.\n`); + } catch (err) { + console.error( + 'ERROR: Deleting instance config ', + instanceConfigId, + ' failed with error message ', + err + ); + } + } + deleteInstanceConfig(); + // [END spanner_delete_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); + +/* instance exists needs to be added*/ diff --git a/samples/v2/instance-config-get-operations.js b/samples/v2/instance-config-get-operations.js new file mode 100644 index 000000000..4737ff40e --- /dev/null +++ b/samples/v2/instance-config-get-operations.js @@ -0,0 +1,81 @@ +/** + * Copyright 2024 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. + */ + +// sample-metadata: +// title: Lists the instance configuration operations. +// usage: node instance-config-get-operations + +'use strict'; + +function main(projectId = 'my-project-id') { + // [START spanner_list_instance_config_operations] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function getInstanceConfigOperations() { + // Lists the instance config operations. + try { + console.log( + `Getting list of instance config operations on project ${projectId}...\n` + ); + const [instanceConfigOperations] = + await instanceAdminClient.listInstanceConfigOperations({ + parent: instanceAdminClient.projectPath(projectId), + // This filter ensures that only operations with metadata type CreateInstanceConfigMetadata + filter: + '(metadata.@type=type.googleapis.com/google.spanner.admin.instance.v1.CreateInstanceConfigMetadata)', + }); + console.log( + `Available instance config operations for project ${projectId}:` + ); + instanceConfigOperations.forEach(instanceConfigOperation => { + const metadata = instanceConfigOperation.metadata; + const instanceConfig = + protos.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata.decode( + instanceConfigOperation.metadata.value + ).instanceConfig; + console.log( + `Instance config operation for ${instanceConfig.name} of type` + + ` ${metadata.type_url} has status ${ + instanceConfigOperation.done ? 'done' : 'running' + }.` + ); + }); + } catch (err) { + console.error('ERROR:', err); + } + } + getInstanceConfigOperations(); + // [END spanner_list_instance_config_operations] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/v2/instance-config-update.js b/samples/v2/instance-config-update.js new file mode 100644 index 000000000..59b8bae46 --- /dev/null +++ b/samples/v2/instance-config-update.js @@ -0,0 +1,93 @@ +/** + * Copyright 2024 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. + */ + +// sample-metadata: +// title: Updates a user-managed instance configuration. +// usage: node instance-config-update + +'use strict'; + +function main( + instanceConfigId = 'custom-my-instance-config', + projectId = 'my-project-id' +) { + // [START spanner_update_instance_config] + + /** + * TODO(developer): Uncomment the following lines before running the sample. + */ + // const instanceConfigId = 'custom-my-instance-config-id'; + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner, protos} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function updateInstanceConfig() { + // Updates an instance config + try { + console.log( + `Updating instance config ${instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId + )}.` + ); + const [operation] = await instanceAdminClient.updateInstanceConfig({ + instanceConfig: { + name: instanceAdminClient.instanceConfigPath( + projectId, + instanceConfigId + ), + displayName: 'updated custom instance config', + labels: { + updated: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }, + // Field mask specifying fields that should get updated in InstanceConfig + // Only display_name and labels can be updated + updateMask: (protos.google.protobuf.FieldMask = { + paths: ['display_name', 'labels'], + }), + }); + console.log( + `Waiting for update operation for ${instanceConfigId} to complete...` + ); + await operation.promise(); + console.log(`Updated instance config ${instanceConfigId}.`); + } catch (err) { + console.error( + 'ERROR: Updating instance config ', + instanceConfigId, + ' failed with error message ', + err + ); + } + } + updateInstanceConfig(); + // [END spanner_update_instance_config] +} + +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/v2/instance-with-processing-units.js b/samples/v2/instance-with-processing-units.js new file mode 100644 index 000000000..1c4e295c5 --- /dev/null +++ b/samples/v2/instance-with-processing-units.js @@ -0,0 +1,79 @@ +/** + * Copyright 2024 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 instanceAdminClient = spanner.getInstanceAdminClient(); + + // Creates a new instance + try { + console.log( + `Creating instance ${instanceAdminClient.instancePath( + projectId, + instanceId + )}.` + ); + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-us-central1' + ), + displayName: 'Display name for the instance.', + processingUnits: 500, + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }, + parent: instanceAdminClient.projectPath(projectId), + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + console.log(`Created instance ${instanceId}.`); + const [metadata] = await instanceAdminClient.getInstance({ + name: instanceAdminClient.instancePath(projectId, instanceId), + }); + 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/v2/instance.js b/samples/v2/instance.js new file mode 100644 index 000000000..9098abb67 --- /dev/null +++ b/samples/v2/instance.js @@ -0,0 +1,98 @@ +/** + * Copyright 2024 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'; + +// creates an instance using Instance Admin Client +async function createInstance(instanceId, projectId) { + // [START spanner_create_instance] + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = await spanner.getInstanceAdminClient(); + /** + * TODO(developer): Uncomment the following lines before running the sample. + **/ + // const projectId = 'my-project-id'; + // const instanceId = 'my-instance'; + + // Creates a new instance + try { + console.log( + `Creating instance ${instanceAdminClient.instancePath( + projectId, + instanceId + )}.` + ); + const [operation] = await instanceAdminClient.createInstance({ + instanceId: instanceId, + parent: instanceAdminClient.projectPath(projectId), + instance: { + config: instanceAdminClient.instanceConfigPath( + projectId, + 'regional-us-central1' + ), + nodeCount: 1, + displayName: 'Display name for the instance.', + labels: { + cloud_spanner_samples: 'true', + created: Math.round(Date.now() / 1000).toString(), // current time + }, + }, + }); + + console.log(`Waiting for operation on ${instanceId} to complete...`); + await operation.promise(); + + console.log(`Created instance ${instanceId}.`); + } catch (err) { + console.error('ERROR:', err); + } + // [END spanner_create_instance] +} + +const { + createInstanceWithProcessingUnits, +} = require('./instance-with-processing-units'); + +require('yargs') + .demand(1) + .command( + 'createInstance ', + 'Creates an example instance in a Cloud Spanner instance using Instance Admin Client.', + {}, + 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') + .strict() + .help().argv; diff --git a/samples/v2/list-instance-configs.js b/samples/v2/list-instance-configs.js new file mode 100644 index 000000000..27017f38e --- /dev/null +++ b/samples/v2/list-instance-configs.js @@ -0,0 +1,63 @@ +/** + * Copyright 2024 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. + */ + +// sample-metadata: +// title: Lists all the available instance configs for the selected project. +// usage: node list-instance-configs.js + +'use strict'; + +function main(projectId) { + // [START spanner_list_instance_configs] + /** + * TODO(developer): Uncomment the following line before running the sample. + */ + // const projectId = 'my-project-id'; + + // Imports the Google Cloud client library + const {Spanner} = require('@google-cloud/spanner'); + + // Creates a client + const spanner = new Spanner({ + projectId: projectId, + }); + + const instanceAdminClient = spanner.getInstanceAdminClient(); + + async function listInstanceConfigs() { + // Lists all available instance configurations in the project. + // See https://cloud.google.com/spanner/docs/instance-configurations#configuration for a list of all available + // configurations. + const [instanceConfigs] = await instanceAdminClient.listInstanceConfigs({ + parent: instanceAdminClient.projectPath(projectId), + }); + console.log(`Available instance configs for project ${projectId}:`); + instanceConfigs.forEach(instanceConfig => { + console.log( + `Available leader options for instance config ${ + instanceConfig.name + } ('${instanceConfig.displayName}'): + ${instanceConfig.leaderOptions.join()}` + ); + }); + } + listInstanceConfigs(); + // [END spanner_list_instance_configs] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2));