Skip to content

Commit

Permalink
Cloud API and Config (#2081)
Browse files Browse the repository at this point in the history
  • Loading branch information
meiao authored Oct 29, 2024
1 parent ee76038 commit ec234dd
Show file tree
Hide file tree
Showing 19 changed files with 593 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public final class AgentBridge {

public static volatile AsyncApi asyncApi = new NoOpAsyncApi();

public static volatile CloudApi cloud = NoOpCloud.INSTANCE;

public static volatile CollectionFactory collectionFactory = new DefaultCollectionFactory();

/**
Expand Down
31 changes: 31 additions & 0 deletions agent-bridge/src/main/java/com/newrelic/agent/bridge/CloudApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.bridge;

import com.newrelic.api.agent.Cloud;
import com.newrelic.api.agent.CloudAccountInfo;

/**
* Internal Cloud API. This extends the public Cloud API and adds methods
* for retrieving the data set by the public API methods.
*/
public interface CloudApi extends Cloud {

/**
* Return the general account information of the provided type.
* This data is either set by {@link Cloud#setAccountInfo(CloudAccountInfo, String)}
* or the agent config.
*/
String getAccountInfo(CloudAccountInfo cloudAccountInfo);

/**
* Retrieves the account information for a cloud service SDK client.
* If no data was recorded for the SDK client, the general account information will be returned.
*/
String getAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.newrelic.agent.bridge;

import com.newrelic.api.agent.AiMonitoring;
import com.newrelic.api.agent.Cloud;
import com.newrelic.api.agent.Config;
import com.newrelic.api.agent.ErrorApi;
import com.newrelic.api.agent.Insights;
Expand Down Expand Up @@ -73,6 +74,11 @@ public AiMonitoring getAiMonitoring() {
return NoOpAiMonitoring.INSTANCE;
}

@Override
public Cloud getCloud() {
return NoOpCloud.INSTANCE;
}

@Override
public ErrorApi getErrorApi() {
return NoOpErrorApi.INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.bridge;

import com.newrelic.api.agent.CloudAccountInfo;

public class NoOpCloud implements CloudApi {

public static final CloudApi INSTANCE = new NoOpCloud();

private NoOpCloud() {
// only instance should be the INSTANCE
}

@Override
public void setAccountInfo(CloudAccountInfo cloudAccountInfo, String value) {
}

@Override
public void setAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo, String value) {
}

@Override
public String getAccountInfo(CloudAccountInfo cloudAccountInfo) {
return null;
}

@Override
public String getAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo) {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import com.newrelic.agent.bridge.TracedMethod;
import com.newrelic.agent.bridge.Transaction;
import com.newrelic.api.agent.AiMonitoring;
import com.newrelic.api.agent.Cloud;
import com.newrelic.api.agent.Config;
import com.newrelic.api.agent.ErrorApi;
import com.newrelic.api.agent.Insights;
Expand Down Expand Up @@ -44,6 +45,11 @@ public AiMonitoring getAiMonitoring() {
return null;
}

@Override
public Cloud getCloud() {
return null;
}

@Override
public ErrorApi getErrorApi() { throw new RuntimeException(); }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import com.newrelic.agent.service.ServiceFactory;
import com.newrelic.agent.tracers.Tracer;
import com.newrelic.api.agent.AiMonitoring;
import com.newrelic.api.agent.Cloud;
import com.newrelic.api.agent.ErrorApi;
import com.newrelic.api.agent.Insights;
import com.newrelic.api.agent.Logger;
Expand Down Expand Up @@ -144,6 +145,11 @@ public AiMonitoring getAiMonitoring() {
return new AiMonitoringImpl();
}

@Override
public Cloud getCloud() {
return AgentBridge.cloud;
}

@Override
public Logs getLogSender() {
return ServiceFactory.getServiceManager().getLogSenderService();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,11 @@ public class MetricNames {
public static final String SUPPORTABILITY_API_SET_ACCOUNT_NAME = "SetAccountName";
public static final String SUPPORTABILITY_API_SET_USER_ID = "SetUserId";

// Cloud API
public static final String SUPPORTABILITY_API_CLOUD_SET_ACCOUNT_INFO_CLIENT = "Cloud/SetAccountInfoClient/";
public static final String SUPPORTABILITY_API_CLOUD_SET_ACCOUNT_INFO = "Cloud/SetAccountInfo/";
public static final String SUPPORTABILITY_CONFIG_AWS_ACCOUNT_ID = "Supportability/Cloud/ConfigAccountInfo/aws_account_id";

//Transaction supportability metrics
public static final String SUPPORTABILITY_TRANSACTION_STARTED = "Supportability/Transaction/StartedCount";
public static final String SUPPORTABILITY_TRANSACTION_FINISHED = "Supportability/Transaction/FinishedCount";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.cloud;

import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.newrelic.agent.MetricNames;
import com.newrelic.agent.config.AgentConfig;
import com.newrelic.agent.service.ServiceFactory;
import com.newrelic.api.agent.CloudAccountInfo;
import com.newrelic.api.agent.NewRelic;

import java.util.Collections;
import java.util.EnumMap;
import java.util.Map;
import java.util.logging.Level;

/**
* This class implements the account info methods from the Cloud API.
*/
public class CloudAccountInfoCache {
private final LoadingCache<Object, Map<CloudAccountInfo, String>> cache;
// this object is used to store data that is not related to a specific sdk client
private static final Object NULL_CLIENT = new Object();

CloudAccountInfoCache() {
cache = Caffeine.newBuilder()
.initialCapacity(4)
.weakKeys()
.executor(Runnable::run)
.build((key) -> Collections.synchronizedMap(new EnumMap<>(CloudAccountInfo.class)));
}

public void setAccountInfo(CloudAccountInfo cloudAccountInfo, String value) {
setAccountInfo(NULL_CLIENT, cloudAccountInfo, value);
}

public void setAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo, String value) {
if (sdkClient == null) {
return;
}
if (value == null) {
Map<CloudAccountInfo, String> accountInfo = cache.getIfPresent(sdkClient);
if (accountInfo != null) {
accountInfo.remove(cloudAccountInfo);
}
return;
}
if (CloudAccountInfoValidator.validate(cloudAccountInfo, value)) {
Map<CloudAccountInfo, String> accountInfo = cache.get(sdkClient);
accountInfo.put(cloudAccountInfo, value);
}
}

public String getAccountInfo(CloudAccountInfo cloudAccountInfo) {
Map<CloudAccountInfo, String> accountInfo = cache.getIfPresent(NULL_CLIENT);
if (accountInfo == null) {
return null;
}
return accountInfo.get(cloudAccountInfo);
}

public String getAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo) {
if (sdkClient == null) {
return getAccountInfo(cloudAccountInfo);
}
Map<CloudAccountInfo, String> accountInfo = cache.getIfPresent(sdkClient);
if (accountInfo == null) {
return getAccountInfo(cloudAccountInfo);
}
return accountInfo.get(cloudAccountInfo);
}

void retrieveDataFromConfig() {
AgentConfig agentConfig = ServiceFactory.getConfigService().getDefaultAgentConfig();
retrieveAwsAccountId(agentConfig);
}

private void retrieveAwsAccountId(AgentConfig agentConfig) {
Object awsAccountId = agentConfig.getValue("cloud.aws.account_id");
if (awsAccountId == null) {
return;
}

NewRelic.getAgent().getLogger().log(Level.INFO, "Found AWS account ID configuration.");
NewRelic.incrementCounter(MetricNames.SUPPORTABILITY_CONFIG_AWS_ACCOUNT_ID);
setAccountInfo(CloudAccountInfo.AWS_ACCOUNT_ID, awsAccountId.toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.cloud;

import com.newrelic.api.agent.CloudAccountInfo;
import com.newrelic.api.agent.NewRelic;

import java.util.logging.Level;
import java.util.regex.Pattern;

public class CloudAccountInfoValidator {

private static final Pattern AWS_ACCOUNT_ID_PATTERN = Pattern.compile("^\\d+$");
private static Level awsAccountIdLogLevel = Level.WARNING;

public static boolean validate(CloudAccountInfo cloudAccountInfo, String value) {
switch (cloudAccountInfo) {
case AWS_ACCOUNT_ID:
return validateAwsAccountId(value);
default:
return false;
}
}

private static boolean validateAwsAccountId(String accountId) {
final int AWS_ACCOUNT_ID_LENGTH = 12;
if (accountId == null) {
return false;
}
boolean valid = accountId.length() == AWS_ACCOUNT_ID_LENGTH &&
AWS_ACCOUNT_ID_PATTERN.matcher(accountId).matches();
if (!valid) {
NewRelic.getAgent().getLogger().log(awsAccountIdLogLevel, "AWS account ID should be a 12-digit number.");
awsAccountIdLogLevel = Level.FINEST;
}
return valid;
}

private CloudAccountInfoValidator() {
// prevents instantiation
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
*
* * Copyright 2024 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/

package com.newrelic.agent.cloud;

import com.newrelic.agent.MetricNames;
import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.CloudApi;
import com.newrelic.api.agent.CloudAccountInfo;

/**
* Facade for the Cloud API.
*/
public class CloudApiImpl implements CloudApi {

private final CloudAccountInfoCache accountInfoCache;

private CloudApiImpl() {
this(new CloudAccountInfoCache());
accountInfoCache.retrieveDataFromConfig();
}

// for testing
CloudApiImpl(CloudAccountInfoCache accountInfoCache) {
this.accountInfoCache = accountInfoCache;
}

// calling this method more than once will invalidate any Cloud API calls to set account info
public static void initialize() {
AgentBridge.cloud = new CloudApiImpl();
}

@Override
public void setAccountInfo(CloudAccountInfo cloudAccountInfo, String value) {
MetricNames.recordApiSupportabilityMetric(MetricNames.SUPPORTABILITY_API_CLOUD_SET_ACCOUNT_INFO + cloudAccountInfo.toString());
accountInfoCache.setAccountInfo(cloudAccountInfo, value);
}

@Override
public void setAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo, String value) {
MetricNames.recordApiSupportabilityMetric(MetricNames.SUPPORTABILITY_API_CLOUD_SET_ACCOUNT_INFO_CLIENT + cloudAccountInfo.toString());
accountInfoCache.setAccountInfo(sdkClient, cloudAccountInfo, value);
}

@Override
public String getAccountInfo(CloudAccountInfo cloudAccountInfo) {
// not recording metrics because this is for the internal API
return accountInfoCache.getAccountInfo(cloudAccountInfo);
}

@Override
public String getAccountInfo(Object sdkClient, CloudAccountInfo cloudAccountInfo) {
// not recording metrics because this is for the internal API
return accountInfoCache.getAccountInfo(sdkClient, cloudAccountInfo);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.newrelic.agent.MetricNames;
import com.newrelic.agent.PrivateApiImpl;
import com.newrelic.agent.TransactionService;
import com.newrelic.agent.cloud.CloudApiImpl;
import com.newrelic.agent.config.AgentConfig;
import com.newrelic.agent.config.ConfigService;
import com.newrelic.agent.logging.AgentLogManager;
Expand Down Expand Up @@ -77,6 +78,7 @@ protected void doStart() {
private void initializeBridgeApis() {
NewRelicApiImplementation.initialize();
PrivateApiImpl.initialize(Agent.LOG);
CloudApiImpl.initialize();
}

/**
Expand Down
Loading

0 comments on commit ec234dd

Please sign in to comment.