Skip to content

Commit c1c805c

Browse files
rajatbhattaolavloitegcf-owl-bot[bot]
authored
feat: support customer managed instance configurations (#1742)
* 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>
1 parent 98c2303 commit c1c805c

File tree

10 files changed

+1280
-50
lines changed

10 files changed

+1280
-50
lines changed

google-cloud-spanner/clirr-ignored-differences.xml

+40
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,46 @@
3535
<className>com/google/cloud/spanner/connection/ConnectionOptions</className>
3636
<method>com.google.cloud.spanner.Dialect getDialect()</method>
3737
</difference>
38+
<difference>
39+
<differenceType>7012</differenceType>
40+
<className>com/google/cloud/spanner/InstanceAdminClient</className>
41+
<method>com.google.api.gax.longrunning.OperationFuture createInstanceConfig(com.google.cloud.spanner.InstanceConfigInfo, com.google.cloud.spanner.Options$CreateAdminApiOption[])</method>
42+
</difference>
43+
<difference>
44+
<differenceType>7012</differenceType>
45+
<className>com/google/cloud/spanner/InstanceAdminClient</className>
46+
<method>com.google.api.gax.longrunning.OperationFuture updateInstanceConfig(com.google.cloud.spanner.InstanceConfigInfo, java.lang.Iterable, com.google.cloud.spanner.Options$UpdateAdminApiOption[])</method>
47+
</difference>
48+
<difference>
49+
<differenceType>7012</differenceType>
50+
<className>com/google/cloud/spanner/InstanceAdminClient</className>
51+
<method>void deleteInstanceConfig(java.lang.String, com.google.cloud.spanner.Options$DeleteAdminApiOption[])</method>
52+
</difference>
53+
<difference>
54+
<differenceType>7012</differenceType>
55+
<className>com/google/cloud/spanner/InstanceAdminClient</className>
56+
<method>com.google.api.gax.paging.Page listInstanceConfigOperations(com.google.cloud.spanner.Options$ListOption[])</method>
57+
</difference>
58+
<difference>
59+
<differenceType>7012</differenceType>
60+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
61+
<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>
62+
</difference>
63+
<difference>
64+
<differenceType>7012</differenceType>
65+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
66+
<method>com.google.api.gax.longrunning.OperationFuture updateInstanceConfig(com.google.spanner.admin.instance.v1.InstanceConfig, java.lang.Boolean, com.google.protobuf.FieldMask)</method>
67+
</difference>
68+
<difference>
69+
<differenceType>7012</differenceType>
70+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
71+
<method>void deleteInstanceConfig(java.lang.String, java.lang.String, java.lang.Boolean)</method>
72+
</difference>
73+
<difference>
74+
<differenceType>7012</differenceType>
75+
<className>com/google/cloud/spanner/spi/v1/SpannerRpc</className>
76+
<method>com.google.cloud.spanner.spi.v1.SpannerRpc$Paginated listInstanceConfigOperations(int, java.lang.String, java.lang.String)</method>
77+
</difference>
3878
<difference>
3979
<differenceType>7013</differenceType>
4080
<className>com/google/cloud/spanner/BackupInfo$Builder</className>

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

+154
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,141 @@
1919
import com.google.api.gax.longrunning.OperationFuture;
2020
import com.google.api.gax.paging.Page;
2121
import com.google.cloud.Policy;
22+
import com.google.cloud.spanner.Options.CreateAdminApiOption;
23+
import com.google.cloud.spanner.Options.DeleteAdminApiOption;
2224
import com.google.cloud.spanner.Options.ListOption;
25+
import com.google.cloud.spanner.Options.UpdateAdminApiOption;
2326
import com.google.longrunning.Operation;
27+
import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata;
2428
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
29+
import com.google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata;
2530
import com.google.spanner.admin.instance.v1.UpdateInstanceMetadata;
2631

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

35+
/**
36+
* Creates an instance config and begins preparing it to be used. The returned {@code Operation}
37+
* can be used to track the progress of preparing the new instance config. The instance config
38+
* name is assigned by the caller and must start with the string 'custom'. If the named instance
39+
* config already exists, a SpannerException is thrown.
40+
*
41+
* <p>Immediately after the request returns:
42+
*
43+
* <ul>
44+
* <li>The instance config is readable via the API, with all requested attributes.
45+
* <li>The instance config's {@code reconciling} field is set to true. Its state is {@code
46+
* CREATING}.
47+
* </ul>
48+
*
49+
* While the operation is pending:
50+
*
51+
* <ul>
52+
* <li>Cancelling the operation renders the instance config immediately unreadable via the API.
53+
* <li>Except for deleting the creating resource, all other attempts to modify the instance
54+
* config are rejected.
55+
* </ul>
56+
*
57+
* Upon completion of the returned operation:
58+
*
59+
* <ul>
60+
* <li>Instances can be created using the instance configuration.
61+
* <li>The instance config's {@code reconciling} field becomes false.
62+
* <li>Its state becomes {@code READY}.
63+
* </ul>
64+
*
65+
* <!--SNIPPET instance_admin_client_create_instance_config-->
66+
*
67+
* <pre>{@code
68+
* String projectId = "my-project";
69+
* String baseInstanceConfig = "my-base-config";
70+
* String instanceConfigId = "custom-user-config";
71+
*
72+
* final InstanceConfig baseConfig = instanceAdminClient.getInstanceConfig(baseInstanceConfig);
73+
*
74+
* List<ReplicaInfo> readOnlyReplicas = ImmutableList.of(baseConfig.getOptionalReplicas().get(0));
75+
*
76+
* InstanceConfigInfo instanceConfigInfo =
77+
* InstanceConfigInfo.newBuilder(InstanceConfigId.of(projectId, instanceConfigId), baseConfig)
78+
* .setDisplayName(instanceConfigId)
79+
* .addReadOnlyReplicas(readOnlyReplicas)
80+
* .build();
81+
*
82+
* final OperationFuture<InstanceConfig, CreateInstanceConfigMetadata> operation =
83+
* instanceAdminClient.createInstanceConfig(instanceConfigInfo);
84+
*
85+
* InstanceConfig instanceConfig = op.get(5, TimeUnit.MINUTES)
86+
* }</pre>
87+
*
88+
* <!--SNIPPET instance_admin_client_create_instance_config-->
89+
*/
90+
default OperationFuture<InstanceConfig, CreateInstanceConfigMetadata> createInstanceConfig(
91+
InstanceConfigInfo instanceConfig, CreateAdminApiOption... options) throws SpannerException {
92+
throw new UnsupportedOperationException("Not implemented");
93+
}
94+
95+
/**
96+
* Updates a custom instance config. This can not be used to update a Google managed instance
97+
* config. The returned {@code Operation} can be used to track the progress of updating the
98+
* instance. If the named instance config does not exist, a SpannerException is thrown. The
99+
* request must include at least one field to update.
100+
*
101+
* <p>Only user managed configurations can be updated.
102+
*
103+
* <p>Immediately after the request returns:
104+
*
105+
* <ul>
106+
* <li>The instance config's {@code reconciling} field is set to true.
107+
* </ul>
108+
*
109+
* While the operation is pending:
110+
*
111+
* <ul>
112+
* <li>Cancelling the operation sets its metadata's cancel_time.
113+
* <li>The operation is guaranteed to succeed at undoing all changes, after which point it
114+
* terminates with a `CANCELLED` status.
115+
* <li>All other attempts to modify the instance config are rejected.
116+
* <li>Reading the instance config via the API continues to give the pre-request values.
117+
* </ul>
118+
*
119+
* Upon completion of the returned operation:
120+
*
121+
* <ul>
122+
* <li>Creating instances using the instance configuration uses the new values.
123+
* <li>The instance config's new values are readable via the API.
124+
* <li>The instance config's {@code reconciling} field becomes false.
125+
* </ul>
126+
*
127+
* <!--SNIPPET instance_admin_client_update_instance_config-->
128+
*
129+
* <pre>{@code
130+
* String projectId = "my-project";
131+
* String instanceConfigId = "custom-user-config";
132+
* String displayName = "my-display-name";
133+
*
134+
* InstanceConfigInfo instanceConfigInfo =
135+
* InstanceConfigInfo.newBuilder(InstanceConfigId.of(projectId, instanceConfigId))
136+
* .setDisplayName(displayName)
137+
* .build();
138+
*
139+
* // Only update display name.
140+
* final OperationFuture<InstanceConfig, UpdateInstanceConfigMetadata> operation =
141+
* instanceAdminClient.updateInstanceConfig(
142+
* instanceConfigInfo, ImmutableList.of(InstanceConfigField.DISPLAY_NAME));
143+
*
144+
* InstanceConfig instanceConfig = operation.get(5, TimeUnit.MINUTES);
145+
* }</pre>
146+
*
147+
* <!--SNIPPET instance_admin_client_update_instance_config-->
148+
*/
149+
default OperationFuture<InstanceConfig, UpdateInstanceConfigMetadata> updateInstanceConfig(
150+
InstanceConfigInfo instanceConfig,
151+
Iterable<InstanceConfigInfo.InstanceConfigField> fieldsToUpdate,
152+
UpdateAdminApiOption... options)
153+
throws SpannerException {
154+
throw new UnsupportedOperationException("Not implemented");
155+
}
156+
30157
/** Gets an instance config. */
31158
/* <!--SNIPPET instance_admin_client_get_instance_config-->
32159
* <pre>{@code
@@ -37,6 +164,28 @@ public interface InstanceAdminClient {
37164
*/
38165
InstanceConfig getInstanceConfig(String configId) throws SpannerException;
39166

167+
/**
168+
* Deletes a custom instance config. Deletion is only allowed for custom instance configs and when
169+
* no instances are using the configuration. If any instances are using the config, a
170+
* SpannerException is thrown.
171+
*
172+
* <p>Only user managed configurations can be deleted.
173+
* <!--SNIPPET instance_admin_client_delete_instance_config-->
174+
*
175+
* <pre>{@code
176+
* String projectId = "my-project";
177+
* String instanceConfigId = "custom-user-config";
178+
*
179+
* instanceAdminClient.deleteInstanceConfig(instanceConfigId);
180+
* }</pre>
181+
*
182+
* <!--SNIPPET instance_admin_client_delete_instance_config-->
183+
*/
184+
default void deleteInstanceConfig(String instanceConfigId, DeleteAdminApiOption... options)
185+
throws SpannerException {
186+
throw new UnsupportedOperationException("Not implemented");
187+
}
188+
40189
/** Lists the supported instance configs for current project. */
41190
/* <!--SNIPPET instance_admin_client_list_configs-->
42191
* <pre>{@code
@@ -47,6 +196,11 @@ public interface InstanceAdminClient {
47196
*/
48197
Page<InstanceConfig> listInstanceConfigs(ListOption... options) throws SpannerException;
49198

199+
/** Lists long-running instance config operations. */
200+
default Page<Operation> listInstanceConfigOperations(ListOption... options) {
201+
throw new UnsupportedOperationException("Not implemented");
202+
}
203+
50204
/**
51205
* Creates an instance and begins preparing it to begin serving. The returned {@code Operation}
52206
* can be used to track the progress of preparing the new instance. The instance name is assigned

google-cloud-spanner/src/main/java/com/google/cloud/spanner/InstanceAdminClientImpl.java

+97
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,19 @@
2323
import com.google.api.pathtemplate.PathTemplate;
2424
import com.google.cloud.Policy;
2525
import com.google.cloud.Policy.DefaultMarshaller;
26+
import com.google.cloud.spanner.Options.CreateAdminApiOption;
27+
import com.google.cloud.spanner.Options.DeleteAdminApiOption;
2628
import com.google.cloud.spanner.Options.ListOption;
29+
import com.google.cloud.spanner.Options.UpdateAdminApiOption;
2730
import com.google.cloud.spanner.SpannerImpl.PageFetcher;
2831
import com.google.cloud.spanner.spi.v1.SpannerRpc;
2932
import com.google.cloud.spanner.spi.v1.SpannerRpc.Paginated;
3033
import com.google.common.base.Preconditions;
3134
import com.google.longrunning.Operation;
3235
import com.google.protobuf.FieldMask;
36+
import com.google.spanner.admin.instance.v1.CreateInstanceConfigMetadata;
3337
import com.google.spanner.admin.instance.v1.CreateInstanceMetadata;
38+
import com.google.spanner.admin.instance.v1.UpdateInstanceConfigMetadata;
3439
import com.google.spanner.admin.instance.v1.UpdateInstanceMetadata;
3540

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

68+
@Override
69+
public OperationFuture<InstanceConfig, CreateInstanceConfigMetadata> createInstanceConfig(
70+
InstanceConfigInfo instanceConfig, CreateAdminApiOption... options) throws SpannerException {
71+
final Options createAdminApiOptions = Options.fromAdminApiOptions(options);
72+
String projectName = PROJECT_NAME_TEMPLATE.instantiate("project", projectId);
73+
OperationFuture<
74+
com.google.spanner.admin.instance.v1.InstanceConfig, CreateInstanceConfigMetadata>
75+
rawOperationFuture =
76+
rpc.createInstanceConfig(
77+
projectName,
78+
instanceConfig.getId().getInstanceConfig(),
79+
instanceConfig.toProto(),
80+
createAdminApiOptions.validateOnly());
81+
82+
return new OperationFutureImpl<>(
83+
rawOperationFuture.getPollingFuture(),
84+
rawOperationFuture.getInitialFuture(),
85+
snapshot ->
86+
InstanceConfig.fromProto(
87+
ProtoOperationTransformers.ResponseTransformer.create(
88+
com.google.spanner.admin.instance.v1.InstanceConfig.class)
89+
.apply(snapshot),
90+
InstanceAdminClientImpl.this),
91+
ProtoOperationTransformers.MetadataTransformer.create(CreateInstanceConfigMetadata.class),
92+
e -> {
93+
throw SpannerExceptionFactory.newSpannerException(e);
94+
});
95+
}
96+
97+
@Override
98+
public OperationFuture<InstanceConfig, UpdateInstanceConfigMetadata> updateInstanceConfig(
99+
InstanceConfigInfo instanceConfig,
100+
Iterable<InstanceConfigInfo.InstanceConfigField> fieldsToUpdate,
101+
UpdateAdminApiOption... options)
102+
throws SpannerException {
103+
final Options deleteAdminApiOptions = Options.fromAdminApiOptions(options);
104+
FieldMask fieldMask = InstanceConfigInfo.InstanceConfigField.toFieldMask(fieldsToUpdate);
105+
106+
OperationFuture<
107+
com.google.spanner.admin.instance.v1.InstanceConfig, UpdateInstanceConfigMetadata>
108+
rawOperationFuture =
109+
rpc.updateInstanceConfig(
110+
instanceConfig.toProto(), deleteAdminApiOptions.validateOnly(), fieldMask);
111+
return new OperationFutureImpl<>(
112+
rawOperationFuture.getPollingFuture(),
113+
rawOperationFuture.getInitialFuture(),
114+
snapshot ->
115+
InstanceConfig.fromProto(
116+
ProtoOperationTransformers.ResponseTransformer.create(
117+
com.google.spanner.admin.instance.v1.InstanceConfig.class)
118+
.apply(snapshot),
119+
InstanceAdminClientImpl.this),
120+
ProtoOperationTransformers.MetadataTransformer.create(UpdateInstanceConfigMetadata.class),
121+
e -> {
122+
throw SpannerExceptionFactory.newSpannerException(e);
123+
});
124+
}
125+
63126
@Override
64127
public InstanceConfig getInstanceConfig(String configId) throws SpannerException {
65128
String instanceConfigName = new InstanceConfigId(projectId, configId).getName();
66129
return InstanceConfig.fromProto(
67130
rpc.getInstanceConfig(instanceConfigName), InstanceAdminClientImpl.this);
68131
}
69132

133+
@Override
134+
public void deleteInstanceConfig(final String instanceConfigId, DeleteAdminApiOption... options)
135+
throws SpannerException {
136+
final Options deleteAdminApiOptions = Options.fromAdminApiOptions(options);
137+
rpc.deleteInstanceConfig(
138+
new InstanceConfigId(projectId, instanceConfigId).getName(),
139+
deleteAdminApiOptions.etag(),
140+
deleteAdminApiOptions.validateOnly());
141+
}
142+
70143
@Override
71144
public Page<InstanceConfig> listInstanceConfigs(ListOption... options) {
72145
final Options listOptions = Options.fromListOptions(options);
@@ -93,6 +166,30 @@ public InstanceConfig fromProto(
93166
return pageFetcher.getNextPage();
94167
}
95168

169+
@Override
170+
public final Page<Operation> listInstanceConfigOperations(ListOption... options) {
171+
final Options listOptions = Options.fromListOptions(options);
172+
final int pageSize = listOptions.hasPageSize() ? listOptions.pageSize() : 0;
173+
final String filter = listOptions.hasFilter() ? listOptions.filter() : null;
174+
175+
PageFetcher<Operation, Operation> pageFetcher =
176+
new PageFetcher<Operation, Operation>() {
177+
@Override
178+
public Paginated<Operation> getNextPage(String nextPageToken) {
179+
return rpc.listInstanceConfigOperations(pageSize, filter, nextPageToken);
180+
}
181+
182+
@Override
183+
public Operation fromProto(Operation proto) {
184+
return proto;
185+
}
186+
};
187+
if (listOptions.hasPageToken()) {
188+
pageFetcher.setNextPageToken(listOptions.pageToken());
189+
}
190+
return pageFetcher.getNextPage();
191+
}
192+
96193
@Override
97194
public OperationFuture<Instance, CreateInstanceMetadata> createInstance(InstanceInfo instance)
98195
throws SpannerException {

0 commit comments

Comments
 (0)