Skip to content

Commit 393045d

Browse files
authored
Test server support for bidi links (#2258)
* Test server support for bidi links * typo * license * feedback * link validation * describe fields * link validation
1 parent d1dc2e1 commit 393045d

File tree

5 files changed

+573
-15
lines changed

5 files changed

+573
-15
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
/*
2+
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
3+
*
4+
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
5+
*
6+
* Modifications copyright (C) 2017 Uber Technologies, Inc.
7+
*
8+
* Licensed under the Apache License, Version 2.0 (the "License");
9+
* you may not use this material except in compliance with the License.
10+
* You may obtain a copy of the License at
11+
*
12+
* http://www.apache.org/licenses/LICENSE-2.0
13+
*
14+
* Unless required by applicable law or agreed to in writing, software
15+
* distributed under the License is distributed on an "AS IS" BASIS,
16+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17+
* See the License for the specific language governing permissions and
18+
* limitations under the License.
19+
*/
20+
21+
package io.temporal.internal.testservice;
22+
23+
import io.temporal.api.common.v1.Link;
24+
import io.temporal.api.enums.v1.EventType;
25+
import java.net.URI;
26+
import java.net.URLDecoder;
27+
import java.net.URLEncoder;
28+
import java.nio.charset.StandardCharsets;
29+
import java.util.StringTokenizer;
30+
import org.slf4j.Logger;
31+
import org.slf4j.LoggerFactory;
32+
33+
public class LinkConverter {
34+
35+
private static final Logger log = LoggerFactory.getLogger(StateMachines.class);
36+
37+
private static final String linkPathFormat = "temporal:///namespaces/%s/workflows/%s/%s/history";
38+
39+
public static io.temporal.api.nexus.v1.Link workflowEventToNexusLink(Link.WorkflowEvent we) {
40+
try {
41+
String url =
42+
String.format(
43+
linkPathFormat,
44+
URLEncoder.encode(we.getNamespace(), StandardCharsets.UTF_8.toString()),
45+
URLEncoder.encode(we.getWorkflowId(), StandardCharsets.UTF_8.toString()),
46+
URLEncoder.encode(we.getRunId(), StandardCharsets.UTF_8.toString()));
47+
48+
if (we.hasEventRef()) {
49+
url += "?";
50+
if (we.getEventRef().getEventId() > 0) {
51+
url += "eventID=" + we.getEventRef().getEventId() + "&";
52+
}
53+
url +=
54+
"eventType="
55+
+ URLEncoder.encode(
56+
we.getEventRef().getEventType().name(), StandardCharsets.UTF_8.toString())
57+
+ "&";
58+
url += "referenceType=EventReference";
59+
}
60+
61+
return io.temporal.api.nexus.v1.Link.newBuilder()
62+
.setUrl(url)
63+
.setType(we.getDescriptorForType().getFullName())
64+
.build();
65+
} catch (Exception e) {
66+
log.error("Failed to encode Nexus link URL", e);
67+
}
68+
return null;
69+
}
70+
71+
public static Link nexusLinkToWorkflowEvent(io.temporal.api.nexus.v1.Link nexusLink) {
72+
Link.Builder link = Link.newBuilder();
73+
try {
74+
URI uri = new URI(nexusLink.getUrl());
75+
76+
if (!uri.getScheme().equals("temporal")) {
77+
log.error("Failed to parse Nexus link URL: invalid scheme: {}", uri.getScheme());
78+
return null;
79+
}
80+
81+
StringTokenizer st = new StringTokenizer(uri.getRawPath(), "/");
82+
if (!st.nextToken().equals("namespaces")) {
83+
log.error("Failed to parse Nexus link URL: invalid path: {}", uri.getRawPath());
84+
return null;
85+
}
86+
String namespace = URLDecoder.decode(st.nextToken(), StandardCharsets.UTF_8.toString());
87+
if (!st.nextToken().equals("workflows")) {
88+
log.error("Failed to parse Nexus link URL: invalid path: {}", uri.getRawPath());
89+
return null;
90+
}
91+
String workflowID = URLDecoder.decode(st.nextToken(), StandardCharsets.UTF_8.toString());
92+
String runID = URLDecoder.decode(st.nextToken(), StandardCharsets.UTF_8.toString());
93+
if (!st.hasMoreTokens() || !st.nextToken().equals("history")) {
94+
log.error("Failed to parse Nexus link URL: invalid path: {}", uri.getRawPath());
95+
return null;
96+
}
97+
98+
Link.WorkflowEvent.Builder we =
99+
Link.WorkflowEvent.newBuilder()
100+
.setNamespace(namespace)
101+
.setWorkflowId(workflowID)
102+
.setRunId(runID);
103+
104+
if (uri.getQuery() != null) {
105+
Link.WorkflowEvent.EventReference.Builder eventRef =
106+
Link.WorkflowEvent.EventReference.newBuilder();
107+
String query = URLDecoder.decode(uri.getQuery(), StandardCharsets.UTF_8.toString());
108+
st = new StringTokenizer(query, "&");
109+
while (st.hasMoreTokens()) {
110+
String[] param = st.nextToken().split("=");
111+
switch (param[0]) {
112+
case "eventID":
113+
eventRef.setEventId(Long.parseLong(param[1]));
114+
continue;
115+
case "eventType":
116+
eventRef.setEventType(EventType.valueOf(param[1]));
117+
}
118+
}
119+
we.setEventRef(eventRef);
120+
link.setWorkflowEvent(we);
121+
}
122+
} catch (Exception e) {
123+
// Swallow un-parsable links since they are not critical to processing
124+
log.error("Failed to parse Nexus link URL", e);
125+
return null;
126+
}
127+
return link.build();
128+
}
129+
}

temporal-test-server/src/main/java/io/temporal/internal/testservice/StateMachines.java

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
package io.temporal.internal.testservice;
2222

23+
import static io.temporal.internal.testservice.LinkConverter.*;
2324
import static io.temporal.internal.testservice.StateMachines.Action.CANCEL;
2425
import static io.temporal.internal.testservice.StateMachines.Action.COMPLETE;
2526
import static io.temporal.internal.testservice.StateMachines.Action.CONTINUE_AS_NEW;
@@ -60,6 +61,7 @@
6061
import io.temporal.api.failure.v1.TimeoutFailureInfo;
6162
import io.temporal.api.history.v1.*;
6263
import io.temporal.api.nexus.v1.*;
64+
import io.temporal.api.nexus.v1.Link;
6365
import io.temporal.api.protocol.v1.Message;
6466
import io.temporal.api.query.v1.WorkflowQueryResult;
6567
import io.temporal.api.taskqueue.v1.StickyExecutionAttributes;
@@ -347,11 +349,13 @@ static final class NexusOperationData {
347349
RetryPolicy retryPolicy = defaultNexusRetryPolicy();
348350

349351
long scheduledEventId = NO_EVENT_ID;
352+
Timestamp cancelRequestedTime;
350353

351354
TestServiceRetryState retryState;
352-
long lastAttemptCompleteTime;
355+
boolean isBackingOff = false;
353356
Duration nextBackoffInterval;
354-
long nextAttemptScheduleTime;
357+
Timestamp lastAttemptCompleteTime;
358+
Timestamp nextAttemptScheduleTime;
355359
String identity;
356360

357361
public NexusOperationData(Endpoint endpoint) {
@@ -685,6 +689,18 @@ private static void scheduleNexusOperation(
685689
NexusOperationRef ref = new NexusOperationRef(ctx.getExecutionId(), scheduledEventId);
686690
NexusTaskToken taskToken = new NexusTaskToken(ref, data.getAttempt(), false);
687691

692+
Link link =
693+
workflowEventToNexusLink(
694+
io.temporal.api.common.v1.Link.WorkflowEvent.newBuilder()
695+
.setNamespace(ctx.getNamespace())
696+
.setWorkflowId(ctx.getExecution().getWorkflowId())
697+
.setRunId(ctx.getExecution().getRunId())
698+
.setEventRef(
699+
io.temporal.api.common.v1.Link.WorkflowEvent.EventReference.newBuilder()
700+
.setEventId(scheduledEventId)
701+
.setEventType(EventType.EVENT_TYPE_NEXUS_OPERATION_SCHEDULED))
702+
.build());
703+
688704
PollNexusTaskQueueResponse.Builder pollResponse =
689705
PollNexusTaskQueueResponse.newBuilder()
690706
.setTaskToken(taskToken.toBytes())
@@ -697,6 +713,7 @@ private static void scheduleNexusOperation(
697713
.setService(attr.getService())
698714
.setOperation(attr.getOperation())
699715
.setPayload(attr.getInput())
716+
.addLinks(link)
700717
.setCallback("http://test-env/operations")
701718
// The test server uses this to lookup the operation
702719
.putCallbackHeader(
@@ -725,15 +742,24 @@ private static void startNexusOperation(
725742
NexusOperationData data,
726743
StartOperationResponse.Async resp,
727744
long notUsed) {
728-
ctx.addEvent(
745+
HistoryEvent.Builder event =
729746
HistoryEvent.newBuilder()
730747
.setEventType(EventType.EVENT_TYPE_NEXUS_OPERATION_STARTED)
731748
.setNexusOperationStartedEventAttributes(
732749
NexusOperationStartedEventAttributes.newBuilder()
733750
.setOperationId(resp.getOperationId())
734751
.setScheduledEventId(data.scheduledEventId)
735-
.setRequestId(data.scheduledEvent.getRequestId()))
736-
.build());
752+
.setRequestId(data.scheduledEvent.getRequestId()));
753+
754+
for (Link l : resp.getLinksList()) {
755+
if (!l.getType()
756+
.equals(io.temporal.api.common.v1.Link.WorkflowEvent.getDescriptor().getFullName())) {
757+
continue;
758+
}
759+
event.addLinks(nexusLinkToWorkflowEvent(l));
760+
}
761+
762+
ctx.addEvent(event.build());
737763
ctx.onCommit(historySize -> data.operationId = resp.getOperationId());
738764
}
739765

@@ -846,7 +872,10 @@ private static RetryState attemptNexusOperationRetry(
846872
ctx.onCommit(
847873
(historySize) -> {
848874
data.retryState = nextAttempt;
849-
data.nextAttemptScheduleTime = ctx.currentTime().getSeconds();
875+
data.isBackingOff = true;
876+
data.lastAttemptCompleteTime = ctx.currentTime();
877+
data.nextAttemptScheduleTime =
878+
Timestamps.add(ProtobufTimeUtils.getCurrentProtoTime(), data.nextBackoffInterval);
850879
task.setTaskToken(
851880
new NexusTaskToken(
852881
ctx.getExecutionId(),
@@ -899,7 +928,12 @@ private static void requestCancelNexusOperation(
899928
// Test server only supports worker targets, so just push directly to Nexus task queue without
900929
// invoking Nexus client.
901930
ctx.addNexusTask(cancelTask);
902-
ctx.onCommit(historySize -> data.nexusTask = cancelTask);
931+
ctx.onCommit(
932+
historySize -> {
933+
data.nexusTask = cancelTask;
934+
data.cancelRequestedTime = ctx.currentTime();
935+
data.isBackingOff = false;
936+
});
903937
}
904938

905939
private static void reportNexusOperationCancellation(
@@ -1238,12 +1272,14 @@ private static void startWorkflow(
12381272
a.setParentWorkflowNamespace(parentExecutionId.getNamespace());
12391273
a.setParentWorkflowExecution(parentExecutionId.getExecution());
12401274
}
1241-
HistoryEvent event =
1275+
HistoryEvent.Builder event =
12421276
HistoryEvent.newBuilder()
12431277
.setEventType(EventType.EVENT_TYPE_WORKFLOW_EXECUTION_STARTED)
1244-
.setWorkflowExecutionStartedEventAttributes(a)
1245-
.build();
1246-
ctx.addEvent(event);
1278+
.setWorkflowExecutionStartedEventAttributes(a);
1279+
if (request.getLinksCount() > 0) {
1280+
event.addAllLinks(request.getLinksList());
1281+
}
1282+
ctx.addEvent(event.build());
12471283
}
12481284

12491285
private static void completeWorkflow(

0 commit comments

Comments
 (0)