Skip to content
This repository has been archived by the owner on Dec 3, 2023. It is now read-only.

feat: support setting ServiceOption for quota project #92

Merged
merged 7 commits into from Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -176,6 +176,7 @@ ApiClientHeaderProvider.Builder getInternalHeaderProviderBuilder(
builder.setClientLibToken(
ServiceOptions.getGoogApiClientLibName(),
GaxProperties.getLibraryVersion(serviceOptions.getClass()));
builder.setQuotaProjectIdToken(serviceOptions.getQuotaProjectId());
return builder;
}

Expand Down
Expand Up @@ -43,6 +43,7 @@
import com.google.api.gax.rpc.NoHeaderProvider;
import com.google.auth.Credentials;
import com.google.auth.oauth2.GoogleCredentials;
import com.google.auth.oauth2.QuotaProjectIdProvider;
import com.google.auth.oauth2.ServiceAccountCredentials;
import com.google.cloud.spi.ServiceRpcFactory;
import com.google.common.base.Preconditions;
Expand Down Expand Up @@ -102,6 +103,7 @@ public abstract class ServiceOptions<
protected Credentials credentials;
private final TransportOptions transportOptions;
private final HeaderProvider headerProvider;
private final String quotaProjectId;

private transient ServiceRpcFactory<OptionsT> serviceRpcFactory;
private transient ServiceFactory<ServiceT, OptionsT> serviceFactory;
Expand Down Expand Up @@ -132,6 +134,7 @@ public abstract static class Builder<
private TransportOptions transportOptions;
private HeaderProvider headerProvider;
private String clientLibToken = ServiceOptions.getGoogApiClientLibName();
private String quotaProjectId;

@InternalApi("This class should only be extended within google-cloud-java")
protected Builder() {}
Expand All @@ -147,6 +150,7 @@ protected Builder(ServiceOptions<ServiceT, OptionsT> options) {
clock = options.clock;
transportOptions = options.transportOptions;
clientLibToken = options.clientLibToken;
quotaProjectId = options.quotaProjectId;
}

protected abstract ServiceOptions<ServiceT, OptionsT> build();
Expand Down Expand Up @@ -212,6 +216,10 @@ public B setCredentials(Credentials credentials) {
if (this.projectId == null && credentials instanceof ServiceAccountCredentials) {
this.projectId = ((ServiceAccountCredentials) credentials).getProjectId();
}

chingor13 marked this conversation as resolved.
Show resolved Hide resolved
if (this.quotaProjectId == null && credentials instanceof QuotaProjectIdProvider) {
this.quotaProjectId = ((ServiceAccountCredentials) credentials).getQuotaProjectId();
}
return self();
}

Expand Down Expand Up @@ -269,6 +277,17 @@ public B setClientLibToken(String clientLibToken) {
return self();
}

/**
* Sets the quotaProjectId that specifies the project used for quota and billing purposes.
*
* @see <a href="https://cloud.google.com/apis/docs/system-parameters">See system parameter
* $userProject</a>
*/
public B setQuotaProjectId(String quotaProjectId) {
this.quotaProjectId = quotaProjectId;
return self();
}

protected Set<String> getAllowedClientLibTokens() {
return allowedClientLibTokens;
}
Expand Down Expand Up @@ -305,6 +324,10 @@ protected ServiceOptions(
firstNonNull(builder.transportOptions, serviceDefaults.getDefaultTransportOptions());
headerProvider = firstNonNull(builder.headerProvider, new NoHeaderProvider());
clientLibToken = builder.clientLibToken;
quotaProjectId =
builder.quotaProjectId != null
? builder.quotaProjectId
: getValueFromCredentialsFile(System.getenv(CREDENTIAL_ENV_NAME), "quota_project_id");
}

