From f200a1c260c1adbadc1ae1fbebeb900b362e0f1c Mon Sep 17 00:00:00 2001 From: Astha Mohta Date: Tue, 19 Oct 2021 23:19:07 +0530 Subject: [PATCH 1/7] feat: adding support for Cross Region Backups --- samples/backups-copy.js | 103 +++++++++++++ samples/backups-update.js | 11 +- samples/system-test/spanner.test.js | 15 ++ src/backup.ts | 227 ++++++++++++++++++++++------ src/instance.ts | 21 ++- system-test/spanner.ts | 21 +-- test/instance.ts | 15 ++ 7 files changed, 356 insertions(+), 57 deletions(-) create mode 100644 samples/backups-copy.js diff --git a/samples/backups-copy.js b/samples/backups-copy.js new file mode 100644 index 000000000..efa12f7bd --- /dev/null +++ b/samples/backups-copy.js @@ -0,0 +1,103 @@ +// 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. + +// sample-metadata: +// title: Read data using an existing index. +// usage: node spannerCopyBackup + +'use strict'; + +function main( + instanceId = 'my-instance', + databaseId = 'my-database', + backupId = 'my-backup', + sourceBackupId = 'my-source-backup', + projectId = 'my-project-id' +) { + // [START spanner_copy_backup] + /** + * TODO(developer): Uncomment these variables before running the sample. + */ + // const instanceId = 'my-instance'; + // const databaseId = 'my-database'; + // const backupId = 'my-backup', + // sourceBackupId = 'my-source-backup', + // const projectId = 'my-project-id'; + + // Imports the Google Cloud Spanner client library + const {Spanner} = require('@google-cloud/spanner'); + const {PreciseDate} = require('@google-cloud/precise-date'); + + // Instantiates a client + const spanner = new Spanner({ + projectId: projectId, + apiEndpoint: 'staging-wrenchworks.sandbox.googleapis.com', + }); + + async function spannerCopyBackup() { + // Gets a reference to a Cloud Spanner instance, database and backup + const instance = spanner.instance(instanceId); + const database = instance.database(databaseId); + const sourceBackup = instance.backup(sourceBackupId); + + // Expire source and copy backup 14 days in the future + const expireTime = Spanner.timestamp( + Date.now() + 1000 * 60 * 60 * 24 * 14 + ).toStruct(); + + // Copy the backup of the database + try { + console.log( + `Creating copy of the source backup ${sourceBackup.formattedName_}.` + ); + const [, operation] = await instance.copyBackup( + sourceBackup.formattedName_, + backupId, + { + expireTime: expireTime, + } + ); + + console.log( + `Waiting for backup copy${instance.backup(backupId)} to complete...` + ); + await operation.promise(); + + // Verify the copy backup is ready + const copyBackup = instance.backup(backupId); + const [copyBackupInfo] = await copyBackup.getMetadata(); + if (copyBackupInfo.state === 'READY') { + console.log( + `Backup copy ${copyBackupInfo.name} of size ` + + `${copyBackupInfo.sizeBytes} bytes was created at ` + + `${new PreciseDate(copyBackupInfo.createTime).toISOString()}` + ); + } else { + console.error('ERROR: Copy of backup is not ready.'); + } + } catch (err) { + console.error('ERROR:', err); + } finally { + // Close the database when finished. + await database.close(); + } + } + spannerCopyBackup(); + // [END spanner_copy_backup] +} +process.on('unhandledRejection', err => { + console.error(err.message); + process.exitCode = 1; +}); +main(...process.argv.slice(2)); diff --git a/samples/backups-update.js b/samples/backups-update.js index 55c044693..8da5e2018 100644 --- a/samples/backups-update.js +++ b/samples/backups-update.js @@ -31,6 +31,7 @@ async function updateBackup(instanceId, backupId, projectId) { // Creates a client const spanner = new Spanner({ projectId: projectId, + apiEndpoint: 'staging-wrenchworks.sandbox.googleapis.com', }); // Gets a reference to a Cloud Spanner instance and backup @@ -40,8 +41,13 @@ async function updateBackup(instanceId, backupId, projectId) { // Read backup metadata and update expiry time try { const currentExpireTime = await backup.getExpireTime(); - const newExpireTime = new PreciseDate(currentExpireTime); - newExpireTime.setDate(newExpireTime.getDate() + 30); + const maxExpireTime = await backup.getMaxExpireTime(); + const wantExpireTime = new PreciseDate(currentExpireTime); + wantExpireTime.setDate(wantExpireTime.getDate() + 1); + // New expire time should be less than the max expire time + const min = (currentExpireTime, maxExpireTime) => + currentExpireTime < maxExpireTime ? currentExpireTime : maxExpireTime; + const newExpireTime = new PreciseDate(min(wantExpireTime, maxExpireTime)); console.log( `Backup ${backupId} current expire time: ${currentExpireTime.toISOString()}` ); @@ -55,3 +61,4 @@ async function updateBackup(instanceId, backupId, projectId) { } module.exports.updateBackup = updateBackup; +//updateBackup('test-cross-region', 'backup123', 'appdev-soda-spanner-staging'); diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index 5436acd6c..1f3b5533a 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -52,6 +52,7 @@ const VERSION_RETENTION_DATABASE_ID = `test-database-${CURRENT_TIME}-v`; const ENCRYPTED_DATABASE_ID = `test-database-${CURRENT_TIME}-enc`; const DEFAULT_LEADER_DATABASE_ID = `test-database-${CURRENT_TIME}-dl`; 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'; @@ -63,6 +64,7 @@ const DEFAULT_LEADER_2 = 'us-east1'; const spanner = new Spanner({ projectId: PROJECT_ID, + apiEndpoint: 'staging-wrenchworks.sandbox.googleapis.com', }); const LABEL = 'node-sample-tests'; const GAX_OPTIONS = { @@ -214,6 +216,7 @@ describe('Spanner', () => { 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); @@ -223,6 +226,7 @@ describe('Spanner', () => { 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), ]); @@ -1021,6 +1025,17 @@ describe('Spanner', () => { assert.include(output, `using encryption key ${key.name}`); }); + // copy_backup + it('should create a copy of a backup', async () => { + const output = execSync( + `node backups-copy.js ${INSTANCE_ID} ${DATABASE_ID} ${COPY_BACKUP_ID} ${BACKUP_ID} ${PROJECT_ID}` + ); + assert.match( + output, + new RegExp(`(.*)Backup copy(.*)${COPY_BACKUP_ID} of size(.*)`) + ); + }); + // cancel_backup it('should cancel a backup of the database', async () => { const output = execSync( diff --git a/src/backup.ts b/src/backup.ts index 679a22ac1..1c0dd7c76 100644 --- a/src/backup.ts +++ b/src/backup.ts @@ -35,6 +35,7 @@ import {google as databaseAdmin} from '../protos/protos'; import {common as p} from 'protobufjs'; export type CreateBackupCallback = LongRunningCallback; +export type CopyBackupCallback = LongRunningCallback; export interface CreateBackupGaxOperation extends GaxOperation { // Overridden with more specific type for CreateBackup operation @@ -56,6 +57,19 @@ export interface CreateBackupOptions { gaxOptions?: CallOptions; } +export interface CopyBackupGaxOperation extends GaxOperation { + // Overridden with more specific type for CreateBackup operation + metadata: Metadata & + databaseAdmin.spanner.admin.database.v1.ICopyBackupMetadata; +} + +export type CopyBackupResponse = [Backup, CopyBackupGaxOperation, IOperation]; + +export interface CopyBackupOptions + extends databaseAdmin.spanner.admin.database.v1.ICopyBackupRequest { + gaxOptions?: CallOptions; +} + /** * IBackup structure with backup state enum translated to string form. */ @@ -84,11 +98,12 @@ export type GetStateCallback = NormalCallback< EnumKey >; export type GetExpireTimeCallback = NormalCallback; +export type GetMaxExpireTimeCallback = NormalCallback; export type ExistsCallback = NormalCallback; /** * The {@link Backup} class represents a Cloud Spanner backup. * - * Create a `Backup` object to interact with or create a Cloud Spanner backup. + * Create a `Backup` object to interact with or create a Cloud Spanner backup or copy a backup. * * @class * @@ -97,6 +112,13 @@ export type ExistsCallback = NormalCallback; * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * const backup = instance.backup('my-backup'); + * + * @example + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner(); + * const instance = spanner.instance('my-instance'); + * const sourceBackup = instance.backup('my-source-backup'); + * const copyBackup = instance.copyBackup('my-copy-backup', 'my-source-backup'); */ class Backup { id: string; @@ -105,18 +127,25 @@ class Backup { resourceHeader_: {[k: string]: string}; request: BackupRequest; metadata?: databaseAdmin.spanner.admin.database.v1.IBackup; - constructor(instance: Instance, name: string) { + sourceName: string | undefined; + constructor(instance: Instance, name: string, sourceName?: string) { this.request = instance.request; this.instanceFormattedName_ = instance.formattedName_; this.formattedName_ = Backup.formatName_(instance.formattedName_, name); this.id = this.formattedName_.split('/').pop() || ''; + this.sourceName = sourceName; this.resourceHeader_ = { [CLOUD_RESOURCE_HEADER]: this.instanceFormattedName_, }; } - create(options: CreateBackupOptions): Promise; - create(options: CreateBackupOptions, callback: CreateBackupCallback): void; + create( + options: CreateBackupOptions | CopyBackupOptions + ): Promise | Promise; + create( + options: CreateBackupOptions | CopyBackupOptions, + callback: CreateBackupCallback | CopyBackupCallback + ): void; /** * @typedef {object} CreateBackupOptions * @property {string} databasePath The database path. @@ -132,6 +161,20 @@ class Backup { * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} * for more details. */ + /** + * @typedef {object} CopyBackupOptions + * @property {string|null} + * sourceBackup The full path of the backup to be copied + * @property {string|number|google.protobuf.Timestamp|external:PreciseDate} + * expireTime The expire time of the backup. + * @property {google.spanner.admin.database.v1.ICopyBackupEncryptionConfig} + * encryptionConfig An encryption configuration describing the + * encryption type and key resources in Cloud KMS to be used to encrypt + * the copy backup. + * @property {object} [gaxOptions] The request configuration options, + * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} + * for more details. + */ /** * @typedef {array} CreateBackupResponse * @property {Backup} 0 The new {@link Backup}. @@ -139,6 +182,13 @@ class Backup { * the status of the request. * @property {object} 2 The full API response. */ + /** + * @typedef {array} CopyBackupResponse + * @property {Backup} 0 The new {@link Backup}. + * @property {google.longrunning.Operation} 1 An {@link Operation} object that can be used to check + * the status of the request. + * @property {object} 2 The full API response. + */ /** * @callback CreateBackupCallback * @param {?Error} err Request error, if any. @@ -147,6 +197,14 @@ class Backup { * check the status of the request. * @param {object} apiResponse The full API response. */ + /** + * @callback CopyBackupCallback + * @param {?Error} err Request error, if any. + * @param {Backup} backup The new {@link Backup}. + * @param {google.longrunning.Operation} operation An {@link Operation} object that can be used to + * check the status of the request. + * @param {object} apiResponse The full API response. + */ /** * Create a backup. * @@ -179,50 +237,103 @@ class Backup { * // Await completion of the backup operation. * await backupOperation.promise(); */ + /** + * Copy a backup. + * + * @method Backup#create + * @param {CopyBackupOptions} options Parameters for copying a backup. + * @param {CallOptions} [options.gaxOptions] The request configuration + * options, See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} + * for more details. + * @param {CopyBackupCallback} [callback] Callback function. + * @returns {Promise} When resolved, the copy backup + * operation will have started, but will not have necessarily completed. + * + * @example + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner(); + * const instance = spanner.instance('my-instance'); + * const oneDay = 1000 * 60 * 60 * 24; + * const expireTime = Date.now() + oneDay; + * const versionTime = Date.now() - oneDay; + * const sourceBackup = instance.backup('my-source-backup'); + * const [, copyBackupOperation] = instance.copyBackup(sourceBackup.formattedName_, 'my-copy-backup', { + * expireTime: expireTime, + * encryptionConfig: { + * encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + * kmsKeyName: 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key', + * },); + * // Await completion of the copy backup operation. + * await copybBackupOperation.promise(); + */ create( - options: CreateBackupOptions, - callback?: CreateBackupCallback - ): Promise | void { + options: CreateBackupOptions | CopyBackupOptions, + callback?: CreateBackupCallback | CopyBackupCallback + ): Promise | Promise | void { const gaxOpts = options.gaxOptions; - const reqOpts: databaseAdmin.spanner.admin.database.v1.ICreateBackupRequest = - { - parent: this.instanceFormattedName_, - backupId: this.id, - backup: { - database: options.databasePath, - expireTime: Spanner.timestamp(options.expireTime).toStruct(), - name: this.formattedName_, + if ('databasePath' in options) { + const reqOpts: databaseAdmin.spanner.admin.database.v1.ICreateBackupRequest = + { + parent: this.instanceFormattedName_, + backupId: this.id, + backup: { + database: options.databasePath, + expireTime: Spanner.timestamp(options.expireTime).toStruct(), + name: this.formattedName_, + }, + }; + if ('versionTime' in options) { + reqOpts.backup!.versionTime = Spanner.timestamp( + options.versionTime + ).toStruct(); + } + if ( + 'encryptionConfig' in options && + (options as CreateBackupOptions).encryptionConfig + ) { + reqOpts.encryptionConfig = ( + options as CreateBackupOptions + ).encryptionConfig; + } + this.request( + { + client: 'DatabaseAdminClient', + method: 'createBackup', + reqOpts, + gaxOpts, + headers: this.resourceHeader_, }, - }; - if ('versionTime' in options) { - reqOpts.backup!.versionTime = Spanner.timestamp( - options.versionTime - ).toStruct(); - } - if ( - 'encryptionConfig' in options && - (options as CreateBackupOptions).encryptionConfig - ) { - reqOpts.encryptionConfig = ( - options as CreateBackupOptions - ).encryptionConfig; - } - this.request( - { - client: 'DatabaseAdminClient', - method: 'createBackup', - reqOpts, - gaxOpts, - headers: this.resourceHeader_, - }, - (err, operation, resp) => { - if (err) { - callback!(err, null, null, resp); - return; + (err, operation, resp) => { + if (err) { + callback!(err, null, null, resp); + return; + } + callback!(null, this, operation, resp); } - callback!(null, this, operation, resp); - } - ); + ); + } else if (this.sourceName) { + delete options.gaxOptions; + options.backupId = this.id; + options.parent = this.instanceFormattedName_; + options.sourceBackup = this.sourceName; + this.request( + { + client: 'DatabaseAdminClient', + method: 'copyBackup', + reqOpts: + options as databaseAdmin.spanner.admin.database.v1.ICopyBackupRequest, + gaxOpts, + headers: this.resourceHeader_, + }, + (err, operation, resp) => { + if (err) { + callback!(err, null, null, resp); + return; + } + callback!(null, this, operation, resp); + } + ); + } } getMetadata(gaxOptions?: CallOptions): Promise; @@ -244,6 +355,7 @@ class Backup { * * @see {@link #getState} * @see {@link #getExpireTime} + * @see {@link #getMaxExpireTime} * * @method Backup#getMetadata * @param {object} [gaxOptions] Request configuration options, @@ -352,6 +464,31 @@ class Backup { return new PreciseDate(backupInfo.expireTime as DateStruct); } + getMaxExpireTime(): Promise; + getMaxExpireTime(callback: GetExpireTimeCallback): void; + /** + * Retrieves the max expiry time of the backup. + * + * @see {@link #updateExpireTime} + * @see {@link #getMetadata} + * + * @method Backup#getMaxExpireTime + * @returns {Promise} When resolved, contains the + * max expire time of the backup if it exists. + * + * @example + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner(); + * const instance = spanner.instance('my-instance'); + * const backup = instance.backup('my-backup'); + * const maxExpireTime = await backup.getMaxExpireTime(); + * console.log(`Backup max expires on ${maxExpireTime.toISOString()}`); + */ + async getMaxExpireTime(): Promise { + const [backupInfo] = await this.getMetadata(); + return new PreciseDate(backupInfo.maxExpireTime as DateStruct); + } + exists(): Promise; exists(callback: ExistsCallback): void; /** @@ -550,7 +687,7 @@ class Backup { * that a callback is omitted. */ promisifyAll(Backup, { - exclude: ['getState', 'getExpireTime', 'exists'], + exclude: ['getState', 'getExpireTime', 'getMaxExpireTime', 'exists'], }); /*! Developer Documentation diff --git a/src/instance.ts b/src/instance.ts index 95804e3f3..bf5b777cd 100644 --- a/src/instance.ts +++ b/src/instance.ts @@ -36,11 +36,12 @@ import { import {Duplex} from 'stream'; import {SessionPoolOptions, SessionPool} from './session-pool'; import {grpc, Operation as GaxOperation, CallOptions} from 'google-gax'; -import {Backup} from './backup'; +import {Backup, CopyBackupCallback, CopyBackupResponse} from './backup'; import {google as instanceAdmin} from '../protos/protos'; import {google as databaseAdmin} from '../protos/protos'; import {google as spannerClient} from '../protos/protos'; import {CreateInstanceRequest} from './index'; +import {CopyBackupOptions} from './backup'; export type IBackup = databaseAdmin.spanner.admin.database.v1.IBackup; export type IDatabase = databaseAdmin.spanner.admin.database.v1.IDatabase; @@ -249,6 +250,24 @@ class Instance extends common.GrpcServiceObject { return new Backup(this, backupId); } + copyBackup( + sourceBackupId: string, + backupId: string, + options: CopyBackupOptions, + callback?: CopyBackupCallback + ): Promise | void { + if (!backupId || !sourceBackupId) { + throw new Error( + 'A backup ID and source backup ID is required to create a Backup.' + ); + } + const copyOfBackup = new Backup(this, backupId, sourceBackupId); + if (callback !== null) { + return copyOfBackup.create(options, callback); + } + return copyOfBackup.create(options); + } + getBackups(options?: GetBackupsOptions): Promise; getBackups(callback: GetBackupsCallback): void; getBackups(options: GetBackupsOptions, callback: GetBackupsCallback): void; diff --git a/system-test/spanner.ts b/system-test/spanner.ts index 33729eb5f..311956c54 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -65,7 +65,6 @@ const GAX_OPTIONS: CallOptions = { }; const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); - describe('Spanner', () => { const envInstanceName = process.env.SPANNERTEST_INSTANCE; // True if a new instance has been created for this test run, false if reusing an existing instance @@ -1263,19 +1262,23 @@ describe('Spanner', () => { backup1Operation.metadata!.name, `${instance.formattedName_}/backups/${backup1Name}` ); - assert.strictEqual( - backup1Operation.metadata!.database, - database1.formattedName_ - ); + + if ('database' in backup1Operation.metadata) + assert.strictEqual( + backup1Operation.metadata!.database, + database1.formattedName_ + ); assert.strictEqual( backup2Operation.metadata!.name, `${instance.formattedName_}/backups/${backup2Name}` ); - assert.strictEqual( - backup2Operation.metadata!.database, - database2.formattedName_ - ); + + if ('database' in backup2Operation.metadata) + assert.strictEqual( + backup2Operation.metadata!.database, + database2.formattedName_ + ); // Wait for backups to finish. await backup1Operation.promise(); diff --git a/test/instance.ts b/test/instance.ts index ac82bb273..233822bed 100644 --- a/test/instance.ts +++ b/test/instance.ts @@ -1457,6 +1457,12 @@ describe('Instance', () => { }, ]; + const COPY_BACKUPS = [ + { + expireTime: new PreciseDate(1000), + }, + ]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const REQUEST_RESPONSE_ARGS: any = [null, BACKUPS, null, {}]; @@ -1629,6 +1635,7 @@ describe('Instance', () => { describe('backup', () => { const BACKUP_NAME = 'backup-name'; + const COPY_BACKUP_NAME = 'copy-backup-name'; it('should throw if a backup ID is not provided', () => { assert.throws(() => { @@ -1642,6 +1649,14 @@ describe('Instance', () => { assert.strictEqual(backup.calledWith_[0], instance); assert.strictEqual(backup.calledWith_[1], BACKUP_NAME); }); + + it('should return an instance of Copy Backup', () => { + const copy_backup = instance.copy_backup(COPY_BACKUP_NAME, BACKUP_NAME) as {} as FakeBackup; + assert(copy_backup instanceof FakeBackup); + assert.strictEqual(copy_backup.calledWith_[0], instance); + assert.strictEqual(copy_backup.calledWith_[1], COPY_BACKUP_NAME); + assert.strictEqual(copy_backup.calledWith_[2], BACKUP_NAME); + }); }); describe('getBackupOperations', () => { From 069c74c522d648cf8ba8c68272687afcd417cad9 Mon Sep 17 00:00:00 2001 From: Astha Mohta Date: Tue, 4 Jan 2022 15:04:56 +0530 Subject: [PATCH 2/7] feat:changes to update backup sample --- samples/backups-copy.js | 1 - samples/backups-update.js | 2 -- system-test/spanner.ts | 1 + test/instance.ts | 15 --------------- 4 files changed, 1 insertion(+), 18 deletions(-) diff --git a/samples/backups-copy.js b/samples/backups-copy.js index efa12f7bd..85a1dc1eb 100644 --- a/samples/backups-copy.js +++ b/samples/backups-copy.js @@ -42,7 +42,6 @@ function main( // Instantiates a client const spanner = new Spanner({ projectId: projectId, - apiEndpoint: 'staging-wrenchworks.sandbox.googleapis.com', }); async function spannerCopyBackup() { diff --git a/samples/backups-update.js b/samples/backups-update.js index 8da5e2018..9286051c2 100644 --- a/samples/backups-update.js +++ b/samples/backups-update.js @@ -31,7 +31,6 @@ async function updateBackup(instanceId, backupId, projectId) { // Creates a client const spanner = new Spanner({ projectId: projectId, - apiEndpoint: 'staging-wrenchworks.sandbox.googleapis.com', }); // Gets a reference to a Cloud Spanner instance and backup @@ -61,4 +60,3 @@ async function updateBackup(instanceId, backupId, projectId) { } module.exports.updateBackup = updateBackup; -//updateBackup('test-cross-region', 'backup123', 'appdev-soda-spanner-staging'); diff --git a/system-test/spanner.ts b/system-test/spanner.ts index 311956c54..a7a40a429 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -65,6 +65,7 @@ const GAX_OPTIONS: CallOptions = { }; const CURRENT_TIME = Math.round(Date.now() / 1000).toString(); + describe('Spanner', () => { const envInstanceName = process.env.SPANNERTEST_INSTANCE; // True if a new instance has been created for this test run, false if reusing an existing instance diff --git a/test/instance.ts b/test/instance.ts index 233822bed..ac82bb273 100644 --- a/test/instance.ts +++ b/test/instance.ts @@ -1457,12 +1457,6 @@ describe('Instance', () => { }, ]; - const COPY_BACKUPS = [ - { - expireTime: new PreciseDate(1000), - }, - ]; - // eslint-disable-next-line @typescript-eslint/no-explicit-any const REQUEST_RESPONSE_ARGS: any = [null, BACKUPS, null, {}]; @@ -1635,7 +1629,6 @@ describe('Instance', () => { describe('backup', () => { const BACKUP_NAME = 'backup-name'; - const COPY_BACKUP_NAME = 'copy-backup-name'; it('should throw if a backup ID is not provided', () => { assert.throws(() => { @@ -1649,14 +1642,6 @@ describe('Instance', () => { assert.strictEqual(backup.calledWith_[0], instance); assert.strictEqual(backup.calledWith_[1], BACKUP_NAME); }); - - it('should return an instance of Copy Backup', () => { - const copy_backup = instance.copy_backup(COPY_BACKUP_NAME, BACKUP_NAME) as {} as FakeBackup; - assert(copy_backup instanceof FakeBackup); - assert.strictEqual(copy_backup.calledWith_[0], instance); - assert.strictEqual(copy_backup.calledWith_[1], COPY_BACKUP_NAME); - assert.strictEqual(copy_backup.calledWith_[2], BACKUP_NAME); - }); }); describe('getBackupOperations', () => { From 68b8a2768ee6b814091ac1a3c107e0829ccf2119 Mon Sep 17 00:00:00 2001 From: Astha Mohta Date: Mon, 10 Jan 2022 10:54:55 +0530 Subject: [PATCH 3/7] fix: changes as per comments --- protos/protos.d.ts | 5 +++-- protos/protos.js | 9 ++++++++- protos/protos.json | 15 ++++++++++++++- samples/backups-copy.js | 10 +++++----- samples/index-read-data.js | 4 ---- samples/system-test/spanner.test.js | 1 - src/instance.ts | 2 +- 7 files changed, 31 insertions(+), 15 deletions(-) diff --git a/protos/protos.d.ts b/protos/protos.d.ts index f80d74cef..8df21aa14 100644 --- a/protos/protos.d.ts +++ b/protos/protos.d.ts @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -16192,7 +16192,8 @@ export namespace google { OUTPUT_ONLY = 3, INPUT_ONLY = 4, IMMUTABLE = 5, - UNORDERED_LIST = 6 + UNORDERED_LIST = 6, + NON_EMPTY_DEFAULT = 7 } /** Properties of a ResourceDescriptor. */ diff --git a/protos/protos.js b/protos/protos.js index bd41dbf9b..d1e728211 100644 --- a/protos/protos.js +++ b/protos/protos.js @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -5790,6 +5790,7 @@ case 4: case 5: case 6: + case 7: break; } } @@ -5894,6 +5895,10 @@ case 6: message[".google.api.fieldBehavior"][i] = 6; break; + case "NON_EMPTY_DEFAULT": + case 7: + message[".google.api.fieldBehavior"][i] = 7; + break; } } if (object[".google.api.resourceReference"] != null) { @@ -39954,6 +39959,7 @@ * @property {number} INPUT_ONLY=4 INPUT_ONLY value * @property {number} IMMUTABLE=5 IMMUTABLE value * @property {number} UNORDERED_LIST=6 UNORDERED_LIST value + * @property {number} NON_EMPTY_DEFAULT=7 NON_EMPTY_DEFAULT value */ api.FieldBehavior = (function() { var valuesById = {}, values = Object.create(valuesById); @@ -39964,6 +39970,7 @@ values[valuesById[4] = "INPUT_ONLY"] = 4; values[valuesById[5] = "IMMUTABLE"] = 5; values[valuesById[6] = "UNORDERED_LIST"] = 6; + values[valuesById[7] = "NON_EMPTY_DEFAULT"] = 7; return values; })(); diff --git a/protos/protos.json b/protos/protos.json index 62431f733..86405f087 100644 --- a/protos/protos.json +++ b/protos/protos.json @@ -575,6 +575,18 @@ ] ], "reserved": [ + [ + 4, + 4 + ], + [ + 5, + 5 + ], + [ + 6, + 6 + ], [ 8, 8 @@ -4327,7 +4339,8 @@ "OUTPUT_ONLY": 3, "INPUT_ONLY": 4, "IMMUTABLE": 5, - "UNORDERED_LIST": 6 + "UNORDERED_LIST": 6, + "NON_EMPTY_DEFAULT": 7 } }, "resourceReference": { diff --git a/samples/backups-copy.js b/samples/backups-copy.js index 85a1dc1eb..4445e155a 100644 --- a/samples/backups-copy.js +++ b/samples/backups-copy.js @@ -1,4 +1,4 @@ -// Copyright 2021 Google LLC +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,8 +13,8 @@ // limitations under the License. // sample-metadata: -// title: Read data using an existing index. -// usage: node spannerCopyBackup +// title: Copies a source backup +// usage: node spannerCopyBackup 'use strict'; @@ -32,7 +32,7 @@ function main( // const instanceId = 'my-instance'; // const databaseId = 'my-database'; // const backupId = 'my-backup', - // sourceBackupId = 'my-source-backup', + // const sourceBackupId = 'my-source-backup', // const projectId = 'my-project-id'; // Imports the Google Cloud Spanner client library @@ -50,7 +50,7 @@ function main( const database = instance.database(databaseId); const sourceBackup = instance.backup(sourceBackupId); - // Expire source and copy backup 14 days in the future + // Expire copy backup 14 days in the future const expireTime = Spanner.timestamp( Date.now() + 1000 * 60 * 60 * 24 * 14 ).toStruct(); diff --git a/samples/index-read-data.js b/samples/index-read-data.js index ec5dedc6a..e566a997b 100644 --- a/samples/index-read-data.js +++ b/samples/index-read-data.js @@ -12,10 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -// sample-metadata: -// title: Read data using an existing index. -// usage: node readDataWithIndex - 'use strict'; function main( diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index 1f3b5533a..be6d81d15 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -64,7 +64,6 @@ const DEFAULT_LEADER_2 = 'us-east1'; const spanner = new Spanner({ projectId: PROJECT_ID, - apiEndpoint: 'staging-wrenchworks.sandbox.googleapis.com', }); const LABEL = 'node-sample-tests'; const GAX_OPTIONS = { diff --git a/src/instance.ts b/src/instance.ts index bf5b777cd..b6b8862e5 100644 --- a/src/instance.ts +++ b/src/instance.ts @@ -258,7 +258,7 @@ class Instance extends common.GrpcServiceObject { ): Promise | void { if (!backupId || !sourceBackupId) { throw new Error( - 'A backup ID and source backup ID is required to create a Backup.' + 'A backup ID and source backup ID is required to create a copy of the source backup.' ); } const copyOfBackup = new Backup(this, backupId, sourceBackupId); From 5332b8349427f150a4bdb4c2a4039758bf39b3ff Mon Sep 17 00:00:00 2001 From: Astha Mohta Date: Tue, 1 Feb 2022 13:23:42 +0530 Subject: [PATCH 4/7] feat: changes as per comments --- samples/backups-copy.js | 31 ++++----- samples/backups-update.js | 2 +- samples/index-read-data.js | 4 ++ samples/system-test/spanner.test.js | 3 +- src/backup.ts | 101 +++------------------------- src/instance.ts | 53 +++++++++++++-- system-test/spanner.ts | 8 +-- 7 files changed, 81 insertions(+), 121 deletions(-) diff --git a/samples/backups-copy.js b/samples/backups-copy.js index 4445e155a..505194373 100644 --- a/samples/backups-copy.js +++ b/samples/backups-copy.js @@ -14,15 +14,14 @@ // sample-metadata: // title: Copies a source backup -// usage: node spannerCopyBackup +// usage: node spannerCopyBackup 'use strict'; function main( instanceId = 'my-instance', - databaseId = 'my-database', backupId = 'my-backup', - sourceBackupId = 'my-source-backup', + sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', projectId = 'my-project-id' ) { // [START spanner_copy_backup] @@ -30,9 +29,8 @@ function main( * TODO(developer): Uncomment these variables before running the sample. */ // const instanceId = 'my-instance'; - // const databaseId = 'my-database'; // const backupId = 'my-backup', - // const sourceBackupId = 'my-source-backup', + // const sourceBackupPath = 'projects/my-project-id/instances/my-source-instance/backups/my-source-backup', // const projectId = 'my-project-id'; // Imports the Google Cloud Spanner client library @@ -45,23 +43,19 @@ function main( }); async function spannerCopyBackup() { - // Gets a reference to a Cloud Spanner instance, database and backup + // Gets a reference to a Cloud Spanner instance and backup const instance = spanner.instance(instanceId); - const database = instance.database(databaseId); - const sourceBackup = instance.backup(sourceBackupId); // Expire copy backup 14 days in the future const expireTime = Spanner.timestamp( Date.now() + 1000 * 60 * 60 * 24 * 14 ).toStruct(); - // Copy the backup of the database + // Copy the backup of the backup try { - console.log( - `Creating copy of the source backup ${sourceBackup.formattedName_}.` - ); + console.log(`Creating copy of the source backup ${sourceBackupPath}.`); const [, operation] = await instance.copyBackup( - sourceBackup.formattedName_, + sourceBackupPath, backupId, { expireTime: expireTime, @@ -69,7 +63,9 @@ function main( ); console.log( - `Waiting for backup copy${instance.backup(backupId)} to complete...` + `Waiting for backup copy ${ + instance.backup(backupId).formattedName_ + } to complete...` ); await operation.promise(); @@ -80,16 +76,15 @@ function main( console.log( `Backup copy ${copyBackupInfo.name} of size ` + `${copyBackupInfo.sizeBytes} bytes was created at ` + - `${new PreciseDate(copyBackupInfo.createTime).toISOString()}` + `${new PreciseDate(copyBackupInfo.createTime).toISOString()} ` + + 'with version time' + + `${new PreciseDate(copyBackupInfo.versionTime).toISOString()}` ); } else { console.error('ERROR: Copy of backup is not ready.'); } } catch (err) { console.error('ERROR:', err); - } finally { - // Close the database when finished. - await database.close(); } } spannerCopyBackup(); diff --git a/samples/backups-update.js b/samples/backups-update.js index 9286051c2..639513821 100644 --- a/samples/backups-update.js +++ b/samples/backups-update.js @@ -40,7 +40,7 @@ async function updateBackup(instanceId, backupId, projectId) { // Read backup metadata and update expiry time try { const currentExpireTime = await backup.getExpireTime(); - const maxExpireTime = await backup.getMaxExpireTime(); + const maxExpireTime = backup.metadata.maxExpireTime; const wantExpireTime = new PreciseDate(currentExpireTime); wantExpireTime.setDate(wantExpireTime.getDate() + 1); // New expire time should be less than the max expire time diff --git a/samples/index-read-data.js b/samples/index-read-data.js index e566a997b..ec5dedc6a 100644 --- a/samples/index-read-data.js +++ b/samples/index-read-data.js @@ -12,6 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. +// sample-metadata: +// title: Read data using an existing index. +// usage: node readDataWithIndex + 'use strict'; function main( diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index 9271ad5c6..ca5d64b74 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -1026,8 +1026,9 @@ describe('Spanner', () => { // copy_backup it('should create a copy of a backup', async () => { + const sourceBackupPath = `projects/${PROJECT_ID}/instances/${INSTANCE_ID}/backups/${BACKUP_ID}`; const output = execSync( - `node backups-copy.js ${INSTANCE_ID} ${DATABASE_ID} ${COPY_BACKUP_ID} ${BACKUP_ID} ${PROJECT_ID}` + `node backups-copy.js ${INSTANCE_ID} ${COPY_BACKUP_ID} ${sourceBackupPath} ${PROJECT_ID}` ); assert.match( output, diff --git a/src/backup.ts b/src/backup.ts index 3f50f3592..7fb9975db 100644 --- a/src/backup.ts +++ b/src/backup.ts @@ -58,7 +58,7 @@ export interface CreateBackupOptions { } export interface CopyBackupGaxOperation extends GaxOperation { - // Overridden with more specific type for CreateBackup operation + // Overridden with more specific type for CopyBackup operation metadata: Metadata & databaseAdmin.spanner.admin.database.v1.ICopyBackupMetadata; } @@ -70,6 +70,7 @@ export interface CopyBackupOptions gaxOptions?: CallOptions; } + /** * IBackup structure with backup state enum translated to string form. */ @@ -98,7 +99,6 @@ export type GetStateCallback = NormalCallback< EnumKey >; export type GetExpireTimeCallback = NormalCallback; -export type GetMaxExpireTimeCallback = NormalCallback; export type ExistsCallback = NormalCallback; /** * The {@link Backup} class represents a Cloud Spanner backup. @@ -113,6 +113,14 @@ export type ExistsCallback = NormalCallback; * const spanner = new Spanner(); * const instance = spanner.instance('my-instance'); * const backup = instance.backup('my-backup'); + * ``` + * + * ``` + * * @example + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner(); + * const instance = spanner.instance('my-instance'); + * const sourceBackup = instance.backup('my-source-backup'); * const copyBackup = instance.copyBackup('my-copy-backup', 'my-source-backup'); * ``` */ @@ -135,7 +143,6 @@ class Backup { }; } - /** * @typedef {object} CreateBackupOptions * @property {string} databasePath The database path. @@ -151,20 +158,6 @@ class Backup { * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} * for more details. */ - /** - * @typedef {object} CopyBackupOptions - * @property {string|null} - * sourceBackup The full path of the backup to be copied - * @property {string|number|google.protobuf.Timestamp|external:PreciseDate} - * expireTime The expire time of the backup. - * @property {google.spanner.admin.database.v1.ICopyBackupEncryptionConfig} - * encryptionConfig An encryption configuration describing the - * encryption type and key resources in Cloud KMS to be used to encrypt - * the copy backup. - * @property {object} [gaxOptions] The request configuration options, - * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} - * for more details. - */ /** * @typedef {array} CreateBackupResponse * @property {Backup} 0 The new {@link Backup}. @@ -172,13 +165,6 @@ class Backup { * the status of the request. * @property {object} 2 The full API response. */ - /** - * @typedef {array} CopyBackupResponse - * @property {Backup} 0 The new {@link Backup}. - * @property {google.longrunning.Operation} 1 An {@link Operation} object that can be used to check - * the status of the request. - * @property {object} 2 The full API response. - */ /** * @callback CreateBackupCallback * @param {?Error} err Request error, if any. @@ -187,14 +173,6 @@ class Backup { * check the status of the request. * @param {object} apiResponse The full API response. */ - /** - * @callback CopyBackupCallback - * @param {?Error} err Request error, if any. - * @param {Backup} backup The new {@link Backup}. - * @param {google.longrunning.Operation} operation An {@link Operation} object that can be used to - * check the status of the request. - * @param {object} apiResponse The full API response. - */ /** * Create a backup. * @@ -229,35 +207,6 @@ class Backup { * await backupOperation.promise(); * ``` */ - /** - * Copy a backup. - * - * @method Backup#create - * @param {CopyBackupOptions} options Parameters for copying a backup. - * @param {CallOptions} [options.gaxOptions] The request configuration - * options, See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} - * for more details. - * @param {CopyBackupCallback} [callback] Callback function. - * @returns {Promise} When resolved, the copy backup - * operation will have started, but will not have necessarily completed. - * - * @example - * const {Spanner} = require('@google-cloud/spanner'); - * const spanner = new Spanner(); - * const instance = spanner.instance('my-instance'); - * const oneDay = 1000 * 60 * 60 * 24; - * const expireTime = Date.now() + oneDay; - * const versionTime = Date.now() - oneDay; - * const sourceBackup = instance.backup('my-source-backup'); - * const [, copyBackupOperation] = instance.copyBackup(sourceBackup.formattedName_, 'my-copy-backup', { - * expireTime: expireTime, - * encryptionConfig: { - * encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', - * kmsKeyName: 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key', - * },); - * // Await completion of the copy backup operation. - * await copybBackupOperation.promise(); - */ create( options: CreateBackupOptions | CopyBackupOptions ): Promise | Promise; @@ -351,7 +300,6 @@ class Backup { * * @see {@link #getState} * @see {@link #getExpireTime} - * @see {@link #getMaxExpireTime} * * @method Backup#getMetadata * @param {object} [gaxOptions] Request configuration options, @@ -469,33 +417,6 @@ class Backup { return new PreciseDate(backupInfo.expireTime as DateStruct); } - getMaxExpireTime(): Promise; - getMaxExpireTime(callback: GetExpireTimeCallback): void; - /** - * Retrieves the max expiry time of the backup. - * - * @see {@link #updateExpireTime} - * @see {@link #getMetadata} - * - * @method Backup#getMaxExpireTime - * @returns {Promise} When resolved, contains the - * max expire time of the backup if it exists. - * - * @example - * const {Spanner} = require('@google-cloud/spanner'); - * const spanner = new Spanner(); - * const instance = spanner.instance('my-instance'); - * const backup = instance.backup('my-backup'); - * const maxExpireTime = await backup.getMaxExpireTime(); - * console.log(`Backup max expires on ${maxExpireTime.toISOString()}`); - */ - async getMaxExpireTime(): Promise { - const [backupInfo] = await this.getMetadata(); - return new PreciseDate(backupInfo.maxExpireTime as DateStruct); - } - - exists(): Promise; - exists(callback: ExistsCallback): void; /** * Checks whether the backup exists. * @@ -702,7 +623,7 @@ class Backup { * that a callback is omitted. */ promisifyAll(Backup, { - exclude: ['getState', 'getExpireTime', 'getMaxExpireTime', 'exists'], + exclude: ['getState', 'getExpireTime', 'exists'], }); /*! Developer Documentation diff --git a/src/instance.ts b/src/instance.ts index 6b907c8c7..6e6c9f042 100644 --- a/src/instance.ts +++ b/src/instance.ts @@ -41,12 +41,16 @@ import { CallOptions, GoogleError, } from 'google-gax'; -import {Backup, CopyBackupCallback, CopyBackupResponse} from './backup'; +import { + Backup, + CopyBackupCallback, + CopyBackupResponse, + CopyBackupOptions, +} from './backup'; import {google as instanceAdmin} from '../protos/protos'; import {google as databaseAdmin} from '../protos/protos'; import {google as spannerClient} from '../protos/protos'; import {CreateInstanceRequest} from './index'; -import {CopyBackupOptions} from './backup'; export type IBackup = databaseAdmin.spanner.admin.database.v1.IBackup; export type IDatabase = databaseAdmin.spanner.admin.database.v1.IDatabase; @@ -261,6 +265,44 @@ class Instance extends common.GrpcServiceObject { return new Backup(this, backupId); } + /** + * Get a reference to a Backup object. + * + * @throws {GoogleError} If any parameter is not provided. + * + * @typedef {object} CopyBackupOptions + * * @property {string|null} + * * sourceBackup The full path of the backup to be copied + * * @property {string|number|google.protobuf.Timestamp|external:PreciseDate} + * * expireTime The expire time of the backup. + * * @property {google.spanner.admin.database.v1.ICopyBackupEncryptionConfig} + * * encryptionConfig An encryption configuration describing the + * * encryption type and key resources in Cloud KMS to be used to encrypt + * * the copy backup. + * * @property {object} [gaxOptions] The request configuration options, + * * See {@link https://googleapis.dev/nodejs/google-gax/latest/interfaces/CallOptions.html|CallOptions} + * * for more details. + * */ + /** + * @callback CopyBackupCallback + * @param {string} sourceBackupId Full path of the source backup to be copied. + * @param {string} backupId The name of the backup. + * @param {CopyBackupOptions} + * @return {Backup} A Backup object. + * + * @example + * ``` + * const {Spanner} = require('@google-cloud/spanner'); + * const spanner = new Spanner(); + * const instance = spanner.instance('my-instance'); + * const backup = instance.backup('my-source-backup','my-backup',{ + * expireTime: expireTime, + * encryptionConfig: { + * encryptionType: 'CUSTOMER_MANAGED_ENCRYPTION', + * kmsKeyName: 'projects/my-project-id/my-region/keyRings/my-key-ring/cryptoKeys/my-key', + * },); + * ``` + */ copyBackup( sourceBackupId: string, backupId: string, @@ -268,20 +310,17 @@ class Instance extends common.GrpcServiceObject { callback?: CopyBackupCallback ): Promise | void { if (!backupId || !sourceBackupId) { - throw new Error( + throw new GoogleError( 'A backup ID and source backup ID is required to create a copy of the source backup.' ); } const copyOfBackup = new Backup(this, backupId, sourceBackupId); - if (callback !== null) { + if (callback) { return copyOfBackup.create(options, callback); } return copyOfBackup.create(options); } - getBackups(options?: GetBackupsOptions): Promise; - getBackups(callback: GetBackupsCallback): void; - getBackups(options: GetBackupsOptions, callback: GetBackupsCallback): void; /** * Query object for listing backups. * diff --git a/system-test/spanner.ts b/system-test/spanner.ts index 1be0d1a12..eab5f4592 100644 --- a/system-test/spanner.ts +++ b/system-test/spanner.ts @@ -1263,23 +1263,23 @@ describe('Spanner', () => { backup1Operation.metadata!.name, `${instance.formattedName_}/backups/${backup1Name}` ); - - if ('database' in backup1Operation.metadata) + if ('database' in backup1Operation.metadata) { assert.strictEqual( backup1Operation.metadata!.database, database1.formattedName_ ); + } assert.strictEqual( backup2Operation.metadata!.name, `${instance.formattedName_}/backups/${backup2Name}` ); - - if ('database' in backup2Operation.metadata) + if ('database' in backup2Operation.metadata) { assert.strictEqual( backup2Operation.metadata!.database, database2.formattedName_ ); + } // Wait for backups to finish. await backup1Operation.promise(); From 2e489e32565585cfa3c9dc430d2d5e4fa5c09f9e Mon Sep 17 00:00:00 2001 From: Astha Mohta Date: Tue, 22 Mar 2022 12:15:49 +0530 Subject: [PATCH 5/7] feat: updating samples --- samples/backups-copy.js | 4 ++-- samples/backups-get-operations.js | 36 +++++++++++++++++++++++++++-- samples/backups.js | 9 ++++++-- samples/system-test/spanner.test.js | 8 ++++++- 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/samples/backups-copy.js b/samples/backups-copy.js index 505194373..9636be972 100644 --- a/samples/backups-copy.js +++ b/samples/backups-copy.js @@ -51,7 +51,7 @@ function main( Date.now() + 1000 * 60 * 60 * 24 * 14 ).toStruct(); - // Copy the backup of the backup + // Copy the source backup try { console.log(`Creating copy of the source backup ${sourceBackupPath}.`); const [, operation] = await instance.copyBackup( @@ -77,7 +77,7 @@ function main( `Backup copy ${copyBackupInfo.name} of size ` + `${copyBackupInfo.sizeBytes} bytes was created at ` + `${new PreciseDate(copyBackupInfo.createTime).toISOString()} ` + - 'with version time' + + 'with version time ' + `${new PreciseDate(copyBackupInfo.versionTime).toISOString()}` ); } else { diff --git a/samples/backups-get-operations.js b/samples/backups-get-operations.js index cd8a4dedf..9369b3e74 100644 --- a/samples/backups-get-operations.js +++ b/samples/backups-get-operations.js @@ -15,7 +15,12 @@ 'use strict'; -async function getBackupOperations(instanceId, databaseId, projectId) { +async function getBackupOperations( + instanceId, + databaseId, + backupId, + projectId +) { // [START spanner_list_backup_operations] // Imports the Google Cloud client library const {Spanner, protos} = require('@google-cloud/spanner'); @@ -25,6 +30,7 @@ async function getBackupOperations(instanceId, databaseId, projectId) { */ // const projectId = 'my-project-id'; // const databaseId = 'my-database'; + // const backupId = 'my-backup'; // const instanceId = 'my-instance'; // Creates a client @@ -35,7 +41,7 @@ async function getBackupOperations(instanceId, databaseId, projectId) { // Gets a reference to a Cloud Spanner instance const instance = spanner.instance(instanceId); - // List backup operations + // List create backup operations try { const [backupOperations] = await instance.getBackupOperations({ filter: @@ -56,6 +62,32 @@ async function getBackupOperations(instanceId, databaseId, projectId) { } catch (err) { console.error('ERROR:', err); } + + // List copy backup operations + try { + console.log( + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) ' + + `AND (metadata.source_backup:${backupId})` + ); + const [backupOperations] = await instance.getBackupOperations({ + filter: + '(metadata.@type:type.googleapis.com/google.spanner.admin.database.v1.CopyBackupMetadata) ' + + `AND (metadata.source_backup:${backupId})`, + }); + console.log('Copy Backup Operations:'); + backupOperations.forEach(backupOperation => { + const metadata = + protos.google.spanner.admin.database.v1.CopyBackupMetadata.decode( + backupOperation.metadata.value + ); + console.log( + `Backup ${metadata.name} copied from source backup ${metadata.sourceBackup} is ` + + `${metadata.progress.progressPercent}% complete.` + ); + }); + } catch (err) { + console.error('ERROR:', err); + } // [END spanner_list_backup_operations] } diff --git a/samples/backups.js b/samples/backups.js index 1ef05454e..3cedeef18 100644 --- a/samples/backups.js +++ b/samples/backups.js @@ -83,11 +83,16 @@ require('yargs') ) ) .command( - 'getBackupOperations ', + 'getBackupOperations ', 'Lists all backup operations in the instance.', {}, opts => - getBackupOperations(opts.instanceName, opts.databaseName, opts.projectId) + getBackupOperations( + opts.instanceName, + opts.databaseName, + opts.backupName, + opts.projectId + ) ) .command( 'getDatabaseOperations ', diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index ca5d64b74..d08e6d3ae 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -1063,13 +1063,19 @@ describe('Spanner', () => { // list_backup_operations it('should list backup operations in the instance', async () => { const output = execSync( - `${backupsCmd} getBackupOperations ${INSTANCE_ID} ${DATABASE_ID} ${PROJECT_ID}` + `${backupsCmd} getBackupOperations ${INSTANCE_ID} ${DATABASE_ID} ${BACKUP_ID} ${PROJECT_ID}` ); assert.match(output, /Create Backup Operations:/); assert.match( output, new RegExp(`Backup (.+)${BACKUP_ID} (.+) is 100% complete`) ); + assert.match(output, /Copy Backup Operations:/); + assert.match( + output, + new RegExp(`Backup (.+)${COPY_BACKUP_ID} (.+) is 100% complete`) + ); + console.log(output); }); // update_backup_expire_time From 1aa86a682f5b8b154bea1dbcac56e08bc9cfced8 Mon Sep 17 00:00:00 2001 From: Astha Mohta Date: Tue, 22 Mar 2022 12:21:05 +0530 Subject: [PATCH 6/7] fix: linting changes --- samples/system-test/spanner.test.js | 1 - src/backup.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/samples/system-test/spanner.test.js b/samples/system-test/spanner.test.js index d08e6d3ae..adeee8f38 100644 --- a/samples/system-test/spanner.test.js +++ b/samples/system-test/spanner.test.js @@ -1075,7 +1075,6 @@ describe('Spanner', () => { output, new RegExp(`Backup (.+)${COPY_BACKUP_ID} (.+) is 100% complete`) ); - console.log(output); }); // update_backup_expire_time diff --git a/src/backup.ts b/src/backup.ts index 7fb9975db..34cb71dfb 100644 --- a/src/backup.ts +++ b/src/backup.ts @@ -70,7 +70,6 @@ export interface CopyBackupOptions gaxOptions?: CallOptions; } - /** * IBackup structure with backup state enum translated to string form. */ From c0c3a9682a58b599658c69a91ed7282c993a58a2 Mon Sep 17 00:00:00 2001 From: Owl Bot Date: Tue, 22 Mar 2022 10:28:26 +0000 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=A6=89=20Updates=20from=20OwlBot=20po?= =?UTF-8?q?st-processor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md --- README.md | 1 + samples/README.md | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/README.md b/README.md index 2c4a6e520..4b0cdb25f 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/nodejs-spanner/tre | Sample | Source Code | Try it | | --------------------------- | --------------------------------- | ------ | | Backups-cancel | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-cancel.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/backups-cancel.js,samples/README.md) | +| Copies a source backup | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-copy.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/backups-copy.js,samples/README.md) | | Backups-create-with-encryption-key | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-create-with-encryption-key.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/backups-create-with-encryption-key.js,samples/README.md) | | Backups-create | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-create.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/backups-create.js,samples/README.md) | | Backups-delete | [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-delete.js) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/backups-delete.js,samples/README.md) | diff --git a/samples/README.md b/samples/README.md index 5383994e3..8e7095045 100644 --- a/samples/README.md +++ b/samples/README.md @@ -15,6 +15,7 @@ and automatic, synchronous replication for high availability. * [Before you begin](#before-you-begin) * [Samples](#samples) * [Backups-cancel](#backups-cancel) + * [Copies a source backup](#copies-a-source-backup) * [Backups-create-with-encryption-key](#backups-create-with-encryption-key) * [Backups-create](#backups-create) * [Backups-delete](#backups-delete) @@ -95,6 +96,23 @@ __Usage:__ +### Copies a source backup + +View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-copy.js). + +[![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/nodejs-spanner&page=editor&open_in_editor=samples/backups-copy.js,samples/README.md) + +__Usage:__ + + +`node spannerCopyBackup ` + + +----- + + + + ### Backups-create-with-encryption-key View the [source code](https://github.com/googleapis/nodejs-spanner/blob/main/samples/backups-create-with-encryption-key.js).