diff --git a/README.md b/README.md index 3b2c43bd442..417e48553d8 100644 --- a/README.md +++ b/README.md @@ -330,6 +330,8 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | Update Jsonb Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateJsonbDataSample.java) | | Update Numeric Data Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateNumericDataSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateNumericDataSample.java) | | Update Using Dml Returning Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/UpdateUsingDmlReturningSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/UpdateUsingDmlReturningSample.java) | +| Create Database With Default Leader Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSample.java) | +| Create Instance Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java) | diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index f501936501b..b474d5fec68 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -142,9 +142,9 @@ 3.1.2 - spanner-testing-east1 - spanner-mr-testing - nam6 + java-client-integration-test + java-client-mr-integration-test + nam15 us-east1 cmek-test-key-ring cmek-test-key diff --git a/samples/snapshot/pom.xml b/samples/snapshot/pom.xml index 897f54a943d..62bd84491ed 100644 --- a/samples/snapshot/pom.xml +++ b/samples/snapshot/pom.xml @@ -141,13 +141,14 @@ 3.1.2 - spanner-testing-east1 - spanner-mr-testing - nam6 + java-client-integration-test + java-client-mr-integration-test + nam15 us-east1 cmek-test-key-ring cmek-test-key mysample + mysample-instance quick-db diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 154ce8cca35..25569c40169 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -178,9 +178,9 @@ 3.1.2 - spanner-testing-east1 - spanner-mr-testing - nam6 + java-client-integration-test + java-client-mr-integration-test + nam15 us-east1 cmek-test-key-ring cmek-test-key diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSample.java new file mode 100644 index 00000000000..eedbca8095a --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSample.java @@ -0,0 +1,78 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner.admin.generated; + +//[START spanner_create_database_with_default_leader] + +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.SpannerExceptionFactory; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.common.collect.ImmutableList; +import com.google.spanner.admin.database.v1.CreateDatabaseRequest; +import com.google.spanner.admin.database.v1.Database; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +public class CreateDatabaseWithDefaultLeaderSample { + + static void createDatabaseWithDefaultLeader() throws IOException { + // TODO(developer): Replace these variables before running the sample. + final String instanceName = "projects/my-project/instances/my-instance-id"; + final String databaseId = "my-database-name"; + final String defaultLeader = "my-default-leader"; + createDatabaseWithDefaultLeader(instanceName, databaseId, defaultLeader); + } + + static void createDatabaseWithDefaultLeader(String instanceName, String databaseId, + String defaultLeader) throws IOException { + DatabaseAdminClient databaseAdminClient = DatabaseAdminClient.create(); + + try { + Database createdDatabase = + databaseAdminClient.createDatabaseAsync( + CreateDatabaseRequest.newBuilder() + .setParent(instanceName) + .setCreateStatement("CREATE DATABASE `" + databaseId + "`") + .addAllExtraStatements( + ImmutableList.of("CREATE TABLE Singers (" + + " SingerId INT64 NOT NULL," + + " FirstName STRING(1024)," + + " LastName STRING(1024)," + + " SingerInfo BYTES(MAX)" + + ") PRIMARY KEY (SingerId)", + "CREATE TABLE Albums (" + + " SingerId INT64 NOT NULL," + + " AlbumId INT64 NOT NULL," + + " AlbumTitle STRING(MAX)" + + ") PRIMARY KEY (SingerId, AlbumId)," + + " INTERLEAVE IN PARENT Singers ON DELETE CASCADE", + "ALTER DATABASE " + "`" + databaseId + "`" + + " SET OPTIONS ( default_leader = '" + defaultLeader + "' )")) + .build()).get(); + System.out.println("Created database [" + createdDatabase.getName() + "]"); + System.out.println("\tDefault leader: " + createdDatabase.getDefaultLeader()); + } catch (ExecutionException e) { + // If the operation failed during execution, expose the cause. + throw (SpannerException) e.getCause(); + } catch (InterruptedException e) { + // Throw when a thread is waiting, sleeping, or otherwise occupied, + // and the thread is interrupted, either before or during the activity. + throw SpannerExceptionFactory.propagateInterrupt(e); + } + } +} +//[END spanner_create_database_with_default_leader] diff --git a/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java new file mode 100644 index 00000000000..88675c414cf --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/admin/generated/CreateInstanceExample.java @@ -0,0 +1,70 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner.admin.generated; + +//[START spanner_create_instance] +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.spanner.admin.instance.v1.CreateInstanceRequest; +import com.google.spanner.admin.instance.v1.Instance; +import com.google.spanner.admin.instance.v1.InstanceConfigName; +import com.google.spanner.admin.instance.v1.ProjectName; +import java.io.IOException; +import java.util.concurrent.ExecutionException; + +class CreateInstanceExample { + + static void createInstance() throws IOException { + // TODO(developer): Replace these variables before running the sample. + String projectId = "my-project"; + String instanceId = "my-instance"; + createInstance(projectId, instanceId); + } + + static void createInstance(String projectId, String instanceId) throws IOException { + InstanceAdminClient instanceAdminClient = InstanceAdminClient.create(); + + // Set Instance configuration. + int nodeCount = 2; + String displayName = "Descriptive name"; + + // Create an Instance object that will be used to create the instance. + Instance instance = + Instance.newBuilder() + .setDisplayName(displayName) + .setNodeCount(nodeCount) + .setConfig( + InstanceConfigName.of(projectId, "regional-us-central1").toString()) + .build(); + try { + // Wait for the createInstance operation to finish. + Instance createdInstance = instanceAdminClient.createInstanceAsync( + CreateInstanceRequest.newBuilder() + .setParent(ProjectName.of(projectId).toString()) + .setInstanceId(instanceId) + .setInstance(instance) + .build()).get(); + System.out.printf("Instance %s was successfully created%n", createdInstance.getName()); + } catch (ExecutionException e) { + System.out.printf( + "Error: Creating instance %s failed with error message %s%n", + instance.getName(), e.getMessage()); + } catch (InterruptedException e) { + System.out.println("Error: Waiting for createInstance operation to finish was interrupted"); + } + } +} +//[END spanner_create_instance] \ No newline at end of file diff --git a/samples/snippets/src/test/java/com/example/spanner/SampleIdGenerator.java b/samples/snippets/src/test/java/com/example/spanner/SampleIdGenerator.java index 10523d36b32..3b375b071e1 100644 --- a/samples/snippets/src/test/java/com/example/spanner/SampleIdGenerator.java +++ b/samples/snippets/src/test/java/com/example/spanner/SampleIdGenerator.java @@ -26,24 +26,38 @@ */ public class SampleIdGenerator { + private static final int INSTANCE_NAME_MAX_LENGTH = 30; private static final int DATABASE_NAME_MAX_LENGTH = 30; private static final int BACKUP_NAME_MAX_LENGTH = 30; private static final int INSTANCE_CONFIG_ID_MAX_LENGTH = 30; + private final List instanceIds; private final List databaseIds; private final List backupIds; private final List instanceConfigIds; private final String baseDatabaseId; private final String baseBackupId; private final String baseInstanceConfigId; + private final String baseInstanceId; - public SampleIdGenerator( - String baseDatabaseId, String baseBackupId, String baseInstanceConfigId) { + public SampleIdGenerator(String baseDatabaseId, String baseBackupId, + String baseInstanceConfigId, String baseInstanceId) { this.baseDatabaseId = baseDatabaseId; this.baseBackupId = baseBackupId; this.baseInstanceConfigId = baseInstanceConfigId; + this.baseInstanceId = baseInstanceId; this.databaseIds = new ArrayList<>(); this.backupIds = new ArrayList<>(); this.instanceConfigIds = new ArrayList<>(); + this.instanceIds = new ArrayList<>(); + } + + public String generateInstanceId() { + final String instanceId = + (baseInstanceId + "-" + UUID.randomUUID().toString().replaceAll("-", "")) + .substring(0, INSTANCE_NAME_MAX_LENGTH); + + instanceIds.add(instanceId); + return instanceId; } public String generateDatabaseId() { @@ -81,6 +95,10 @@ public List getBackupIds() { return backupIds; } + public List getInstanceIds() { + return instanceIds; + } + public List getInstanceConfigIds() { return instanceConfigIds; } diff --git a/samples/snippets/src/test/java/com/example/spanner/SampleTestBase.java b/samples/snippets/src/test/java/com/example/spanner/SampleTestBase.java index 39424c8d080..ccfe3e6d4d1 100644 --- a/samples/snippets/src/test/java/com/example/spanner/SampleTestBase.java +++ b/samples/snippets/src/test/java/com/example/spanner/SampleTestBase.java @@ -25,7 +25,8 @@ /** Base class for sample integration tests. */ public class SampleTestBase { - + private static final String BASE_INSTANCE_ID = + System.getProperty("spanner.sample.instance", "mysample-instance"); private static final String BASE_DATABASE_ID = System.getProperty("spanner.sample.database", "sampledb"); private static final String BASE_BACKUP_ID = "samplebk"; @@ -56,11 +57,27 @@ public static void beforeClass() { spanner = options.getService(); databaseAdminClient = spanner.getDatabaseAdminClient(); instanceAdminClient = spanner.getInstanceAdminClient(); - idGenerator = new SampleIdGenerator(BASE_DATABASE_ID, BASE_BACKUP_ID, BASE_INSTANCE_CONFIG_ID); + idGenerator = new SampleIdGenerator( + BASE_DATABASE_ID, BASE_BACKUP_ID, BASE_INSTANCE_CONFIG_ID, BASE_INSTANCE_ID); } @AfterClass public static void afterClass() { + for (String instanceId : idGenerator.getInstanceIds()) { + System.out.println("Trying to drop " + instanceId); + try { + // If the database is not found, it is ignored (no exception is thrown) + instanceAdminClient.deleteInstance(instanceId); + } catch (Exception e) { + System.out.println( + "Failed to drop instance " + + instanceId + + " due to " + + e.getMessage() + + ", skipping..."); + } + } + for (String databaseId : idGenerator.getDatabaseIds()) { System.out.println("Trying to drop " + databaseId); try { diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSampleIT.java new file mode 100644 index 00000000000..39d02e1ed5c --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateDatabaseWithDefaultLeaderSampleIT.java @@ -0,0 +1,61 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner.admin.generated; + +import static org.junit.Assert.assertTrue; + +import com.example.spanner.SampleRunner; +import com.google.spanner.admin.instance.v1.InstanceConfig; +import com.google.spanner.admin.instance.v1.InstanceName; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CreateDatabaseWithDefaultLeaderSampleIT extends SampleTestBaseV2 { + + @Test + public void testCreateDatabaseWithDefaultLeader() throws Exception { + final String databaseId = idGenerator.generateDatabaseId(); + + // Finds possible default leader + + final String instanceConfigId = instanceAdminClient.getInstance( + InstanceName.of(projectId, multiRegionalInstanceId)).getConfig(); + final InstanceConfig config = instanceAdminClient.getInstanceConfig(instanceConfigId); + assertTrue( + "Expected instance config " + instanceConfigId + " to have at least one leader option", + config.getLeaderOptionsCount() > 0 + ); + final String defaultLeader = config.getLeaderOptions(0); + + // Runs sample + final String out = SampleRunner.runSample(() -> + CreateDatabaseWithDefaultLeaderSample.createDatabaseWithDefaultLeader( + getInstanceName(projectId, multiRegionalInstanceId), + databaseId, + defaultLeader + ) + ); + + assertTrue( + "Expected created database to have default leader " + defaultLeader + "." + + " Output received was " + out, + out.contains("Default leader: " + defaultLeader) + ); + } +} diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceSampleIT.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceSampleIT.java new file mode 100644 index 00000000000..7743b24f132 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/CreateInstanceSampleIT.java @@ -0,0 +1,43 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner.admin.generated; + +import static org.junit.Assert.assertTrue; + +import com.example.spanner.SampleRunner; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CreateInstanceSampleIT extends SampleTestBaseV2 { + + @Test + public void testCreateInstance() throws Exception { + final String instanceId = idGenerator.generateInstanceId(); + + // Runs sample + final String out = SampleRunner.runSample(() -> + CreateInstanceExample.createInstance(projectId, instanceId) + ); + + assertTrue( + "Expected created instance " + instanceId + "." + + " Output received was " + out, out.contains("was successfully created") + ); + } +} diff --git a/samples/snippets/src/test/java/com/example/spanner/admin/generated/SampleTestBaseV2.java b/samples/snippets/src/test/java/com/example/spanner/admin/generated/SampleTestBaseV2.java new file mode 100644 index 00000000000..30989214fa3 --- /dev/null +++ b/samples/snippets/src/test/java/com/example/spanner/admin/generated/SampleTestBaseV2.java @@ -0,0 +1,160 @@ +/* + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.spanner.admin.generated; + +import com.example.spanner.SampleIdGenerator; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminSettings; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminSettings; +import java.io.IOException; +import java.util.concurrent.TimeUnit; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * Base class for sample integration intests using auto-generated admin clients. + */ +public class SampleTestBaseV2 { + + private static final String BASE_INSTANCE_ID = + System.getProperty("spanner.sample.instance", "mysample-instance"); + + private static final String BASE_DATABASE_ID = + System.getProperty("spanner.sample.database", "sampledb"); + private static final String BASE_BACKUP_ID = "samplebk"; + private static final String BASE_INSTANCE_CONFIG_ID = "sampleconfig"; + private static final int AWAIT_TERMINATION_SECONDS = 10; + + protected static String projectId; + protected static final String instanceId = System.getProperty("spanner.test.instance"); + protected static DatabaseAdminClient databaseAdminClient; + protected static InstanceAdminClient instanceAdminClient; + + protected static final String multiRegionalInstanceId = + System.getProperty("spanner.test.instance.mr"); + protected static final String instanceConfigName = System + .getProperty("spanner.test.instance.config"); + protected static SampleIdGenerator idGenerator; + + @BeforeClass + public static void beforeClass() throws IOException { + final String serverUrl = ""; + final SpannerOptions.Builder optionsBuilder = + SpannerOptions.newBuilder().setAutoThrottleAdministrativeRequests(); + final SpannerOptions options = optionsBuilder.build(); + final DatabaseAdminSettings.Builder databaseAdminSettingsBuilder = + DatabaseAdminSettings.newBuilder(); + final InstanceAdminSettings.Builder instanceAdminSettingBuilder = + InstanceAdminSettings.newBuilder(); + + if (!serverUrl.isEmpty()) { + databaseAdminSettingsBuilder.setEndpoint(serverUrl); + instanceAdminSettingBuilder.setEndpoint(serverUrl); + } + + projectId = options.getProjectId(); + databaseAdminClient = DatabaseAdminClient.create(databaseAdminSettingsBuilder.build()); + instanceAdminClient = InstanceAdminClient.create(instanceAdminSettingBuilder.build()); + idGenerator = new SampleIdGenerator( + BASE_DATABASE_ID, BASE_BACKUP_ID, BASE_INSTANCE_CONFIG_ID, BASE_INSTANCE_ID); + } + + @AfterClass + public static void afterClass() throws InterruptedException { + for (String instanceId : idGenerator.getInstanceIds()) { + System.out.println("Trying to drop " + instanceId); + try { + // If the database is not found, it is ignored (no exception is thrown) + instanceAdminClient.deleteInstance(getInstanceName(projectId, instanceId)); + } catch (Exception e) { + System.out.println( + "Failed to drop instance " + + instanceId + + " due to " + + e.getMessage() + + ", skipping..."); + } + } + + for (String databaseId : idGenerator.getDatabaseIds()) { + System.out.println("Trying to drop " + databaseId); + try { + // If the database is not found, it is ignored (no exception is thrown) + databaseAdminClient.dropDatabase( + getDatabaseName(projectId, instanceId, databaseId)); + databaseAdminClient.dropDatabase( + getDatabaseName(projectId, multiRegionalInstanceId, databaseId)); + } catch (Exception e) { + System.out.println( + "Failed to drop database " + + databaseId + + " due to " + + e.getMessage() + + ", skipping..."); + } + } + for (String backupId : idGenerator.getBackupIds()) { + try { + // If the backup is not found, it is ignored (no exception is thrown) + databaseAdminClient.deleteBackup( + getBackupName(projectId, instanceId, backupId)); + databaseAdminClient.deleteBackup( + getBackupName(projectId, multiRegionalInstanceId, backupId)); + } catch (Exception e) { + System.out.println( + "Failed to delete backup " + backupId + " due to " + e.getMessage() + ", skipping..."); + } + } + for (String configId : idGenerator.getInstanceConfigIds()) { + try { + // If the config is not found, it is ignored (no exception is thrown) + instanceAdminClient.deleteInstanceConfig(configId); + } catch (Exception e) { + System.out.println( + "Failed to delete instance config " + + configId + + " due to " + + e.getMessage() + + ", skipping..."); + } + } + + databaseAdminClient.close(); + instanceAdminClient.close(); + + databaseAdminClient.awaitTermination(AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + instanceAdminClient.awaitTermination(AWAIT_TERMINATION_SECONDS, TimeUnit.SECONDS); + } + + static String getDatabaseName(final String projectId, + final String instanceId, final String databaseId) { + return String.format( + "projects/%s/instances/%s/databases/%s", projectId, instanceId, databaseId); + } + + static String getBackupName(final String projectId, + final String instanceId, final String backupId) { + return String.format( + "projects/%s/instances/%s/backups/%s", projectId, instanceId, backupId); + } + + static String getInstanceName(final String projectId, final String instanceId) { + return String.format("projects/%s/instances/%s", projectId, instanceId); + } +}