Skip to content

Commit cf4416d

Browse files
adds multi-datasource support based on configuration which includes OCI Vault support and various fixes. (#19)
* add list of default metrics in readme * readme updates * various updates * README.md updates * README.md updates * basic example * adds... 1. ability to specify DATA_SOURCE_USER, DATA_SOURCE_PASSWORD, and DATA_SOURCE_SERVICENAME individually rather than as one DATA_SOURCE_NAME entry (thus more secure) 2. multi-database support via "/scrape" endpoint with "target" param taking dns for specific database (this will soon be enhanced so that it is instead configurable, again to be more secure) . Also, updated POM entries for 21.7 version. * adds multi-datasource support based on configuration which includes OCI Vault support and various fixes.
1 parent 47097b6 commit cf4416d

File tree

12 files changed

+284
-87
lines changed

12 files changed

+284
-87
lines changed

‎README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,30 @@ OAuth2 https://spring.io/guides/tutorials/spring-boot-oauth2/
9898

9999
The reader is referred to this material to configure security and other aspects as appropriate.
100100

101+
## Support for Multiple Databases
102+
103+
This feature can be used in two ways:
104+
105+
1. By accessing the `/scrape` endpoint and passing a `target` parameter that is the full dsn value.
106+
For example: `http://localhost:9161/scrape?target`
107+
2. By accessing the `/scrape` endpoint and passing a `name` parameter that is the name of the datasource
108+
as defined in the yaml file located at the environment variable `MULTI_DATASOURCE_CONFIG`
109+
The yaml file takes the following form and an example can be found under examples/multidatasource_config.yaml :
110+
```
111+
dataSourceName :
112+
serviceName :
113+
userName :
114+
password :
115+
TNS_ADMIN :
116+
# if present, OCI Vault is used for password rather than "password" attribute
117+
passwordOCID :
118+
# the following is applicable only if OCI config file is used rather than instance principals authentication (generally only the case in development)
119+
ociConfigFile :
120+
ociRegion :
121+
ociProfile :
122+
```
123+
The feature is currently only applicable to metrics, not tracing or logging.
124+
The feature currently uses the same global `DEFAULT_METRICS` config for every datasource.
101125

102126
[Metrics Exporter]: Metrics.md
103127
[Log Exporter]: Logs.md

‎examples/metrics/default-metrics.toml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,10 @@ context = "sessions"
33
labels = ["inst_id", "status", "type"]
44
metricsdesc = { value = "Gauge metric with count of sessions by status and type." }
55
request = "select inst_id, status, type, count(*) as value from gv$session group by status, type, inst_id"
6-
ignorezeroresult = true
6+
ignorezeroresult = true
7+
8+
[[metric]]
9+
context = "context_with_labels"
10+
labels = [ "label_1", "label_2" ]
11+
request = "SELECT 1 as value_1, 2 as value_2, 'First label' as label_1, 'Second label' as label_2 FROM DUAL"
12+
metricsdesc = { value_1 = "Simple example returning always 1.", value_2 = "Same but returning always 2." }

‎examples/multidatasource_config.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
dataSourceMyFirstDB :
2+
serviceName : myservicename1
3+
userName : myuser1
4+
password : mypassword1
5+
TNS_ADMIN : /somefolder/Wallet_somewallet1
6+
# if present, OCI Vault is used for password rather than "password" attribute
7+
# passwordOCID :
8+
# the following is applicable only if OCI config file is used rather than instance principals authentication (generally only the case in development)
9+
# ociConfigFile :
10+
# ociRegion :
11+
# ociProfile :
12+
dataSourceMySecondDB :
13+
serviceName : myservicename1
14+
userName : myuser2
15+
password : mypassword2
16+
TNS_ADMIN : /somefolder/Wallet_somewallet2
17+
# # if present, OCI Vault is used for password rather than "password" attribute
18+
# passwordOCID :
19+
# the following is applicable only if OCI config file is used rather than instance principals authentication (generally only the case in development)
20+
# ociConfigFile :
21+
# ociRegion :
22+
# ociProfile :

‎pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,10 @@
127127
<artifactId>oci-java-sdk-secrets</artifactId>
128128
<version>1.32.2</version>
129129
</dependency>
130+
<dependency>
131+
<groupId>org.projectlombok</groupId>
132+
<artifactId>lombok</artifactId>
133+
</dependency>
130134
</dependencies>
131135

132136
<build>
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package oracle.observability;
2+
3+
import lombok.Data;
4+
5+
@Data
6+
public class DataSourceConfig {
7+
private String dataSourceName;
8+
private String serviceName;
9+
private String userName;
10+
private String password;
11+
private String TNS_ADMIN;
12+
private String passwordOCID;
13+
private String ociConfigFile;
14+
private String ociRegion;
15+
private String ociProfile;
16+
}

‎src/main/java/oracle/observability/ObservabilityExporter.java

Lines changed: 79 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@
66
import com.oracle.bmc.secrets.model.Base64SecretBundleContentDetails;
77
import com.oracle.bmc.secrets.requests.GetSecretBundleRequest;
88
import com.oracle.bmc.secrets.responses.GetSecretBundleResponse;
9-
import oracle.observability.metrics.MetricsExporter;
109
import oracle.ucp.jdbc.PoolDataSource;
1110
import oracle.ucp.jdbc.PoolDataSourceFactory;
1211
import org.apache.commons.codec.binary.Base64;
1312
import org.slf4j.Logger;
1413
import org.slf4j.LoggerFactory;
1514

16-
import java.io.File;
17-
import java.io.IOException;
15+
import java.io.*;
1816
import java.sql.SQLException;
1917
import java.util.HashMap;
2018
import java.util.Map;
@@ -26,61 +24,110 @@ public class ObservabilityExporter {
2624
public File DEFAULT_METRICS_FILE;
2725
public String CUSTOM_METRICS = System.getenv("CUSTOM_METRICS"); //
2826
public String QUERY_TIMEOUT = System.getenv("QUERY_TIMEOUT"); // "5"
27+
public static final String CONTEXT = "context";
28+
public static final String REQUEST = "request";
29+
30+
//Single/global datasource config related....
2931
public String DATABASE_MAXIDLECONNS = System.getenv("DATABASE_MAXIDLECONNS"); // "0"
3032
public String DATABASE_MAXOPENCONNS = System.getenv("DATABASE_MAXOPENCONNS"); // "10"
3133
public static String DATA_SOURCE_NAME = System.getenv("DATA_SOURCE_NAME"); //eg %USER%/$(dbpassword)@%PDB_NAME%_tp
32-
public static String DATA_SOURCE_USER = System.getenv("DATA_SOURCE_USER"); //eg %USER%/$(dbpassword)@%PDB_NAME%_tp
33-
public static String DATA_SOURCE_PASSWORD = System.getenv("DATA_SOURCE_PASSWORD"); //eg %USER%/$(dbpassword)@%PDB_NAME%_tp
34-
public static String DATA_SOURCE_SERVICENAME = System.getenv("DATA_SOURCE_SERVICENAME"); //eg %USER%/$(dbpassword)@%PDB_NAME%_tp
35-
public String TNS_ADMIN = System.getenv("TNS_ADMIN"); //eg /msdataworkshop/creds
36-
public String OCI_REGION = System.getenv("OCI_REGION"); //eg us-ashburn-1
37-
public String VAULT_SECRET_OCID = System.getenv("VAULT_SECRET_OCID"); //eg ocid....
38-
public String OCI_CONFIG_FILE = System.getenv("OCI_CONFIG_FILE"); //eg "~/.oci/config"
39-
public String OCI_PROFILE = System.getenv("OCI_PROFILE"); //eg "DEFAULT"
40-
public static final String CONTEXT = "context";
41-
public static final String REQUEST = "request";
34+
//if all three of the following exist, they are internally concatenated and override/used as DATA_SOURCE_NAME
35+
public static String DATA_SOURCE_USER = System.getenv("DATA_SOURCE_USER"); //eg %USER%
36+
public static String DATA_SOURCE_PASSWORD = System.getenv("DATA_SOURCE_PASSWORD"); //eg $(dbpassword)
37+
public static String DATA_SOURCE_SERVICENAME = System.getenv("DATA_SOURCE_SERVICENAME"); //eg %PDB_NAME%_tp
38+
public static String TNS_ADMIN = System.getenv("TNS_ADMIN"); //eg /msdataworkshop/creds
4239

43-
static {
40+
public static String OCI_REGION = System.getenv("OCI_REGION"); //eg us-ashburn-1
41+
public static String VAULT_SECRET_OCID = System.getenv("VAULT_SECRET_OCID"); //eg ocid....
42+
public static String OCI_CONFIG_FILE = System.getenv("OCI_CONFIG_FILE"); //eg "~/.oci/config"
43+
public static String OCI_PROFILE = System.getenv("OCI_PROFILE"); //eg "DEFAULT"
4444

45+
//MULTI_DATASOURCE_CONFIG related....
46+
public static String MULTI_DATASOURCE_CONFIG = System.getenv("MULTI_DATASOURCE_CONFIG");
47+
public static final String SERVICE_NAME_STRING = "serviceName";
48+
public static final String USER_NAME_STRING = "userName";
49+
public static final String PASSWORD_STRING = "password";
50+
public static final String TNS_ADMIN_STRING = "TNS_ADMIN";
51+
public static final String PASSWORD_OCID_STRING = "passwordOCID";
52+
public static final String OCI_CONFIG_FILE_STRING = "ociConfigFile";
53+
public static final String OCI_REGION_STRING = "ociRegion";
54+
public static final String OCI_PROFILE_STRING = "ociProfile";
55+
56+
static { // not really necessary but gives information that a global datasource is in use
4557
if (DATA_SOURCE_USER != null && DATA_SOURCE_PASSWORD != null && DATA_SOURCE_SERVICENAME != null) {
4658
DATA_SOURCE_NAME = DATA_SOURCE_USER + "/" + DATA_SOURCE_PASSWORD + "@" + DATA_SOURCE_SERVICENAME;
4759
LOGGER.info("DATA_SOURCE_NAME = DATA_SOURCE_USER + \"/\" + DATA_SOURCE_PASSWORD + \"@\" + DATA_SOURCE_SERVICENAME");
4860
//eg %USER%/$(dbpassword)@%PDB_NAME%_tp
4961
}
5062
}
51-
PoolDataSource observabilityDB;
63+
PoolDataSource globalObservabilityDB;
64+
65+
//This map is used for multi-datasource scraping, both when using dns target string and config
5266
Map<String, PoolDataSource> dataSourceNameToDataSourceMap = new HashMap<>();
5367

68+
//This map is used for multi-datasource scraping when using config only
69+
public static Map<String, DataSourceConfig> dataSourceNameToDataSourceConfigMap = new HashMap<>();
70+
71+
//used by logs and tracing exporters as they do not currently support multi-datasource config
5472
public PoolDataSource getPoolDataSource() throws SQLException {
55-
return getPoolDataSource(DATA_SOURCE_NAME);
73+
return getPoolDataSource(DATA_SOURCE_NAME, false);
5674
}
57-
public PoolDataSource getPoolDataSource(String dataSourceName) throws SQLException {
58-
if (dataSourceName.equals(DATA_SOURCE_NAME)) {
59-
if (observabilityDB != null) return observabilityDB;
60-
return observabilityDB = getDataSource(DATA_SOURCE_NAME);
75+
76+
public PoolDataSource getPoolDataSource(String dataSourceName, boolean isScrapeByName) throws SQLException {
77+
if (DATA_SOURCE_NAME != null && dataSourceName.equals(DATA_SOURCE_NAME)) {
78+
if (globalObservabilityDB != null) return globalObservabilityDB;
79+
return globalObservabilityDB = getDataSource(DATA_SOURCE_NAME);
6180
} else {
6281
if(dataSourceNameToDataSourceMap.containsKey(dataSourceName) && dataSourceNameToDataSourceMap.get(dataSourceName) != null)
6382
return dataSourceNameToDataSourceMap.get(dataSourceName);
64-
PoolDataSource poolDataSource = getDataSource(dataSourceName);
83+
84+
System.out.println("putting dataSourceName:" + dataSourceName + " isScrapeByName:" + isScrapeByName +
85+
" ObservabilityExporter.dataSourceNameToDataSourceConfigMap.get(dataSourceName):"+
86+
ObservabilityExporter.dataSourceNameToDataSourceConfigMap.get(dataSourceName));
87+
PoolDataSource poolDataSource = isScrapeByName?
88+
getDataSource(ObservabilityExporter.dataSourceNameToDataSourceConfigMap.get(dataSourceName))
89+
:getDataSource(dataSourceName);
6590
dataSourceNameToDataSourceMap.put(dataSourceName, poolDataSource);
6691
return poolDataSource;
6792
}
6893
}
6994

7095
private PoolDataSource getDataSource(String dataSourceName) throws SQLException {
71-
PoolDataSource poolDataSource = PoolDataSourceFactory.getPoolDataSource();
72-
poolDataSource.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
7396
String user = dataSourceName.substring(0, dataSourceName.indexOf("/"));
7497
String pw = dataSourceName.substring(dataSourceName.indexOf("/") + 1, dataSourceName.indexOf("@"));
7598
String serviceName = dataSourceName.substring(dataSourceName.indexOf("@") + 1);
76-
String url = "jdbc:oracle:thin:@" + serviceName + "?TNS_ADMIN=" + TNS_ADMIN;
99+
return getPoolDataSource(dataSourceName, user, pw, serviceName, TNS_ADMIN,
100+
VAULT_SECRET_OCID, OCI_CONFIG_FILE, OCI_PROFILE, OCI_REGION, false);
101+
}
102+
private PoolDataSource getDataSource(DataSourceConfig dataSourceConfig) throws SQLException {
103+
return getPoolDataSource(dataSourceConfig.getDataSourceName(),
104+
dataSourceConfig.getUserName(),
105+
dataSourceConfig.getPassword(),
106+
dataSourceConfig.getServiceName(),
107+
dataSourceConfig.getTNS_ADMIN(),
108+
dataSourceConfig.getPasswordOCID(),
109+
dataSourceConfig.getOciConfigFile(),
110+
dataSourceConfig.getOciProfile(),
111+
dataSourceConfig.getOciRegion(),
112+
true);
113+
}
114+
115+
private PoolDataSource getPoolDataSource(
116+
String dataSourceName, String user, String pw, String serviceName, String tnsAdmin,
117+
String vaultSecretOcid, String ociConfigFile, String ociProfile, String ociRegion, boolean isScrapeByName) throws SQLException {
118+
System.out.println("getPoolDataSource dataSourceName = " + dataSourceName + ", user = " + user + ", pw = " + pw + ", serviceName = " + serviceName + ", vaultSecretOcid = " + vaultSecretOcid + ", ociConfigFile = " + ociConfigFile + ", ociProfile = " + ociProfile + ", ociRegion = " + ociRegion + ", isScrapeByName = " + isScrapeByName);
119+
PoolDataSource poolDataSource = PoolDataSourceFactory.getPoolDataSource();
120+
poolDataSource.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
121+
String url = "jdbc:oracle:thin:@" + serviceName + "?TNS_ADMIN=" + tnsAdmin;
77122
poolDataSource.setURL(url);
78123
poolDataSource.setUser(user);
79-
if (VAULT_SECRET_OCID == null || VAULT_SECRET_OCID.trim().equals("") || !dataSourceName.equals(DATA_SOURCE_NAME)) {
124+
if (VAULT_SECRET_OCID == null || VAULT_SECRET_OCID.trim().equals("") ||
125+
//vault is not supported with scrape by dns currently, only with scrape by datasource name and global datasource
126+
(!isScrapeByName && !dataSourceName.equals(DATA_SOURCE_NAME)) ) {
80127
poolDataSource.setPassword(pw);
81128
} else {
82129
try {
83-
poolDataSource.setPassword(getPasswordFromVault());
130+
poolDataSource.setPassword(getPasswordFromVault(vaultSecretOcid, ociConfigFile, ociProfile, ociRegion));
84131
} catch (IOException e) {
85132
throw new SQLException(e);
86133
}
@@ -89,18 +136,18 @@ private PoolDataSource getDataSource(String dataSourceName) throws SQLException
89136
}
90137

91138

92-
public String getPasswordFromVault() throws IOException {
139+
public String getPasswordFromVault(String vaultSecretOcid, String ociConfigFile, String ociProfile, String ociRegion) throws IOException {
93140
SecretsClient secretsClient;
94-
if (OCI_CONFIG_FILE == null || OCI_CONFIG_FILE.trim().equals("")) {
141+
if (ociConfigFile == null || ociConfigFile.trim().equals("")) {
95142
secretsClient = new SecretsClient(InstancePrincipalsAuthenticationDetailsProvider.builder().build());
96143
} else {
97-
String profile = OCI_PROFILE==null || OCI_PROFILE.trim().equals("") ? "DEFAULT": OCI_PROFILE;
98-
secretsClient = new SecretsClient(new ConfigFileAuthenticationDetailsProvider(OCI_CONFIG_FILE, profile));
144+
String profile = ociProfile ==null || ociProfile.trim().equals("") ? "DEFAULT": ociProfile;
145+
secretsClient = new SecretsClient(new ConfigFileAuthenticationDetailsProvider(ociConfigFile, profile));
99146
}
100-
secretsClient.setRegion(OCI_REGION);
147+
secretsClient.setRegion(ociRegion);
101148
GetSecretBundleRequest getSecretBundleRequest = GetSecretBundleRequest
102149
.builder()
103-
.secretId(VAULT_SECRET_OCID)
150+
.secretId(vaultSecretOcid )
104151
.stage(GetSecretBundleRequest.Stage.Current)
105152
.build();
106153
GetSecretBundleResponse getSecretBundleResponse = secretsClient.getSecretBundle(getSecretBundleRequest);

‎src/main/java/oracle/observability/logs/LogsExporter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public void run() {
4343
LOGGER.debug("LogsExporter default metrics from:" + DEFAULT_METRICS);
4444
if(LOG_INTERVAL!=null && !LOG_INTERVAL.trim().equals("")) logInterval = Integer.getInteger(LOG_INTERVAL);
4545
LOGGER.debug("LogsExporter logInterval:" + logInterval);
46+
//todo move to common/ObservabilityExporter location and log something friendly if it does not exist and exit, ie fast fail startup
4647
File tomlfile = new File(DEFAULT_METRICS);
4748
TomlMapper mapper = new TomlMapper();
4849
JsonNode jsonNode = mapper.readerFor(LogsExporterConfigEntry.class).readTree(new FileInputStream(tomlfile));
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package oracle.observability.metrics;
2+
3+
import io.prometheus.client.CollectorRegistry;
4+
import io.prometheus.client.Gauge;
5+
6+
import java.util.HashMap;
7+
import java.util.Map;
8+
9+
public class CollectorRegistryWithGaugeMap extends CollectorRegistry {
10+
Map<String, Gauge> gaugeMap = new HashMap<>();
11+
12+
}

0 commit comments

Comments
 (0)