Skip to content

Commit

Permalink
Experimental cloud operations client (#2146)
Browse files Browse the repository at this point in the history
Fixes #2059
  • Loading branch information
cretz authored Jul 19, 2024
1 parent eb7d9ee commit 0ba6188
Show file tree
Hide file tree
Showing 11 changed files with 520 additions and 0 deletions.
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,45 @@ jobs:
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

unit_test_cloud:
name: Unit test with cloud
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repo
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive
ref: ${{ github.event.pull_request.head.sha }}

- name: Set up Java
uses: actions/setup-java@v4
with:
java-version: "11"
distribution: "temurin"

- name: Set up Gradle
uses: gradle/actions/setup-gradle@v3

- name: Run cloud test
# Only supported in non-fork runs, since secrets are not available in forks. We intentionally
# are only doing this check on the step instead of the job so we require job passing in CI
# even for those that can't run this step.
if: ${{ github.event.pull_request.head.repo.full_name == '' || github.event.pull_request.head.repo.full_name == 'temporalio/sdk-java' }}
env:
USER: unittest
TEMPORAL_CLIENT_CLOUD_NAMESPACE: sdk-ci.a2dd6
TEMPORAL_CLIENT_CLOUD_API_KEY: ${{ secrets.TEMPORAL_CLIENT_CLOUD_API_KEY }}
TEMPORAL_CLIENT_CLOUD_API_VERSION: 2024-05-13-00
run: ./gradlew --no-daemon :temporal-sdk:test --tests '*CloudOperationsClientTest'

- name: Publish Test Report
uses: mikepenz/action-junit-report@v4
if: success() || failure() # always run even if the previous step fails
with:
report_paths: '**/build/test-results/test/TEST-*.xml'

copyright:
name: Copyright and code format
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "temporal-serviceclient/src/main/proto"]
path = temporal-serviceclient/src/main/proto
url = https://github.com/temporalio/api.git
[submodule "temporal-serviceclient/src/main/protocloud"]
path = temporal-serviceclient/src/main/protocloud
url = https://github.com/temporalio/api-cloud.git
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.client;

import io.temporal.common.Experimental;
import io.temporal.serviceclient.CloudServiceStubs;

