Skip to content

Commit c0802c3

Browse files
authored
feat: Create subscriptions at a seek target (#1243)
Support creating subscriptions at a nominated target location within the message backlog. A seek is performed for publish and event timestamps. Export subscriptions (pre-release feature) are also supported.
1 parent 43ad527 commit c0802c3

File tree

6 files changed

+412
-2
lines changed

6 files changed

+412
-2
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ If you are using Maven, add this to your pom.xml file:
3232
If you are using Gradle without BOM, add this to your dependencies:
3333

3434
```Groovy
35-
implementation 'com.google.cloud:google-cloud-pubsublite:1.7.0'
35+
implementation 'com.google.cloud:google-cloud-pubsublite:1.7.1'
3636
```
3737

3838
If you are using SBT, add this to your dependencies:
3939

4040
```Scala
41-
libraryDependencies += "com.google.cloud" % "google-cloud-pubsublite" % "1.7.0"
41+
libraryDependencies += "com.google.cloud" % "google-cloud-pubsublite" % "1.7.1"
4242
```
4343

4444
## Authentication

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<!-- see http://www.mojohaus.org/clirr-maven-plugin/examples/ignored-differences.html -->
33
<differences>
4+
<!-- Added method to AdminClient interface (Always okay) -->
5+
<difference>
6+
<differenceType>7012</differenceType>
7+
<className>com/google/cloud/pubsublite/AdminClient</className>
8+
<method>*</method>
9+
</difference>
410
<difference>
511
<differenceType>7004</differenceType>
612
<className>com/google/cloud/pubsublite/internal/**</className>

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/AdminClient.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ default ApiFuture<Subscription> createSubscription(Subscription subscription) {
128128
ApiFuture<Subscription> createSubscription(
129129
Subscription subscription, BacklogLocation startingOffset);
130130

131+
/**
132+
* Create the provided subscription at the given target location within the message backlog, if it
133+
* does not yet exist.
134+
*
135+
* <p>A seek is initiated if the target location is a publish or event time. If the seek fails,
136+
* the created subscription is not deleted.
137+
*
138+
* @param subscription The subscription to create.
139+
* @param target The target location that the subscription should be initialized to.
140+
* @return A future that will have either an error {@link com.google.api.gax.rpc.ApiException} or
141+
* the subscription on success.
142+
*/
143+
ApiFuture<Subscription> createSubscription(Subscription subscription, SeekTarget target);
144+
131145
/**
132146
* Get the subscription with id {@code id} if it exists.
133147
*

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/internal/AdminClientImpl.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import com.google.cloud.pubsublite.proto.DeleteReservationRequest;
3535
import com.google.cloud.pubsublite.proto.DeleteSubscriptionRequest;
3636
import com.google.cloud.pubsublite.proto.DeleteTopicRequest;
37+
import com.google.cloud.pubsublite.proto.ExportConfig;
3738
import com.google.cloud.pubsublite.proto.GetReservationRequest;
3839
import com.google.cloud.pubsublite.proto.GetSubscriptionRequest;
3940
import com.google.cloud.pubsublite.proto.GetTopicPartitionsRequest;
@@ -166,6 +167,54 @@ public ApiFuture<Subscription> createSubscription(
166167
.build());
167168
}
168169

170+
@Override
171+
public ApiFuture<Subscription> createSubscription(Subscription subscription, SeekTarget target) {
172+
switch (target.getKind()) {
173+
case BACKLOG_LOCATION:
174+
return createSubscription(subscription, target.backlogLocation());
175+
case PUBLISH_TIME:
176+
case EVENT_TIME:
177+
break;
178+
}
179+
180+
// Export subscriptions must be paused while seeking. The state is later updated to active.
181+
final boolean requiresUpdate =
182+
subscription.hasExportConfig()
183+
&& subscription.getExportConfig().getDesiredState() == ExportConfig.State.ACTIVE;
184+
if (requiresUpdate) {
185+
Subscription.Builder builder = subscription.toBuilder();
186+
builder.getExportConfigBuilder().setDesiredState(ExportConfig.State.PAUSED);
187+
subscription = builder.build();
188+
}
189+
190+
// Request 1: create the subscription.
191+
return ApiFutures.transformAsync(
192+
createSubscription(subscription, BacklogLocation.BEGINNING),
193+
newSubscription -> {
194+
// Request 2: seek the subscription.
195+
SubscriptionPath path = SubscriptionPath.parse(newSubscription.getName());
196+
return ApiFutures.transformAsync(
197+
seekSubscription(path, target).getInitialFuture(),
198+
operation -> {
199+
if (!requiresUpdate) {
200+
return ApiFutures.immediateFuture(newSubscription);
201+
}
202+
// Request 3 (optional): make the export subscription active.
203+
Subscription updatedSubscription =
204+
Subscription.newBuilder()
205+
.setName(newSubscription.getName())
206+
.setExportConfig(
207+
ExportConfig.newBuilder().setDesiredState(ExportConfig.State.ACTIVE))
208+
.build();
209+
FieldMask fieldMask =
210+
FieldMask.newBuilder().addPaths("export_config.desired_state").build();
211+
return updateSubscription(updatedSubscription, fieldMask);
212+
},
213+
SystemExecutors.getFuturesExecutor());
214+
},
215+
SystemExecutors.getFuturesExecutor());
216+
}
217+
169218
@Override
170219
public ApiFuture<Subscription> getSubscription(SubscriptionPath path) {
171220
return serviceClient

google-cloud-pubsublite/src/main/java/com/google/cloud/pubsublite/internal/testing/UnitTestExamples.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@
2828
import com.google.cloud.pubsublite.SubscriptionPath;
2929
import com.google.cloud.pubsublite.TopicName;
3030
import com.google.cloud.pubsublite.TopicPath;
31+
import com.google.cloud.pubsublite.proto.ExportConfig;
32+
import com.google.cloud.pubsublite.proto.ExportConfig.PubSubConfig;
33+
import com.google.cloud.pubsublite.proto.ExportConfig.State;
3134
import com.google.cloud.pubsublite.proto.Reservation;
3235
import com.google.cloud.pubsublite.proto.Subscription;
3336
import com.google.cloud.pubsublite.proto.Subscription.DeliveryConfig;
@@ -58,6 +61,7 @@ private UnitTestExamples() {}
5861
.put(Reservation.class, exampleReservation())
5962
.put(LocationPath.class, exampleLocationPath())
6063
.put(Offset.class, exampleOffset())
64+
.put(ExportConfig.class, exampleExportConfig())
6165
.build();
6266

6367
public static <T> T example(Class<T> klass) {
@@ -124,6 +128,13 @@ public static Subscription exampleSubscription() {
124128
.build();
125129
}
126130

131+
public static ExportConfig exampleExportConfig() {
132+
return ExportConfig.newBuilder()
133+
.setDesiredState(State.ACTIVE)
134+
.setPubsubConfig(PubSubConfig.newBuilder().setTopic("pubsub_topic"))
135+
.build();
136+
}
137+
127138
public static ReservationName exampleReservationName() {
128139
return ReservationName.of("example-reservation");
129140
}

0 commit comments

Comments
 (0)