Skip to content

Commit 3a552a7

Browse files
authored
Add ConfigProvider SPI and K8s implementation to load ConfigMap resources (#80)
* Load configMap properties at start up * Add config provider to clean up dependencies * Address feedback - change service loader interface, removing system props, and load configs per driver * Move field expansion out of SPI
1 parent 5fbef15 commit 3a552a7

File tree

19 files changed

+163
-34
lines changed

19 files changed

+163
-34
lines changed

Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ clean:
1616
./gradlew clean
1717

1818
deploy-config:
19-
kubectl create configmap hoptimator-configmap --from-file=model.yaml=test-model.yaml --dry-run=client -o yaml | kubectl apply -f -
19+
kubectl apply -f ./deploy/config/hoptimator-configmap.yaml
2020

2121
undeploy-config:
2222
kubectl delete configmap hoptimator-configmap || echo "skipping"
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: v1
2+
kind: ConfigMap
3+
metadata:
4+
name: hoptimator-configmap
5+
data:
6+
# https://kubernetes.io/docs/concepts/configuration/configmap/
7+
8+
# property-like keys; each key maps to a simple value
9+
player_initial_lives: "3"
10+
ui_properties_file_name: "user-interface.properties"
11+
12+
# file-like keys
13+
game.properties: |
14+
enemy.types=aliens,monsters
15+
player.maximum-lives=5
16+
user-interface.properties: |
17+
color.good=purple
18+
color.bad=yellow
19+
allow.textmode=true
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.linkedin.hoptimator;
2+
3+
import java.util.Properties;
4+
5+
public interface ConfigProvider {
6+
7+
Properties loadConfig() throws Exception;
8+
}

hoptimator-cli/build.gradle

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ plugins {
66
}
77

88
dependencies {
9-
implementation project(':hoptimator-jdbc')
109
implementation project(':hoptimator-api')
1110
implementation project(':hoptimator-avro')
1211
implementation project(':hoptimator-demodb')

hoptimator-jdbc/build.gradle

+22-5
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,40 @@ dependencies {
1414
testFixturesImplementation libs.calcite.core
1515
testFixturesImplementation project(':hoptimator-api')
1616
testFixturesImplementation project(':hoptimator-util')
17+
testFixturesImplementation(platform('org.junit:junit-bom:5.11.3'))
18+
testFixturesImplementation 'org.junit.jupiter:junit-jupiter'
1719

1820
testRuntimeOnly project(':hoptimator-demodb')
19-
testFixturesImplementation(platform('org.junit:junit-bom:5.11.3'))
20-
testImplementation(platform('org.junit:junit-bom:5.11.3'))
21-
testImplementation 'org.junit.jupiter:junit-jupiter'
22-
testFixturesImplementation 'org.junit.jupiter:junit-jupiter'
21+
testImplementation(platform('org.junit:junit-bom:5.11.3'))
22+
testImplementation 'org.junit.jupiter:junit-jupiter'
2323
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
2424
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
2525
}
2626

2727
test {
28-
useJUnitPlatform()
28+
useJUnitPlatform {
29+
excludeTags 'integration'
30+
}
2931
testLogging {
3032
events "passed", "skipped", "failed"
3133
}
3234
}
3335

36+
tasks.register('intTest', Test) {
37+
description = 'Runs integration tests.'
38+
group = 'verification'
39+
40+
shouldRunAfter test
41+
42+
useJUnitPlatform {
43+
includeTags 'integration'
44+
}
45+
46+
testLogging {
47+
events "passed", "skipped", "failed"
48+
}
49+
}
50+
3451
java {
3552
sourceCompatibility = JavaVersion.VERSION_11
3653
targetCompatibility = JavaVersion.VERSION_11

hoptimator-jdbc/src/main/java/com/linkedin/hoptimator/jdbc/HoptimatorDriver.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
public class HoptimatorDriver extends Driver {
2424

2525
public HoptimatorDriver() {
26-
super(() -> new Prepare());
26+
super(Prepare::new);
2727
}
2828

2929
static {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.linkedin.hoptimator.jdbc;
2+
3+
import java.util.Properties;
4+
5+
import com.linkedin.hoptimator.ConfigProvider;
6+
7+
public class SystemPropertiesConfigProvider implements ConfigProvider {
8+
9+
public Properties loadConfig() {
10+
return System.getProperties();
11+
}
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.linkedin.hoptimator.jdbc.SystemPropertiesConfigProvider

hoptimator-k8s/src/main/java/com/linkedin/hoptimator/k8s/K8sApiEndpoints.java

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.linkedin.hoptimator.k8s;
22

3+
import io.kubernetes.client.openapi.models.V1ConfigMap;
4+
import io.kubernetes.client.openapi.models.V1ConfigMapList;
35
import io.kubernetes.client.openapi.models.V1Namespace;
46
import io.kubernetes.client.openapi.models.V1NamespaceList;
57
import io.kubernetes.client.openapi.models.V1Secret;
@@ -24,6 +26,9 @@ public final class K8sApiEndpoints {
2426
new K8sApiEndpoint<>("Namespace", "", "v1", "namespaces", true, V1Namespace.class, V1NamespaceList.class);
2527
public static final K8sApiEndpoint<V1Secret, V1SecretList> SECRETS =
2628
new K8sApiEndpoint<>("Secret", "", "v1", "secrets", false, V1Secret.class, V1SecretList.class);
29+
public static final K8sApiEndpoint<V1ConfigMap, V1ConfigMapList> CONFIG_MAPS =
30+
new K8sApiEndpoint<>("ConfigMap", "", "v1", "configmaps", false,
31+
V1ConfigMap.class, V1ConfigMapList.class);
2732

2833
// Hoptimator custom resources
2934
public static final K8sApiEndpoint<V1alpha1Database, V1alpha1DatabaseList> DATABASES =
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.linkedin.hoptimator.k8s;
2+
3+
import java.sql.SQLException;
4+
import java.util.Map;
5+
import java.util.Properties;
6+
7+
import io.kubernetes.client.openapi.models.V1ConfigMap;
8+
import io.kubernetes.client.openapi.models.V1ConfigMapList;
9+
10+
import com.linkedin.hoptimator.ConfigProvider;
11+
12+
13+
public class K8sConfigProvider implements ConfigProvider {
14+
15+
public static final String HOPTIMATOR_CONFIG_MAP = "hoptimator-configmap";
16+
17+
public Properties loadConfig() throws SQLException {
18+
Map<String, String> topLevelConfigs = loadTopLevelConfig(HOPTIMATOR_CONFIG_MAP);
19+
Properties p = new Properties();
20+
p.putAll(topLevelConfigs);
21+
return p;
22+
}
23+
24+
// Load top-level config map properties
25+
private Map<String, String> loadTopLevelConfig(String configMapName) throws SQLException {
26+
K8sApi<V1ConfigMap, V1ConfigMapList> configMapApi = new K8sApi<>(K8sContext.currentContext(), K8sApiEndpoints.CONFIG_MAPS);
27+
return configMapApi.get(configMapName).getData();
28+
}
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
com.linkedin.hoptimator.k8s.K8sConfigProvider

hoptimator-kafka/build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ plugins {
33
}
44

55
dependencies {
6+
implementation project(':hoptimator-util')
67
implementation libs.calcite.core
78
implementation libs.kafka.clients
89

@@ -15,8 +16,8 @@ dependencies {
1516
testRuntimeOnly project(':hoptimator-k8s')
1617
testRuntimeOnly project(':hoptimator-kafka')
1718
testImplementation(testFixtures(project(':hoptimator-jdbc')))
18-
testImplementation(platform('org.junit:junit-bom:5.11.3'))
19-
testImplementation 'org.junit.jupiter:junit-jupiter'
19+
testImplementation(platform('org.junit:junit-bom:5.11.3'))
20+
testImplementation 'org.junit.jupiter:junit-jupiter'
2021
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
2122
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
2223
}

hoptimator-kafka/src/main/java/com/linkedin/hoptimator/kafka/KafkaDriver.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import org.apache.calcite.jdbc.Driver;
1212
import org.apache.calcite.schema.SchemaPlus;
1313

14+
import com.linkedin.hoptimator.util.ConfigService;
15+
1416

1517
/** JDBC driver for Kafka topics. */
1618
public class KafkaDriver extends Driver {
@@ -34,7 +36,9 @@ public Connection connect(String url, Properties props) throws SQLException {
3436
if (!url.startsWith(getConnectStringPrefix())) {
3537
return null;
3638
}
37-
Properties properties = ConnectStringParser.parse(url.substring(getConnectStringPrefix().length()));
39+
// Connection string properties are given precedence over config properties
40+
Properties properties = ConfigService.config();
41+
properties.putAll(ConnectStringParser.parse(url.substring(getConnectStringPrefix().length())));
3842
try {
3943
Connection connection = super.connect(url, props);
4044
if (connection == null) {

hoptimator-util/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ dependencies {
88
implementation libs.calcite.core
99

1010
testImplementation(testFixtures(project(':hoptimator-jdbc')))
11-
testImplementation(platform('org.junit:junit-bom:5.11.3'))
12-
testImplementation 'org.junit.jupiter:junit-jupiter'
11+
testImplementation(platform('org.junit:junit-bom:5.11.3'))
12+
testImplementation 'org.junit.jupiter:junit-jupiter'
1313
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
1414
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
1515
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.linkedin.hoptimator.util;
2+
3+
import java.io.StringReader;
4+
import java.util.Properties;
5+
import java.util.ServiceLoader;
6+
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
10+
import com.linkedin.hoptimator.ConfigProvider;
11+
12+
public final class ConfigService {
13+
14+
private static final Logger log = LoggerFactory.getLogger(ConfigService.class);
15+
16+
private ConfigService() {
17+
}
18+
19+
// Loads top level configs and expands input fields as file-like properties
20+
// Ex:
21+
// log.properties: |
22+
// level=INFO
23+
public static Properties config(String... expansionFields) {
24+
ServiceLoader<ConfigProvider> loader = ServiceLoader.load(ConfigProvider.class);
25+
Properties properties = new Properties();
26+
for (ConfigProvider provider : loader) {
27+
try {
28+
Properties loadedProperties = provider.loadConfig();
29+
log.debug("Loaded properties={} from provider={}", loadedProperties, provider);
30+
properties.putAll(loadedProperties);
31+
for (String expansionField : expansionFields) {
32+
if (loadedProperties == null || !loadedProperties.containsKey(expansionField)) {
33+
log.warn("provider={} does not contain field={}", provider, expansionField);
34+
continue;
35+
}
36+
properties.load(new StringReader(loadedProperties.getProperty(expansionField)));
37+
}
38+
} catch (Exception e) {
39+
log.warn("Could not load properties for provider={}", provider, e);
40+
}
41+
}
42+
return properties;
43+
}
44+
}

hoptimator-util/src/main/java/com/linkedin/hoptimator/util/DataTypeUtils.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import java.util.Collections;
44
import java.util.LinkedHashMap;
55
import java.util.List;
6+
import java.util.Map;
67
import java.util.stream.Collectors;
78
import java.util.stream.Stream;
89

@@ -80,7 +81,7 @@ private static RelDataType buildRecord(Node node, RelDataTypeFactory typeFactory
8081
return node.dataType;
8182
}
8283
RelDataTypeFactory.Builder builder = new RelDataTypeFactory.Builder(typeFactory);
83-
for (LinkedHashMap.Entry<String, Node> child : node.children.entrySet()) {
84+
for (Map.Entry<String, Node> child : node.children.entrySet()) {
8485
builder.add(child.getKey(), buildRecord(child.getValue(), typeFactory));
8586
}
8687
return builder.build();

hoptimator-venice/build.gradle

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ dependencies {
2121

2222
testRuntimeOnly project(':hoptimator-k8s')
2323
testImplementation(testFixtures(project(':hoptimator-jdbc')))
24-
testImplementation(platform('org.junit:junit-bom:5.11.3'))
25-
testImplementation 'org.junit.jupiter:junit-jupiter'
24+
testImplementation(platform('org.junit:junit-bom:5.11.3'))
25+
testImplementation 'org.junit.jupiter:junit-jupiter'
2626
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
2727
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
2828
}

hoptimator-venice/src/main/java/com/linkedin/hoptimator/venice/VeniceDriver.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import org.apache.calcite.jdbc.Driver;
1313
import org.apache.calcite.schema.SchemaPlus;
1414

15+
import com.linkedin.hoptimator.util.ConfigService;
16+
1517

1618
/** JDBC driver for Venice stores. */
1719
public class VeniceDriver extends Driver {
18-
1920
public static final String CATALOG_NAME = "VENICE";
21+
public static final String CONFIG_NAME = "venice.config";
2022

2123
static {
2224
new VeniceDriver().register();
@@ -37,7 +39,9 @@ public Connection connect(String url, Properties props) throws SQLException {
3739
if (!url.startsWith(getConnectStringPrefix())) {
3840
return null;
3941
}
40-
Properties properties = ConnectStringParser.parse(url.substring(getConnectStringPrefix().length()));
42+
// Connection string properties are given precedence over config properties
43+
Properties properties = ConfigService.config(CONFIG_NAME);
44+
properties.putAll(ConnectStringParser.parse(url.substring(getConnectStringPrefix().length())));
4145
String cluster = properties.getProperty("cluster");
4246
if (cluster == null) {
4347
throw new IllegalArgumentException("Missing required cluster property. Need: jdbc:venice://cluster=...");

test-model.yaml

-16
This file was deleted.

0 commit comments

Comments
 (0)