/** Client to the Temporal Cloud operations service for performing cloud operations. */
@Experimental
public interface CloudOperationsClient {
@Experimental
static CloudOperationsClient newInstance(CloudServiceStubs service) {
return new CloudOperationsClientImpl(service);
}

/** Get the raw cloud service stubs. */
@Experimental
CloudServiceStubs getCloudServiceStubs();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.client;

import io.temporal.serviceclient.CloudServiceStubs;

class CloudOperationsClientImpl implements CloudOperationsClient {
private final CloudServiceStubs cloudServiceStubs;

CloudOperationsClientImpl(CloudServiceStubs cloudServiceStubs) {
this.cloudServiceStubs = cloudServiceStubs;
}

@Override
public CloudServiceStubs getCloudServiceStubs() {
return cloudServiceStubs;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.client;

import io.temporal.api.cloud.cloudservice.v1.GetNamespaceRequest;
import io.temporal.api.cloud.cloudservice.v1.GetNamespaceResponse;
import io.temporal.serviceclient.CloudServiceStubs;
import io.temporal.serviceclient.CloudServiceStubsOptions;
import org.junit.Assert;
import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;

public class CloudOperationsClientTest {
private String namespace;
private String apiKey;
private String apiVersion;

@Before
public void checkCloudEnvVars() {
namespace = System.getenv("TEMPORAL_CLIENT_CLOUD_NAMESPACE");
apiKey = System.getenv("TEMPORAL_CLIENT_CLOUD_API_KEY");
apiVersion = System.getenv("TEMPORAL_CLIENT_CLOUD_API_VERSION");
Assume.assumeTrue(
"Cloud environment variables not present", namespace != null && apiKey != null);
}

@Test
public void simpleCall() {
CloudOperationsClient client =
CloudOperationsClient.newInstance(
CloudServiceStubs.newServiceStubs(
CloudServiceStubsOptions.newBuilder()
.addApiKey(() -> apiKey)
.setVersion(apiVersion)
.build()));
// Do simple get namespace call
GetNamespaceResponse resp =
client
.getCloudServiceStubs()
.blockingStub()
.getNamespace(GetNamespaceRequest.newBuilder().setNamespace(namespace).build());
Assert.assertEquals(namespace, resp.getNamespace().getNamespace());
}
}
9 changes: 9 additions & 0 deletions temporal-serviceclient/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ sourcesJar {
.setDuplicatesStrategy(DuplicatesStrategy.EXCLUDE)
}

// Putting protocloud as an additional proto source set
sourceSets {
main {
proto {
srcDir 'src/main/protocloud'
}
}
}

protobuf {
// version/variables substitution is not supported in protobuf section.
// protoc and protoc-gen-grpc-java versions are selected to be compatible
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ final class ChannelManager {
private static final Metadata.Key<String> CLIENT_NAME_HEADER_KEY =
Metadata.Key.of("client-name", Metadata.ASCII_STRING_MARSHALLER);

/** refers to the name of the gRPC header that contains the cloud service version */
private static final Metadata.Key<String> CLOUD_VERSION_HEADER_KEY =
Metadata.Key.of("temporal-cloud-api-version", Metadata.ASCII_STRING_MARSHALLER);

private static final String CLIENT_NAME_HEADER_VALUE = "temporal-java";

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

public ChannelManager(
ServiceStubsOptions options, List<ClientInterceptor> additionalHeadInterceptors) {
this(options, additionalHeadInterceptors, null);
}

public ChannelManager(
ServiceStubsOptions options,
List<ClientInterceptor> additionalHeadInterceptors,
@Nullable Capabilities fixedServerCapabilities) {
// If fixed capabilities are present, set them on the future
if (fixedServerCapabilities != null) {
serverCapabilitiesFuture.complete(fixedServerCapabilities);
}

// Do not shutdown a channel passed to the constructor from outside
this.channelNeedsShutdown = options.getChannel() == null;

Expand Down Expand Up @@ -154,6 +170,12 @@ private Channel applyHeadStandardInterceptors(Channel channel) {
headers.put(LIBRARY_VERSION_HEADER_KEY, Version.LIBRARY_VERSION);
headers.put(SUPPORTED_SERVER_VERSIONS_HEADER_KEY, Version.SUPPORTED_SERVER_VERSIONS);
headers.put(CLIENT_NAME_HEADER_KEY, CLIENT_NAME_HEADER_VALUE);
if (options instanceof CloudServiceStubsOptions) {
String version = ((CloudServiceStubsOptions) options).getVersion();
if (version != null) {
headers.put(CLOUD_VERSION_HEADER_KEY, version);
}
}

return ClientInterceptors.intercept(
channel,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.temporal.serviceclient;

import static io.temporal.internal.WorkflowThreadMarker.enforceNonWorkflowThread;

import io.temporal.api.cloud.cloudservice.v1.CloudServiceGrpc;
import io.temporal.internal.WorkflowThreadMarker;

/**
* Initializes and holds gRPC blocking and future stubs.
*
* <p>WARNING: The cloud service is currently experimental.
*/
public interface CloudServiceStubs
extends ServiceStubs<
CloudServiceGrpc.CloudServiceBlockingStub, CloudServiceGrpc.CloudServiceFutureStub> {
String HEALTH_CHECK_SERVICE_NAME = "temporal.api.cloud.cloudservice.v1.CloudService";

/** Creates CloudService gRPC stubs pointed on to Temporal Cloud. */
static CloudServiceStubs newCloudServiceStubs() {
return newServiceStubs(CloudServiceStubsOptions.getDefaultInstance());
}

/**
* Creates CloudService gRPC stubs<br>
* This method creates stubs with lazy connectivity, connection is not performed during the
* creation time and happens on the first request.
*
* @param options stub options to use
*/
static CloudServiceStubs newServiceStubs(CloudServiceStubsOptions options) {
enforceNonWorkflowThread();
return WorkflowThreadMarker.protectFromWorkflowThread(
new CloudServiceStubsImpl(options), CloudServiceStubs.class);
}
}
Loading

0 comments on commit 0ba6188

Please sign in to comment.