/**
Expand Down Expand Up @@ -488,24 +511,24 @@ static boolean headerContainsMetadataFlavor(HttpResponse response) {
}

protected static String getServiceAccountProjectId() {
return getServiceAccountProjectId(System.getenv(CREDENTIAL_ENV_NAME));
return getValueFromCredentialsFile(System.getenv(CREDENTIAL_ENV_NAME), "project_id");
}

@InternalApi("Visible for testing")
static String getServiceAccountProjectId(String credentialsPath) {
String project = null;
static String getValueFromCredentialsFile(String credentialsPath, String key) {
String value = null;
if (credentialsPath != null) {
try (InputStream credentialsStream = new FileInputStream(credentialsPath)) {
JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();
JsonObjectParser parser = new JsonObjectParser(jsonFactory);
GenericJson fileContents =
parser.parseAndClose(credentialsStream, Charsets.UTF_8, GenericJson.class);
project = (String) fileContents.get("project_id");
value = (String) fileContents.get(key);
} catch (IOException e) {
// ignore
}
}
return project;
return value;
}

/**
Expand Down Expand Up @@ -664,7 +687,8 @@ protected int baseHashCode() {
retrySettings,
serviceFactoryClassName,
serviceRpcFactoryClassName,
clock);
clock,
quotaProjectId);
}

protected boolean baseEquals(ServiceOptions<?, ?> other) {
Expand All @@ -674,7 +698,8 @@ protected boolean baseEquals(ServiceOptions<?, ?> other) {
&& Objects.equals(retrySettings, other.retrySettings)
&& Objects.equals(serviceFactoryClassName, other.serviceFactoryClassName)
&& Objects.equals(serviceRpcFactoryClassName, other.serviceRpcFactoryClassName)
&& Objects.equals(clock, clock);
&& Objects.equals(clock, clock)
&& Objects.equals(quotaProjectId, other.quotaProjectId);
}

private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException {
Expand Down Expand Up @@ -734,4 +759,9 @@ public static <T> T getFromServiceLoader(Class<? extends T> clazz, T defaultInst
public String getClientLibToken() {
return clientLibToken;
}

/** Returns the quotaProjectId that specifies the project used for quota and billing purposes. */
public String getQuotaProjectId() {
return quotaProjectId;
}
}
Expand Up @@ -53,6 +53,10 @@
import org.junit.rules.ExpectedException;

