Skip to content

Commit 0ba6188

Browse files
authored
Experimental cloud operations client (#2146)
Fixes #2059
1 parent eb7d9ee commit 0ba6188

File tree

11 files changed

+520
-0
lines changed

11 files changed

+520
-0
lines changed

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,45 @@ jobs:
7979
with:
8080
report_paths: '**/build/test-results/test/TEST-*.xml'
8181

82+
unit_test_cloud:
83+
name: Unit test with cloud
84+
runs-on: ubuntu-latest
85+
timeout-minutes: 30
86+
steps:
87+
- name: Checkout repo
88+
uses: actions/checkout@v4
89+
with:
90+
fetch-depth: 0
91+
submodules: recursive
92+
ref: ${{ github.event.pull_request.head.sha }}
93+
94+
- name: Set up Java
95+
uses: actions/setup-java@v4
96+
with:
97+
java-version: "11"
98+
distribution: "temurin"
99+
100+
- name: Set up Gradle
101+
uses: gradle/actions/setup-gradle@v3
102+
103+
- name: Run cloud test
104+
# Only supported in non-fork runs, since secrets are not available in forks. We intentionally
105+
# are only doing this check on the step instead of the job so we require job passing in CI
106+
# even for those that can't run this step.
107+
if: ${{ github.event.pull_request.head.repo.full_name == '' || github.event.pull_request.head.repo.full_name == 'temporalio/sdk-java' }}
108+
env:
109+
USER: unittest
110+
TEMPORAL_CLIENT_CLOUD_NAMESPACE: sdk-ci.a2dd6
111+
TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }}
112+
TEMPORAL_CLIENT_CLOUD_API_VERSION: 2024-05-13-00
113+
run: ./gradlew --no-daemon :temporal-sdk:test --tests '*CloudOperationsClientTest'
114+
115+
- name: Publish Test Report
116+
uses: mikepenz/action-junit-report@v4
117+
if: success() || failure() # always run even if the previous step fails
118+
with:
119+
report_paths: '**/build/test-results/test/TEST-*.xml'
120+
82121
copyright:
83122
name: Copyright and code format
84123
runs-on: ubuntu-latest

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
[submodule "temporal-serviceclient/src/main/proto"]
22
path = temporal-serviceclient/src/main/proto
33
url = https://github.com/temporalio/api.git
4+
[submodule "temporal-serviceclient/src/main/protocloud"]
5+
path = temporal-serviceclient/src/main/protocloud
6+
url = https://github.com/temporalio/api-cloud.git
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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.client;
22+
23+
import io.temporal.common.Experimental;
24+
import io.temporal.serviceclient.CloudServiceStubs;
25+
26+
/** Client to the Temporal Cloud operations service for performing cloud operations. */
27+
@Experimental
28+
public interface CloudOperationsClient {
29+
@Experimental
30+
static CloudOperationsClient newInstance(CloudServiceStubs service) {
31+
return new CloudOperationsClientImpl(service);
32+
}
33+
34+
/** Get the raw cloud service stubs. */
35+
@Experimental
36+
CloudServiceStubs getCloudServiceStubs();
37+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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.client;
22+
23+
import io.temporal.serviceclient.CloudServiceStubs;
24+
25+
class CloudOperationsClientImpl implements CloudOperationsClient {
26+
private final CloudServiceStubs cloudServiceStubs;
27+
28+
CloudOperationsClientImpl(CloudServiceStubs cloudServiceStubs) {
29+
this.cloudServiceStubs = cloudServiceStubs;
30+
}
31+
32+
@Override
33+
public CloudServiceStubs getCloudServiceStubs() {
34+
return cloudServiceStubs;
35+
}
36+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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.client;
22+
23+
import io.temporal.api.cloud.cloudservice.v1.GetNamespaceRequest;
24+
import io.temporal.api.cloud.cloudservice.v1.GetNamespaceResponse;
25+
import io.temporal.serviceclient.CloudServiceStubs;
26+
import io.temporal.serviceclient.CloudServiceStubsOptions;
27+
import org.junit.Assert;
28+
import org.junit.Assume;
29+
import org.junit.Before;
30+
import org.junit.Test;
31+
32+
public class CloudOperationsClientTest {
33+
private String namespace;
34+
private String apiKey;
35+
private String apiVersion;
36+
37+
@Before
38+
public void checkCloudEnvVars() {
39+
namespace = System.getenv("TEMPORAL_CLIENT_CLOUD_NAMESPACE");
40+
apiKey = System.getenv("TEMPORAL_CLIENT_CLOUD_API_KEY");
41+
apiVersion = System.getenv("TEMPORAL_CLIENT_CLOUD_API_VERSION");
42+
Assume.assumeTrue(
43+
"Cloud environment variables not present", namespace != null && apiKey != null);
44+
}
45+
46+
@Test
47+
public void simpleCall() {
48+
CloudOperationsClient client =
49+
CloudOperationsClient.newInstance(
50+
CloudServiceStubs.newServiceStubs(
51+
CloudServiceStubsOptions.newBuilder()
52+
.addApiKey(() -> apiKey)
53+
.setVersion(apiVersion)
54+
.build()));
55+
// Do simple get namespace call
56+
GetNamespaceResponse resp =
57+
client
58+
.getCloudServiceStubs()
59+
.blockingStub()
60+
.getNamespace(GetNamespaceRequest.newBuilder().setNamespace(namespace).build());
61+
Assert.assertEquals(namespace, resp.getNamespace().getNamespace());
62+
}
63+
}

temporal-serviceclient/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,15 @@ sourcesJar {
5656
.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE)
5757
}
5858

59+
// Putting protocloud as an additional proto source set
60+
sourceSets {
61+
main {
62+
proto {
63+
srcDir 'src/main/protocloud'
64+
}
65+
}
66+
}
67+
5968
protobuf {
6069
// version/variables substitution is not supported in protobuf section.
6170
// protoc and protoc-gen-grpc-java versions are selected to be compatible

temporal-serviceclient/src/main/java/io/temporal/serviceclient/ChannelManager.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ final class ChannelManager {
7575
private static final Metadata.Key<String> CLIENT_NAME_HEADER_KEY =
7676
Metadata.Key.of("client-name", Metadata.ASCII_STRING_MARSHALLER);
7777

78+
/** refers to the name of the gRPC header that contains the cloud service version */
79+
private static final Metadata.Key<String> CLOUD_VERSION_HEADER_KEY =
80+
Metadata.Key.of("temporal-cloud-api-version", Metadata.ASCII_STRING_MARSHALLER);
81+
7882
private static final String CLIENT_NAME_HEADER_VALUE = "temporal-java";
7983

8084
private final ServiceStubsOptions options;
@@ -93,6 +97,18 @@ final class ChannelManager {
9397

9498
public ChannelManager(
9599
ServiceStubsOptions options, List<ClientInterceptor> additionalHeadInterceptors) {
100+
this(options, additionalHeadInterceptors, null);
101+
}
102+
103+
public ChannelManager(
104+
ServiceStubsOptions options,
105+
List<ClientInterceptor> additionalHeadInterceptors,
106+
@Nullable Capabilities fixedServerCapabilities) {
107+
// If fixed capabilities are present, set them on the future
108+
if (fixedServerCapabilities != null) {
109+
serverCapabilitiesFuture.complete(fixedServerCapabilities);
110+
}
111+
96112
// Do not shutdown a channel passed to the constructor from outside
97113
this.channelNeedsShutdown = options.getChannel() == null;
98114

@@ -154,6 +170,12 @@ private Channel applyHeadStandardInterceptors(Channel channel) {
154170
headers.put(LIBRARY_VERSION_HEADER_KEY, Version.LIBRARY_VERSION);
155171
headers.put(SUPPORTED_SERVER_VERSIONS_HEADER_KEY, Version.SUPPORTED_SERVER_VERSIONS);
156172
headers.put(CLIENT_NAME_HEADER_KEY, CLIENT_NAME_HEADER_VALUE);
173+
if (options instanceof CloudServiceStubsOptions) {
174+
String version = ((CloudServiceStubsOptions) options).getVersion();
175+
if (version != null) {
176+
headers.put(CLOUD_VERSION_HEADER_KEY, version);
177+
}
178+
}
157179

158180
return ClientInterceptors.intercept(
159181
channel,
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
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.serviceclient;
22+
23+
import static io.temporal.internal.WorkflowThreadMarker.enforceNonWorkflowThread;
24+
25+
import io.temporal.api.cloud.cloudservice.v1.CloudServiceGrpc;
26+
import io.temporal.internal.WorkflowThreadMarker;
27+
28+
/**
29+
* Initializes and holds gRPC blocking and future stubs.
30+
*
31+
* <p>WARNING: The cloud service is currently experimental.
32+
*/
33+
public interface CloudServiceStubs
34+
extends ServiceStubs<
35+
CloudServiceGrpc.CloudServiceBlockingStub, CloudServiceGrpc.CloudServiceFutureStub> {
36+
String HEALTH_CHECK_SERVICE_NAME = "temporal.api.cloud.cloudservice.v1.CloudService";
37+
38+
/** Creates CloudService gRPC stubs pointed on to Temporal Cloud. */
39+
static CloudServiceStubs newCloudServiceStubs() {
40+
return newServiceStubs(CloudServiceStubsOptions.getDefaultInstance());
41+
}
42+
43+
/**
44+
* Creates CloudService gRPC stubs<br>
45+
* This method creates stubs with lazy connectivity, connection is not performed during the
46+
* creation time and happens on the first request.
47+
*
48+
* @param options stub options to use
49+
*/
50+
static CloudServiceStubs newServiceStubs(CloudServiceStubsOptions options) {
51+
enforceNonWorkflowThread();
52+
return WorkflowThreadMarker.protectFromWorkflowThread(
53+
new CloudServiceStubsImpl(options), CloudServiceStubs.class);
54+
}
55+
}

0 commit comments

Comments
 (0)