Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9c15366
docs: add sample files and their integration test for instance admin …
alkatrivedi Dec 21, 2023
3daea37
fix: lint errors
alkatrivedi Jan 20, 2024
53322e6
fix: header-checks errors
alkatrivedi Jan 20, 2024
9cc07e2
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Jan 28, 2024
1c89cd6
fix: add comments
alkatrivedi Jan 29, 2024
0bce60f
fix: add emulator support for instance admin client
alkatrivedi Feb 5, 2024
a381cdc
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 9, 2024
91705ff
fix: emulator support to admin client
alkatrivedi Feb 9, 2024
417e040
fix: remove unnecessary comments
alkatrivedi Feb 9, 2024
c3cd5b6
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 13, 2024
9c84ea2
fix: remove instance admin client emulator support
alkatrivedi Feb 13, 2024
37123d6
fix: lint errors
alkatrivedi Feb 13, 2024
ab5c3ad
docs: remove redundant code lines
alkatrivedi Feb 13, 2024
0cadac2
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 14, 2024
6211e29
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 14, 2024
e58f8a0
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 23, 2024
efc0a5c
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 26, 2024
916fda5
refactor code
alkatrivedi Feb 26, 2024
108df03
Merge branch 'googleapis:main' into apis-admin-instance
alkatrivedi Feb 28, 2024
20dbae3
refactor: autogen samples
alkatrivedi Feb 28, 2024
3f0b188
refactor: samples integraiton test
alkatrivedi Feb 28, 2024
9467f10
refactor: samples integration test
alkatrivedi Feb 28, 2024
8d87eee
remove unneeded file
alkatrivedi Feb 28, 2024
d77dc8e
refactor: samples integration test
alkatrivedi Feb 28, 2024
548caf7
refactor: integration tests
alkatrivedi Feb 28, 2024
0e5059d
refactor: spanner import
alkatrivedi Feb 28, 2024
d67b7b3
refactor: samples and comments
alkatrivedi Feb 28, 2024
340e4d7
refactor: samples integration test
alkatrivedi Feb 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
365 changes: 365 additions & 0 deletions samples/system-test/v2/spanner.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,365 @@
// 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 {InstanceAdminClient} = require('@google-cloud/spanner/build/src/v1');
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-west2';
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 instanceAdminClient = new InstanceAdminClient({
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('AdminClients', () => {
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.`)
);
});

// create_instance_with_autoscaling_config
it('should create an example instance with autoscaling config', 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.'
)
);
});

// 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}.`)
);
});
});

describe('leader options', () => {
before(async () => {
const [baseInstanceConfig] = await instanceAdminClient.getInstanceConfig({
name: instanceAdminClient.instanceConfigPath(
PROJECT_ID,
BASE_INSTANCE_CONFIG_ID
),
});
const [operation] = await instanceAdminClient.createInstanceConfig({
instanceConfigId: SAMPLE_INSTANCE_CONFIG_ID,
instanceConfig: {
name: instanceAdminClient.instanceConfigPath(
PROJECT_ID,
SAMPLE_INSTANCE_CONFIG_ID
),
baseConfig: instanceAdminClient.instanceConfigPath(
PROJECT_ID,
BASE_INSTANCE_CONFIG_ID
),
displayName: SAMPLE_INSTANCE_CONFIG_ID,
replicas: baseInstanceConfig.replicas,
optionalReplicas: baseInstanceConfig.optionalReplicas,
},
parent: instanceAdminClient.projectPath(PROJECT_ID),
});
await operation.promise();
});

after(async () => {
const [operation] = await instanceAdminClient.deleteInstanceConfig({
name: instanceAdminClient.instanceConfigPath(
PROJECT_ID,
SAMPLE_INSTANCE_CONFIG_ID
),
});
await operation.promise();
});

// 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');
});
});
});
Loading