Skip to content

Commit

Permalink
feat: support customer managed instance configurations (#1742)
Browse files Browse the repository at this point in the history
* feat: implementation changes to add support for
customer managed instance configurations.

* test: add more unit tests to Instance admin client

Unit tests each for create, update and delete
instance config operations have been added.

* feat: add samples for create, update and deleting
user managed instance configs.

* chore: add test instance config to samples pom

* test: add CUD instance config integration tests

* feat: add proto file as reference.

* feat: lint the code changes, add documentation

* feat: minor alignment changes

* feat: fix checkstyle violations.

* feat: change read-only fields setters to protected.

* feat: change setConfigType to protected.

* feat: change some more method access modifiers

* feat: add optional fields, some design changes.

* feat: some documentation changes

* feat: change BASE_CONFIG project name in tests.

* feat: pom.xml changes

* feat: add support for adding readonly replicas
while creating instance config.

* feat: clirr changes

* feat: some refactoring

* feat: some method doc changes.

* feat: incorporate review comments.

* feat: remove samples

* Update pom.xml

* Update SampleIdGenerator.java

* Update SampleRunner.java

* Update SampleTestBase.java

* feat: changes to InstanceConfigTest

* feat: changes to pom.xml

* Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceAdminClient.java doc

Co-authored-by: Knut Olav Løite <koloite@gmail.com>

* Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceAdminClient.java

Co-authored-by: Knut Olav Løite <koloite@gmail.com>

* Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceAdminClient.java

Co-authored-by: Knut Olav Løite <koloite@gmail.com>

* Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceAdminClient.java

Co-authored-by: Knut Olav Løite <koloite@gmail.com>

* Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java

Co-authored-by: Knut Olav Løite <koloite@gmail.com>

* Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceConfigInfo.java

Co-authored-by: Knut Olav Løite <koloite@gmail.com>

* feat: make setBaseConfig protected.

* feat: add method doc for addReadOnlyReplicas

* feat: incorporate review comments.

* feat: deprecate few constructors

* feat: make some methods private

* feat: move initialization to property declaration.

* feat: make InstanceConfig.Builder setters return
InstanceConfig.Builder

* feat: some doc changes

* feat: add support for ListInstanceConfigOperations

* fix: linting

* feat: few changes

* Update spanner_instance_admin.proto

* Update SpannerRpc.java

* feat: remove unnecessary parameter from
GapicSpannerRpc

* feat: clirr fix

* 🦉 Updates from OwlBot post-processor

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* feat: revert unintended sample changes

Co-authored-by: Knut Olav Løite <koloite@gmail.com>
Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Sep 29, 2022
1 parent 98c2303 commit c1c805c
Show file tree
Hide file tree
Showing 10 changed files with 1,280 additions and 50 deletions.
40 changes: 40 additions & 0 deletions google-cloud-spanner/clirr-ignored-differences.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,46 @@
<className>com/google/cloud/spanner/connection/ConnectionOptions</className>
<method>com.google.cloud.spanner.Dialect getDialect()</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/InstanceAdminClient</className>
<method>com.google.api.gax.longrunning.OperationFuture createInstanceConfig(com.google.cloud.spanner.InstanceConfigInfo, com.google.cloud.spanner.Options$CreateAdminApiOption[])</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/InstanceAdminClient</className>
<method>com.google.api.gax.longrunning.OperationFuture updateInstanceConfig(com.google.cloud.spanner.InstanceConfigInfo, java.lang.Iterable, com.google.cloud.spanner.Options$UpdateAdminApiOption[])</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/InstanceAdminClient</className>
<method>void deleteInstanceConfig(java.lang.String, com.google.cloud.spanner.Options$DeleteAdminApiOption[])</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/InstanceAdminClient</className>
<method>com.google.api.gax.paging.Page listInstanceConfigOperations(com.google.cloud.spanner.Options$ListOption[])</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.api.gax.longrunning.OperationFuture createInstanceConfig(java.lang.String, java.lang.String, com.google.spanner.admin.instance.v1.InstanceConfig, java.lang.Boolean)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.api.gax.longrunning.OperationFuture updateInstanceConfig(com.google.spanner.admin.instance.v1.InstanceConfig, java.lang.Boolean, com.google.protobuf.FieldMask)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>void deleteInstanceConfig(java.lang.String, java.lang.String, java.lang.Boolean)</method>
</difference>
<difference>
<differenceType>7012</differenceType>
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listInstanceConfigOperations(int, java.lang.String, java.lang.String)</method>
</difference>
<difference>
<differenceType>7013</differenceType>
<className>com/google/cloud/spanner/BackupInfo$Builder</className>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,141 @@
import com.google.api.gax.longrunning.OperationFuture;
import com.google.api.gax.paging.Page;
import com.google.cloud.Policy;
import com.google.cloud.spanner.Options.CreateAdminApiOption;
import com.google.cloud.spanner.Options.DeleteAdminApiOption;
import com.google.cloud.spanner.Options.ListOption;
import com.google.cloud.spanner.Options.UpdateAdminApiOption;
import com.google.longrunning.Operation;
import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata;
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
import com.google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata;
import com.google.spanner.admin.instance.v1.UpdateInstanceMetadata;

/** Client to do admin operations on Cloud Spanner Instance and Instance Configs. */
public interface InstanceAdminClient {

/**
* Creates an instance config and begins preparing it to be used. The returned {@code Operation}
* can be used to track the progress of preparing the new instance config. The instance config
* name is assigned by the caller and must start with the string 'custom'. If the named instance
* config already exists, a SpannerException is thrown.
*
* <p>Immediately after the request returns:
*
* <ul>
* <li>The instance config is readable via the API, with all requested attributes.
* <li>The instance config's {@code reconciling} field is set to true. Its state is {@code
* CREATING}.
* </ul>
*
* While the operation is pending:
*
* <ul>
* <li>Cancelling the operation renders the instance config immediately unreadable via the API.
* <li>Except for deleting the creating resource, all other attempts to modify the instance
* config are rejected.
* </ul>
*
* Upon completion of the returned operation:
*
* <ul>
* <li>Instances can be created using the instance configuration.
* <li>The instance config's {@code reconciling} field becomes false.
* <li>Its state becomes {@code READY}.
* </ul>
*
* <!--SNIPPET instance_admin_client_create_instance_config-->
*
* <pre>{@code
* String projectId = "my-project";
* String baseInstanceConfig = "my-base-config";
* String instanceConfigId = "custom-user-config";
*
* final InstanceConfig baseConfig = instanceAdminClient.getInstanceConfig(baseInstanceConfig);
*
* List<ReplicaInfo> readOnlyReplicas = ImmutableList.of(baseConfig.getOptionalReplicas().get(0));
*
* InstanceConfigInfo instanceConfigInfo =
* InstanceConfigInfo.newBuilder(InstanceConfigId.of(projectId, instanceConfigId), baseConfig)
* .setDisplayName(instanceConfigId)
* .addReadOnlyReplicas(readOnlyReplicas)
* .build();
*
* final OperationFuture<InstanceConfig, CreateInstanceConfigMetadata> operation =
* instanceAdminClient.createInstanceConfig(instanceConfigInfo);
*
* InstanceConfig instanceConfig = op.get(5, TimeUnit.MINUTES)
* }</pre>
*
* <!--SNIPPET instance_admin_client_create_instance_config-->
*/
default OperationFuture<InstanceConfig, CreateInstanceConfigMetadata> createInstanceConfig(
InstanceConfigInfo instanceConfig, CreateAdminApiOption... options) throws SpannerException {
throw new UnsupportedOperationException("Not implemented");
}

/**
* Updates a custom instance config. This can not be used to update a Google managed instance
* config. The returned {@code Operation} can be used to track the progress of updating the
* instance. If the named instance config does not exist, a SpannerException is thrown. The
* request must include at least one field to update.
*
* <p>Only user managed configurations can be updated.
*
* <p>Immediately after the request returns:
*
* <ul>
* <li>The instance config's {@code reconciling} field is set to true.
* </ul>
*
* While the operation is pending:
*
* <ul>
* <li>Cancelling the operation sets its metadata's cancel_time.
* <li>The operation is guaranteed to succeed at undoing all changes, after which point it
* terminates with a `CANCELLED` status.
* <li>All other attempts to modify the instance config are rejected.
* <li>Reading the instance config via the API continues to give the pre-request values.
* </ul>
*
* Upon completion of the returned operation:
*
* <ul>
* <li>Creating instances using the instance configuration uses the new values.
* <li>The instance config's new values are readable via the API.
* <li>The instance config's {@code reconciling} field becomes false.
* </ul>
*
* <!--SNIPPET instance_admin_client_update_instance_config-->
*
* <pre>{@code
* String projectId = "my-project";
* String instanceConfigId = "custom-user-config";
* String displayName = "my-display-name";
*
* InstanceConfigInfo instanceConfigInfo =
* InstanceConfigInfo.newBuilder(InstanceConfigId.of(projectId, instanceConfigId))
* .setDisplayName(displayName)
* .build();
*
* // Only update display name.
* final OperationFuture<InstanceConfig, UpdateInstanceConfigMetadata> operation =
* instanceAdminClient.updateInstanceConfig(
* instanceConfigInfo, ImmutableList.of(InstanceConfigField.DISPLAY_NAME));
*
* InstanceConfig instanceConfig = operation.get(5, TimeUnit.MINUTES);
* }</pre>
*
* <!--SNIPPET instance_admin_client_update_instance_config-->
*/
default OperationFuture<InstanceConfig, UpdateInstanceConfigMetadata> updateInstanceConfig(
InstanceConfigInfo instanceConfig,
Iterable<InstanceConfigInfo.InstanceConfigField> fieldsToUpdate,
UpdateAdminApiOption... options)
throws SpannerException {
throw new UnsupportedOperationException("Not implemented");
}

/** Gets an instance config. */
/* <!--SNIPPET instance_admin_client_get_instance_config-->
* <pre>{@code
Expand All @@ -37,6 +164,28 @@ public interface InstanceAdminClient {
*/
InstanceConfig getInstanceConfig(String configId) throws SpannerException;

/**
* Deletes a custom instance config. Deletion is only allowed for custom instance configs and when
* no instances are using the configuration. If any instances are using the config, a
* SpannerException is thrown.
*
* <p>Only user managed configurations can be deleted.
* <!--SNIPPET instance_admin_client_delete_instance_config-->
*
* <pre>{@code
* String projectId = "my-project";
* String instanceConfigId = "custom-user-config";
*
* instanceAdminClient.deleteInstanceConfig(instanceConfigId);
* }</pre>
*
* <!--SNIPPET instance_admin_client_delete_instance_config-->
*/
default void deleteInstanceConfig(String instanceConfigId, DeleteAdminApiOption... options)
throws SpannerException {
throw new UnsupportedOperationException("Not implemented");
}

/** Lists the supported instance configs for current project. */
/* <!--SNIPPET instance_admin_client_list_configs-->
* <pre>{@code
Expand All @@ -47,6 +196,11 @@ public interface InstanceAdminClient {
*/
Page<InstanceConfig> listInstanceConfigs(ListOption... options) throws SpannerException;

/** Lists long-running instance config operations. */
default Page<Operation> listInstanceConfigOperations(ListOption... options) {
throw new UnsupportedOperationException("Not implemented");
}

/**
* Creates an instance and begins preparing it to begin serving. The returned {@code Operation}
* can be used to track the progress of preparing the new instance. The instance name is assigned
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@
import com.google.api.pathtemplate.PathTemplate;
import com.google.cloud.Policy;
import com.google.cloud.Policy.DefaultMarshaller;
import com.google.cloud.spanner.Options.CreateAdminApiOption;
import com.google.cloud.spanner.Options.DeleteAdminApiOption;
import com.google.cloud.spanner.Options.ListOption;
import com.google.cloud.spanner.Options.UpdateAdminApiOption;
import com.google.cloud.spanner.SpannerImpl.PageFetcher;
import com.google.cloud.spanner.spi.v1.SpannerRpc;
import com.google.cloud.spanner.spi.v1.SpannerRpc.Paginated;
import com.google.common.base.Preconditions;
import com.google.longrunning.Operation;
import com.google.protobuf.FieldMask;
import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata;
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
import com.google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata;
import com.google.spanner.admin.instance.v1.UpdateInstanceMetadata;

/** Default implementation of {@link InstanceAdminClient} */
Expand Down Expand Up @@ -60,13 +65,81 @@ protected com.google.iam.v1.Policy toPb(Policy policy) {
this.dbClient = dbClient;
}

@Override
public OperationFuture<InstanceConfig, CreateInstanceConfigMetadata> createInstanceConfig(
InstanceConfigInfo instanceConfig, CreateAdminApiOption... options) throws SpannerException {
final Options createAdminApiOptions = Options.fromAdminApiOptions(options);
String projectName = PROJECT_NAME_TEMPLATE.instantiate("project", projectId);
OperationFuture<
com.google.spanner.admin.instance.v1.InstanceConfig, CreateInstanceConfigMetadata>
rawOperationFuture =
rpc.createInstanceConfig(
projectName,
instanceConfig.getId().getInstanceConfig(),
instanceConfig.toProto(),
createAdminApiOptions.validateOnly());

return new OperationFutureImpl<>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
snapshot ->
InstanceConfig.fromProto(
ProtoOperationTransformers.ResponseTransformer.create(
com.google.spanner.admin.instance.v1.InstanceConfig.class)
.apply(snapshot),
InstanceAdminClientImpl.this),
ProtoOperationTransformers.MetadataTransformer.create(CreateInstanceConfigMetadata.class),
e -> {
throw SpannerExceptionFactory.newSpannerException(e);
});
}

@Override
public OperationFuture<InstanceConfig, UpdateInstanceConfigMetadata> updateInstanceConfig(
InstanceConfigInfo instanceConfig,
Iterable<InstanceConfigInfo.InstanceConfigField> fieldsToUpdate,
UpdateAdminApiOption... options)
throws SpannerException {
final Options deleteAdminApiOptions = Options.fromAdminApiOptions(options);
FieldMask fieldMask = InstanceConfigInfo.InstanceConfigField.toFieldMask(fieldsToUpdate);

OperationFuture<
com.google.spanner.admin.instance.v1.InstanceConfig, UpdateInstanceConfigMetadata>
rawOperationFuture =
rpc.updateInstanceConfig(
instanceConfig.toProto(), deleteAdminApiOptions.validateOnly(), fieldMask);
return new OperationFutureImpl<>(
rawOperationFuture.getPollingFuture(),
rawOperationFuture.getInitialFuture(),
snapshot ->
InstanceConfig.fromProto(
ProtoOperationTransformers.ResponseTransformer.create(
com.google.spanner.admin.instance.v1.InstanceConfig.class)
.apply(snapshot),
InstanceAdminClientImpl.this),
ProtoOperationTransformers.MetadataTransformer.create(UpdateInstanceConfigMetadata.class),
e -> {
throw SpannerExceptionFactory.newSpannerException(e);
});
}

@Override
public InstanceConfig getInstanceConfig(String configId) throws SpannerException {
String instanceConfigName = new InstanceConfigId(projectId, configId).getName();
return InstanceConfig.fromProto(
rpc.getInstanceConfig(instanceConfigName), InstanceAdminClientImpl.this);
}

@Override
public void deleteInstanceConfig(final String instanceConfigId, DeleteAdminApiOption... options)
throws SpannerException {
final Options deleteAdminApiOptions = Options.fromAdminApiOptions(options);
rpc.deleteInstanceConfig(
new InstanceConfigId(projectId, instanceConfigId).getName(),
deleteAdminApiOptions.etag(),
deleteAdminApiOptions.validateOnly());
}

@Override
public Page<InstanceConfig> listInstanceConfigs(ListOption... options) {
final Options listOptions = Options.fromListOptions(options);
Expand All @@ -93,6 +166,30 @@ public InstanceConfig fromProto(
return pageFetcher.getNextPage();
}

@Override
public final Page<Operation> listInstanceConfigOperations(ListOption... options) {
final Options listOptions = Options.fromListOptions(options);
final int pageSize = listOptions.hasPageSize() ? listOptions.pageSize() : 0;
final String filter = listOptions.hasFilter() ? listOptions.filter() : null;

PageFetcher<Operation, Operation> pageFetcher =
new PageFetcher<Operation, Operation>() {
@Override
public Paginated<Operation> getNextPage(String nextPageToken) {
return rpc.listInstanceConfigOperations(pageSize, filter, nextPageToken);
}

@Override
public Operation fromProto(Operation proto) {
return proto;
}
};
if (listOptions.hasPageToken()) {
pageFetcher.setNextPageToken(listOptions.pageToken());
}
return pageFetcher.getNextPage();
}

@Override
public OperationFuture<Instance, CreateInstanceMetadata> createInstance(InstanceInfo instance)
throws SpannerException {
Expand Down
Loading

0 comments on commit c1c805c

Please sign in to comment.