Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(gax): add protobuf version tracking to headers #3199

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
31ebd95
track protobuf version
ldetmer Sep 11, 2024
e6aea43
add support for gccl
ldetmer Sep 13, 2024
a64e14b
formatting fixes
ldetmer Sep 13, 2024
3a7b27d
Merge branch 'main' into protobuf-metric
ldetmer Sep 13, 2024
0037a0a
formatting fixes
ldetmer Sep 13, 2024
ea7e7ec
feat(gax): add protobuf version to headers to track
ldetmer Sep 13, 2024
4e18aa6
feat(gax): add protobuf version to headers to track
ldetmer Sep 13, 2024
b1a8960
Merge branch 'main' into protobuf-metric
ldetmer Sep 13, 2024
8d6065c
feat(gax): add protobuf version to headers to track
ldetmer Sep 14, 2024
2dbc3a4
feat(gax): add protobuf version to headers to track
ldetmer Sep 14, 2024
b69684b
feat(gax): add protobuf version to headers to track
ldetmer Sep 14, 2024
02051b3
feat(gax): add protobuf version to headers to track
ldetmer Sep 15, 2024
31da0e5
feat(gax): add protobuf version to headers to track
ldetmer Sep 16, 2024
5a918f4
fixed todo wording
ldetmer Sep 16, 2024
94d3f45
cleaned up append token logic
ldetmer Sep 16, 2024
822ac0c
cleaned up append token logic
ldetmer Sep 16, 2024
390c922
cleaned up append token logic
ldetmer Sep 16, 2024
01eb41a
fixed language
ldetmer Sep 16, 2024
92e901b
added showcase tests + moved strings inline
ldetmer Sep 18, 2024
a202b8b
formatting fix
ldetmer Sep 18, 2024
f9fac2d
Merge branch 'main' into protobuf-metric
ldetmer Sep 18, 2024
9849efd
formatting fix
ldetmer Sep 18, 2024
604f00d
print header to debug native image
ldetmer Sep 18, 2024
9c51ec7
print header to debug native image
ldetmer Sep 18, 2024
a99c4b1
fixed native showcase test
ldetmer Sep 18, 2024
c7b3d2b
fixed formatting
ldetmer Sep 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
@InternalApi
public class GaxHttpJsonProperties {
private static final Pattern DEFAULT_API_CLIENT_HEADER_PATTERN =
Pattern.compile("gl-java/.+ gapic/.* gax/.+ rest/.*");
Pattern.compile("gl-java/.+ gapic/.*?--protobuf-.+ gax/.+ rest/.*");

/** Returns default api client header pattern (to facilitate testing) */
public static Pattern getDefaultApiClientHeaderPattern() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class GaxHttpJsonPropertiesTest {
void testDefaultHeaderPattern() {
assertTrue(
GaxHttpJsonProperties.getDefaultApiClientHeaderPattern()
.matcher("gl-java/1.8_00 gapic/1.2.3-alpha gax/1.5.0 rest/1.7.0")
.matcher("gl-java/1.8_00 gapic/1.2.3-alpha--protobuf-1.5.0 gax/1.5.0 rest/1.7.0")
.matches());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,15 @@
import com.google.api.core.InternalApi;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.google.protobuf.Any;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URISyntaxException;
import java.util.Optional;
import java.util.Properties;
import java.util.jar.Attributes;
import java.util.jar.JarFile;

/** Provides properties of the GAX library. */
@InternalApi
Expand All @@ -43,6 +49,8 @@ public class GaxProperties {
private static final String DEFAULT_VERSION = "";
private static final String GAX_VERSION = getLibraryVersion(GaxProperties.class, "version.gax");
private static final String JAVA_VERSION = getRuntimeVersion();
private static final String PROTOBUF_VERSION =
getBundleVersion(Any.class).orElse(DEFAULT_VERSION);

private GaxProperties() {}

Expand Down Expand Up @@ -91,6 +99,11 @@ public static String getGaxVersion() {
return GAX_VERSION;
}

/** Returns the current version of protobuf runtime library. */
public static String getProtobufVersion() {
return PROTOBUF_VERSION;
}

/**
* Returns the current runtime version. For GraalVM the values in this method will be fetched at
* build time and the values should not differ from the runtime (executable)
Expand All @@ -113,4 +126,27 @@ static String getRuntimeVersion() {
// with hyphens.
return javaRuntimeInformation.replaceAll("[^0-9a-zA-Z_\\\\.]", "-");
}

/**
* Returns the current library version as reported by Bundle-Version attribute in library's
* META-INF/MANIFEST for libraries using OSGi bundle manifest specification
* https://www.ibm.com/docs/en/wasdtfe?topic=overview-osgi-bundles. This should only be used if
* MANIFEST file does not contain a widely recognized version declaration such as Specific-Version
* OR Implementation-Version declared in Manifest Specification
* https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Manifest_Specification,
* otherwise please use #getLibraryVersion
*/
@VisibleForTesting
static Optional<String> getBundleVersion(Class<?> clazz) {
try {
File file = new File(clazz.getProtectionDomain().getCodeSource().getLocation().toURI());
try (JarFile jar = new JarFile(file.getPath())) {
Attributes attributes = jar.getManifest().getMainAttributes();
return Optional.ofNullable(attributes.getValue("Bundle-Version"));
}
} catch (URISyntaxException | IOException e) {
// Unable to read Bundle-Version from manifest. Recover gracefully.
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
import com.google.common.collect.ImmutableMap;
import java.io.Serializable;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
* Implementation of HeaderProvider that provides headers describing the API client library making
Expand All @@ -41,6 +43,7 @@
public class ApiClientHeaderProvider implements HeaderProvider, Serializable {
ldetmer marked this conversation as resolved.
Show resolved Hide resolved
private static final long serialVersionUID = -8876627296793342119L;
static final String QUOTA_PROJECT_ID_HEADER_KEY = "x-goog-user-project";
static final String PROTOBUF_HEADER_VERSION_KEY = "protobuf";

public static final String API_VERSION_HEADER_KEY = "x-goog-api-version";

Expand All @@ -57,8 +60,12 @@ protected ApiClientHeaderProvider(Builder builder) {
appendToken(apiClientHeaderValue, builder.getGeneratedLibToken());
appendToken(apiClientHeaderValue, builder.getGeneratedRuntimeToken());
appendToken(apiClientHeaderValue, builder.getTransportToken());
appendToken(apiClientHeaderValue, builder.protobufRuntimeToken);
burkedavison marked this conversation as resolved.
Show resolved Hide resolved

if (apiClientHeaderValue.length() > 0) {
headersBuilder.put(builder.getApiClientHeaderKey(), apiClientHeaderValue.toString());
headersBuilder.put(
builder.getApiClientHeaderKey(),
checkAndAppendProtobufVersionIfNecessary(apiClientHeaderValue));
}
}

Expand All @@ -76,6 +83,22 @@ protected ApiClientHeaderProvider(Builder builder) {
this.headers = headersBuilder.build();
}

private static String checkAndAppendProtobufVersionIfNecessary(
StringBuilder apiClientHeaderValue) {
// TODO(b/366417603): appending protobuf version to existing client library token until resolved
Pattern pattern = Pattern.compile("(gccl|gapic)\\S*");
Matcher matcher = pattern.matcher(apiClientHeaderValue);
if (matcher.find()) {
return apiClientHeaderValue.substring(0, matcher.end())
+ "--"
+ PROTOBUF_HEADER_VERSION_KEY
+ "-"
+ GaxProperties.getProtobufVersion()
+ apiClientHeaderValue.substring(matcher.end());
}
return apiClientHeaderValue.toString();
}

@Override
public Map<String, String> getHeaders() {
return headers;
Expand Down Expand Up @@ -110,6 +133,7 @@ public static class Builder {
private String generatedRuntimeToken;
private String transportToken;
private String quotaProjectIdToken;
private final String protobufRuntimeToken;

private String resourceHeaderKey;
private String resourceToken;
Expand All @@ -125,11 +149,11 @@ protected Builder() {
setClientRuntimeToken(GaxProperties.getGaxVersion());
transportToken = null;
quotaProjectIdToken = null;

resourceHeaderKey = getDefaultResourceHeaderKey();
resourceToken = null;

apiVersionToken = null;
protobufRuntimeToken =
constructToken(PROTOBUF_HEADER_VERSION_KEY, GaxProperties.getProtobufVersion());
}

public String getApiClientHeaderKey() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@
*/
package com.google.api.gax.core;

import static com.google.api.gax.core.GaxProperties.getBundleVersion;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.common.base.Strings;
import java.io.IOException;
import java.util.Optional;
import java.util.regex.Pattern;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
Expand All @@ -41,17 +45,11 @@ class GaxPropertiesTest {

@Test
void testGaxVersion() {
String gaxVersion = GaxProperties.getGaxVersion();
assertTrue(Pattern.compile("^\\d+\\.\\d+\\.\\d+").matcher(gaxVersion).find());
String[] versionComponents = gaxVersion.split("\\.");
// This test was added in version 1.56.0, so check that the major and minor numbers are greater
// than that.
int major = Integer.parseInt(versionComponents[0]);
int minor = Integer.parseInt(versionComponents[1]);
Version version = readVersion(GaxProperties.getGaxVersion());

assertTrue(major >= 1);
if (major == 1) {
assertTrue(minor >= 56);
assertTrue(version.major >= 1);
if (version.major == 1) {
assertTrue(version.minor >= 56);
}
}

Expand Down Expand Up @@ -159,4 +157,41 @@ void testGetJavaRuntimeInfo_nullJavaVersion() {
String runtimeInfo = GaxProperties.getRuntimeVersion();
assertEquals("null__oracle__20.0.1", runtimeInfo);
}

@Test
public void testGetProtobufVersion() throws IOException {
Version version = readVersion(GaxProperties.getProtobufVersion());

assertTrue(version.major >= 3);
if (version.major == 3) {
assertTrue(version.minor >= 25);
}
}

@Test
public void testGetBundleVersion_noManifestFile() throws IOException {
Optional<String> version = getBundleVersion(GaxProperties.class);

assertFalse(version.isPresent());
}

private Version readVersion(String version) {
assertTrue(Pattern.compile("^\\d+\\.\\d+\\.\\d+").matcher(version).find());
String[] versionComponents = version.split("\\.");
// This test was added in version 1.56.0, so check that the major and minor numbers are greater
// than that.
int major = Integer.parseInt(versionComponents[0]);
int minor = Integer.parseInt(versionComponents[1]);
return new Version(major, minor);
}

private static class Version {
public int major;
public int minor;

public Version(int major, int minor) {
this.major = major;
this.minor = minor;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ class ApiClientHeaderProviderTest {
void testServiceHeaderDefault() {
ApiClientHeaderProvider provider = ApiClientHeaderProvider.newBuilder().build();
assertThat(provider.getHeaders().size()).isEqualTo(1);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT)).matches("^gl-java/.* gax/.*$");
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gax/.* protobuf/.*");
}

@Test
Expand All @@ -51,7 +52,7 @@ void testServiceHeaderManual() {
ApiClientHeaderProvider.newBuilder().setClientLibToken("gccl", "1.2.3").build();
assertThat(provider.getHeaders().size()).isEqualTo(1);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gccl/1\\.2\\.3 gax/.*$");
.matches("^gl-java/.* gccl/1\\.2\\.3--protobuf-.* gax/.* protobuf/.*");
}

@Test
Expand All @@ -64,7 +65,8 @@ void testServiceHeaderManualGapic() {
.build();
assertThat(provider.getHeaders().size()).isEqualTo(1);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gccl/4\\.5\\.6 gapic/7\\.8\\.9 gax/.* grpc/1\\.2\\.3$");
.matches(
"^gl-java/.* gccl/4\\.5\\.6--protobuf-.* gapic/7\\.8\\.9 gax/.* grpc/1\\.2\\.3 protobuf/.*");
}

@Test
Expand All @@ -76,7 +78,7 @@ void testServiceHeaderManualGrpc() {
.build();
assertThat(provider.getHeaders().size()).isEqualTo(1);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gccl/4\\.5\\.6 gax/.* grpc/1\\.2\\.3$");
.matches("^gl-java/.* gccl/4\\.5\\.6--protobuf-.* gax/.* grpc/1\\.2\\.3 protobuf/.*");
}

@Test
Expand All @@ -88,7 +90,7 @@ void testServiceHeaderGapic() {
.build();
assertThat(provider.getHeaders().size()).isEqualTo(1);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gapic/4\\.5\\.6 gax/.* grpc/1\\.2\\.3$");
.matches("^gl-java/.* gapic/4\\.5\\.6--protobuf-.* gax/.* grpc/1\\.2\\.3 protobuf/.*");
}

@Test
Expand All @@ -101,7 +103,7 @@ void testCloudResourcePrefixHeader() {
.build();
assertThat(provider.getHeaders().size()).isEqualTo(2);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gapic/4\\.5\\.6 gax/.* grpc/1\\.2\\.3$");
.matches("^gl-java/.* gapic/4\\.5\\.6--protobuf-.* gax/.* grpc/1\\.2\\.3 protobuf/.*");
assertThat(provider.getHeaders().get(CLOUD_RESOURCE_PREFIX)).isEqualTo("test-prefix");
}

Expand All @@ -117,7 +119,7 @@ void testCustomHeaderKeys() {
.build();
assertThat(provider.getHeaders().size()).isEqualTo(2);
assertThat(provider.getHeaders().get("custom-header1"))
.matches("^gl-java/.* gapic/4\\.5\\.6 gax/.* grpc/1\\.2\\.3$");
.matches("^gl-java/.* gapic/4\\.5\\.6--protobuf-.* gax/.* grpc/1\\.2\\.3 protobuf/.*");
assertThat(provider.getHeaders().get("custom-header2")).isEqualTo("test-prefix");
}

Expand All @@ -131,7 +133,7 @@ void testQuotaProjectHeader() {
.build();
assertThat(provider.getHeaders().size()).isEqualTo(2);
assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* gccl/1\\.2\\.3 gax/.*$");
.matches("^gl-java/.* gccl/1\\.2\\.3--protobuf-.* gax/.* protobuf/.*");
assertThat(provider.getHeaders().get(ApiClientHeaderProvider.QUOTA_PROJECT_ID_HEADER_KEY))
.matches(quotaProjectHeaderValue);
}
Expand All @@ -149,4 +151,22 @@ void testApiVersionHeader() {
assertThat(
emptyProvider.getHeaders().get(ApiClientHeaderProvider.API_VERSION_HEADER_KEY).isEmpty());
}

@Test
void testNonGapicGeneratedLibToken_doesNotAppendProtobufVersion() {
ApiClientHeaderProvider provider =
ApiClientHeaderProvider.newBuilder().setGeneratedLibToken("other-token", "1.2.3").build();

assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* other-token/1.2.3 gax/.* protobuf/.*");
}

@Test
void testNonGcclGeneratedLibToken_doesNotAppendProtobufVersion() {
ApiClientHeaderProvider provider =
ApiClientHeaderProvider.newBuilder().setClientLibToken("other-token", "1.2.3").build();

assertThat(provider.getHeaders().get(X_GOOG_API_CLIENT))
.matches("^gl-java/.* other-token/1.2.3 gax/.* protobuf/.*");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import static com.google.common.truth.Truth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.google.api.gax.httpjson.*;
import com.google.api.gax.rpc.ApiClientHeaderProvider;
Expand All @@ -31,6 +32,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
Expand All @@ -39,13 +41,19 @@
// https://github.com/googleapis/gapic-showcase/pull/1456
// TODO: watch for showcase gRPC trailer changes suggested in
// https://github.com/googleapis/gapic-showcase/pull/1509#issuecomment-2089147103
class ITApiVersionHeaders {
class ITVersionHeaders {
private static final String HTTP_RESPONSE_HEADER_STRING =
"x-showcase-request-" + ApiClientHeaderProvider.API_VERSION_HEADER_KEY;
private static final String HTTP_CLIENT_API_HEADER_KEY =
"x-showcase-request-" + ApiClientHeaderProvider.getDefaultApiClientHeaderKey();
private static final Metadata.Key<String> API_VERSION_HEADER_KEY =
Metadata.Key.of(
ApiClientHeaderProvider.API_VERSION_HEADER_KEY, Metadata.ASCII_STRING_MARSHALLER);

private static final Metadata.Key<String> API_CLIENT_HEADER_KEY =
Metadata.Key.of(
ApiClientHeaderProvider.getDefaultApiClientHeaderKey(), Metadata.ASCII_STRING_MARSHALLER);

private static final String EXPECTED_ECHO_API_VERSION = "v1_20240408";
private static final String CUSTOM_API_VERSION = "user-supplied-version";
private static final String EXPECTED_EXCEPTION_MESSAGE =
Expand Down Expand Up @@ -323,4 +331,25 @@ void testHttpJsonCompliance_userApiVersionSetSuccess() throws IOException {
assertThat(headerValue).isEqualTo(CUSTOM_API_VERSION);
}
}

@Test
void testGrpcCall_sendsCorrectApiClientHeader() {
Pattern defautlGrpcHeaderPattern =
Pattern.compile("gl-java/.* gapic/.*?--protobuf-.* gax/.* grpc/.* protobuf/.*");
grpcClient.echo(EchoRequest.newBuilder().build());
String headerValue = grpcInterceptor.metadata.get(API_CLIENT_HEADER_KEY);
assertTrue(defautlGrpcHeaderPattern.matcher(headerValue).matches());
}

@Test
void testHttpJson_sendsCorrectApiClientHeader() {
Pattern defautlHttpHeaderPattern =
Pattern.compile("gl-java/.* gapic/.*?--protobuf-.* gax/.* rest/ protobuf/.*");
httpJsonClient.echo(EchoRequest.newBuilder().build());
ArrayList<String> headerValues =
(ArrayList<String>)
httpJsonInterceptor.metadata.getHeaders().get(HTTP_CLIENT_API_HEADER_KEY);
String headerValue = headerValues.get(0);
assertTrue(defautlHttpHeaderPattern.matcher(headerValue).matches());
}
}
Loading