public class ServiceOptionsTest {
private static GoogleCredentials credentials;
private static GoogleCredentials credentialsWithProjectId;
private static GoogleCredentials credentialsWithQuotaProject;

private static final String JSON_KEY =
"{\n"
+ " \"private_key_id\": \"somekeyid\",\n"
Expand Down Expand Up @@ -80,16 +84,6 @@ public class ServiceOptionsTest {
+ " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
+ " \"type\": \"service_account\"\n"
+ "}";
private static GoogleCredentials credentials;

static {
try {
InputStream keyStream = new ByteArrayInputStream(JSON_KEY.getBytes());
credentials = GoogleCredentials.fromStream(keyStream);
} catch (IOException e) {
fail("Couldn't create fake JSON credentials.");
}
}

private static final String JSON_KEY_PROJECT_ID =
"{\n"
Expand Down Expand Up @@ -119,15 +113,51 @@ public class ServiceOptionsTest {
+ " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
+ " \"type\": \"service_account\"\n"
+ "}";
private static GoogleCredentials credentialsWithProjectId;

private static final String JSON_KEY_QUOTA_PROJECT_ID =
"{\n"
+ " \"private_key_id\": \"somekeyid\",\n"
+ " \"private_key\": \"-----BEGIN PRIVATE KEY-----\\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggS"
+ "kAgEAAoIBAQC+K2hSuFpAdrJI\\nnCgcDz2M7t7bjdlsadsasad+fvRSW6TjNQZ3p5LLQY1kSZRqBqylRkzteMOyHg"
+ "aR\\n0Pmxh3ILCND5men43j3h4eDbrhQBuxfEMalkG92sL+PNQSETY2tnvXryOvmBRwa/\\nQP/9dJfIkIDJ9Fw9N4"
+ "Bhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nknddadwkwewcVxHFhcZJO+XWf6ofLUXpRwiTZakGMn8EE1uVa2"
+ "LgczOjwWHGi99MFjxSer5m9\\n1tCa3/KEGKiS/YL71JvjwX3mb+cewlkcmweBKZHM2JPTk0ZednFSpVZMtycjkbLa"
+ "\\ndYOS8V85AgMBewECggEBAKksaldajfDZDV6nGqbFjMiizAKJolr/M3OQw16K6o3/\\n0S31xIe3sSlgW0+UbYlF"
+ "4U8KifhManD1apVSC3csafaspP4RZUHFhtBywLO9pR5c\\nr6S5aLp+gPWFyIp1pfXbWGvc5VY/v9x7ya1VEa6rXvL"
+ "sKupSeWAW4tMj3eo/64ge\\nsdaceaLYw52KeBYiT6+vpsnYrEkAHO1fF/LavbLLOFJmFTMxmsNaG0tuiJHgjshB\\"
+ "n82DpMCbXG9YcCgI/DbzuIjsdj2JC1cascSP//3PmefWysucBQe7Jryb6NQtASmnv\\nCdDw/0jmZTEjpe4S1lxfHp"
+ "lAhHFtdgYTvyYtaLZiVVkCgYEA8eVpof2rceecw/I6\\n5ng1q3Hl2usdWV/4mZMvR0fOemacLLfocX6IYxT1zA1FF"
+ "JlbXSRsJMf/Qq39mOR2\\nSpW+hr4jCoHeRVYLgsbggtrevGmILAlNoqCMpGZ6vDmJpq6ECV9olliDvpPgWOP+\\nm"
+ "YPDreFBGxWvQrADNbRt2dmGsrsCgYEAyUHqB2wvJHFqdmeBsaacewzV8x9WgmeX\\ngUIi9REwXlGDW0Mz50dxpxcK"
+ "CAYn65+7TCnY5O/jmL0VRxU1J2mSWyWTo1C+17L0\\n3fUqjxL1pkefwecxwecvC+gFFYdJ4CQ/MHHXU81Lwl1iWdF"
+ "Cd2UoGddYaOF+KNeM\\nHC7cmqra+JsCgYEAlUNywzq8nUg7282E+uICfCB0LfwejuymR93CtsFgb7cRd6ak\\nECR"
+ "8FGfCpH8ruWJINllbQfcHVCX47ndLZwqv3oVFKh6pAS/vVI4dpOepP8++7y1u\\ncoOvtreXCX6XqfrWDtKIvv0vjl"
+ "HBhhhp6mCcRpdQjV38H7JsyJ7lih/oNjECgYAt\\nkndj5uNl5SiuVxHFhcZJO+XWf6ofLUregtevZakGMn8EE1uVa"
+ "2AY7eafmoU/nZPT\\n00YB0TBATdCbn/nBSuKDESkhSg9s2GEKQZG5hBmL5uCMfo09z3SfxZIhJdlerreP\\nJ7gSi"
+ "dI12N+EZxYd4xIJh/HFDgp7RRO87f+WJkofMQKBgGTnClK1VMaCRbJZPriw\\nEfeFCoOX75MxKwXs6xgrw4W//AYG"
+ "GUjDt83lD6AZP6tws7gJ2IwY/qP7+lyhjEqN\\nHtfPZRGFkGZsdaksdlaksd323423d+15/UvrlRSFPNj1tWQmNKk"
+ "XyRDW4IG1Oa2p\\nrALStNBx5Y9t0/LQnFI4w3aG\\n-----END PRIVATE KEY-----\\n\",\n"
+ " \"project_id\": \"someprojectid\",\n"
+ " \"client_email\": \"someclientid@developer.gserviceaccount.com\",\n"
+ " \"client_id\": \"someclientid.apps.googleusercontent.com\",\n"
+ " \"type\": \"service_account\",\n"
+ " \"quota_project_id\": \"some-quota-project-id\"\n"
codyoss marked this conversation as resolved.
Show resolved Hide resolved
+ "}";

static {
credentials = loadCredentials(JSON_KEY);
credentialsWithProjectId = loadCredentials(JSON_KEY_PROJECT_ID);
credentialsWithQuotaProject = loadCredentials(JSON_KEY_QUOTA_PROJECT_ID);
}

static GoogleCredentials loadCredentials(String credentialFile) {
try {
InputStream keyStream = new ByteArrayInputStream(JSON_KEY_PROJECT_ID.getBytes());
credentialsWithProjectId = GoogleCredentials.fromStream(keyStream);
InputStream keyStream = new ByteArrayInputStream(credentialFile.getBytes());
return GoogleCredentials.fromStream(keyStream);
} catch (IOException e) {
fail("Couldn't create fake JSON credentials.");
}
return null;
}

private static final ApiClock TEST_CLOCK = new TestClock();
Expand All @@ -138,6 +168,7 @@ public class ServiceOptionsTest {
.setHost("host")
.setProjectId("project-id")
.setRetrySettings(ServiceOptions.getNoRetrySettings())
.setQuotaProjectId("quota-project-id")
.build();
private static final TestServiceOptions OPTIONS_NO_CREDENTIALS =
TestServiceOptions.newBuilder()
Expand All @@ -146,6 +177,7 @@ public class ServiceOptionsTest {
.setHost("host")
.setProjectId("project-id")
.setRetrySettings(ServiceOptions.getNoRetrySettings())
.setQuotaProjectId("quota-project-id")
.build();
private static final TestServiceOptions DEFAULT_OPTIONS =
TestServiceOptions.newBuilder().setProjectId("project-id").build();
Expand Down Expand Up @@ -283,6 +315,39 @@ public void testBuilder() {
assertSame(ServiceOptions.getDefaultRetrySettings(), DEFAULT_OPTIONS.getRetrySettings());
}

@Test
public void testBuilder_quotaProjectServiceOptionTakesPrecedence() {
TestServiceOptions noCredsWithQuotaProject =
TestServiceOptions.newBuilder()
.setCredentials(NoCredentials.getInstance())
.setProjectId("project-id")
.setQuotaProjectId("quota-project-id")
.build();
TestServiceOptions quotaProjectCredsWithQuotaProject =
TestServiceOptions.newBuilder()
.setQuotaProjectId("quota-project-id")
.setCredentials(credentialsWithQuotaProject)
.build();
TestServiceOptions quotaProjectCredsWithQuotaProject2 =
TestServiceOptions.newBuilder()
.setCredentials(credentialsWithQuotaProject)
.setQuotaProjectId("quota-project-id")
.build();
TestServiceOptions quotaProjectCreds =
TestServiceOptions.newBuilder().setCredentials(credentialsWithQuotaProject).build();
TestServiceOptions none =
TestServiceOptions.newBuilder()
.setCredentials(NoCredentials.getInstance())
.setProjectId("project-id")
.build();

assertEquals("quota-project-id", noCredsWithQuotaProject.getQuotaProjectId());
assertEquals("quota-project-id", quotaProjectCredsWithQuotaProject.getQuotaProjectId());
assertEquals("quota-project-id", quotaProjectCredsWithQuotaProject2.getQuotaProjectId());
assertEquals("some-quota-project-id", quotaProjectCreds.getQuotaProjectId());
assertEquals(null, none.getQuotaProjectId());
}

@Test
public void testBuilderNoCredentials() {
assertEquals(NoCredentials.getInstance(), OPTIONS_NO_CREDENTIALS.getCredentials());
Expand All @@ -293,6 +358,7 @@ public void testBuilderNoCredentials() {
assertEquals("host", OPTIONS_NO_CREDENTIALS.getHost());
assertEquals("project-id", OPTIONS_NO_CREDENTIALS.getProjectId());
assertSame(ServiceOptions.getNoRetrySettings(), OPTIONS_NO_CREDENTIALS.getRetrySettings());
assertEquals("quota-project-id", OPTIONS.getQuotaProjectId());
}

@Test
Expand Down Expand Up @@ -372,7 +438,8 @@ public void testGetServiceAccountProjectId() throws Exception {
Files.write("{\"project_id\":\"my-project-id\"}".getBytes(), credentialsFile);

assertEquals(
"my-project-id", ServiceOptions.getServiceAccountProjectId(credentialsFile.getPath()));
"my-project-id",
ServiceOptions.getValueFromCredentialsFile(credentialsFile.getPath(), "project_id"));
}

@Test
Expand All @@ -381,14 +448,14 @@ public void testGetServiceAccountProjectId_badJson() throws Exception {
credentialsFile.deleteOnExit();
Files.write("asdfghj".getBytes(), credentialsFile);

assertNull(ServiceOptions.getServiceAccountProjectId(credentialsFile.getPath()));
assertNull(ServiceOptions.getValueFromCredentialsFile(credentialsFile.getPath(), "project_id"));
}

@Test
public void testGetServiceAccountProjectId_nonExistentFile() throws Exception {
File credentialsFile = new File("/doesnotexist");

assertNull(ServiceOptions.getServiceAccountProjectId(credentialsFile.getPath()));
assertNull(ServiceOptions.getValueFromCredentialsFile(credentialsFile.getPath(), "project_id"));
}

@Test
Expand Down
4 changes: 2 additions & 2 deletions pom.xml
Expand Up @@ -152,11 +152,11 @@
<github.global.server>github</github.global.server>
<site.installationModule>google-cloud-core-parent</site.installationModule>

<gax.version>1.51.0</gax.version>
<gax.version>1.52.0</gax.version>
<google.api-common.version>1.8.1</google.api-common.version>
<google.common-protos.version>1.17.0</google.common-protos.version>
<google.iam.version>0.13.0</google.iam.version>
<google.auth.version>0.18.0</google.auth.version>
<google.auth.version>0.19.0</google.auth.version>
<google.api.version>1.30.6</google.api.version>
<google.http.version>1.33.0</google.http.version>
<grpc.version>1.25.0</grpc.version>
Expand Down