├── common
├── src
│ └── main
│ │ └── java
│ │ └── io
│ │ └── github
│ │ └── slvwolf
│ │ ├── ConfigUpdate.java
│ │ ├── UnknownConfigException.java
│ │ ├── Counter.java
│ │ ├── SchemaItem.java
│ │ └── CCClient.java
└── pom.xml
├── .gitignore
├── README.md
├── all
├── src
│ └── main
│ │ └── java
│ │ └── io
│ │ └── github
│ │ └── slvwolf
│ │ └── CCentral.java
└── pom.xml
├── LICENSE
├── .github
└── workflows
│ └── codeql-analysis.yml
├── etcd
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── io
│ │ └── github
│ │ └── slvwolf
│ │ ├── EtcdAccess.java
│ │ └── CCEtcdClient.java
│ └── test
│ └── java
│ └── io
│ └── github
│ └── slvwolf
│ └── CCentralTest.java
└── pom.xml
/common/src/main/java/io/github/slvwolf/ConfigUpdate.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | /**
4 | * Functional interface for listening configuration updates
5 | */
6 | public interface ConfigUpdate {
7 | void valueChanged(String configKey);
8 | }
9 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/slvwolf/UnknownConfigException.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | public class UnknownConfigException extends Exception {
4 | public UnknownConfigException(String message) {
5 | super(message);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target
2 | .idea
3 | .DS_Store
4 |
5 | *.iml
6 |
7 | *.class
8 |
9 | # Mobile Tools for Java (J2ME)
10 | .mtj.tmp/
11 |
12 | # Package Files #
13 | *.jar
14 | *.war
15 | *.ear
16 |
17 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
18 | hs_err_pid*
19 | *.classpath
20 | *.project
21 | *.settings
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CCentral
2 | Java client library for CCentral (Simple centralized configuration management and real-time monitoring for services).
3 | Server implementation can be found from [here](https://github.com/slvwolf/ccentral).
4 |
5 | ## Packages
6 |
7 | - `ccentral-all` - Everything included
8 | - `ccentral-common` - Basic interfaces, no connector implementations
9 | - `ccentral-etcd` - Etcd CCentral connector
--------------------------------------------------------------------------------
/all/src/main/java/io/github/slvwolf/CCentral.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | import mousio.etcd4j.EtcdClient;
4 |
5 | import java.net.URI;
6 |
7 | public class CCentral {
8 |
9 | public static CCClient initWithEtcdClient(String serviceId, EtcdClient client) {
10 | // Client ID will be injected by the CCEtcdClient
11 | return new CCEtcdClient(new EtcdAccess(client, serviceId, ""));
12 | }
13 |
14 | public static CCClient initWithEtcdHost(String serviceId, URI[] hosts) {
15 | return new CCEtcdClient(serviceId, hosts);
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/slvwolf/Counter.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | class Counter {
4 | private int last;
5 | private int current;
6 | private long ts;
7 |
8 | public Counter() {
9 | last = 0;
10 | current = 0;
11 | ts = System.currentTimeMillis();
12 | }
13 |
14 | private void refresh() {
15 | while (ts + 60_000 < System.currentTimeMillis()) {
16 | last = current;
17 | current = 0;
18 | ts = ts + 60_000;
19 | }
20 | }
21 |
22 | public void increment(int amount) {
23 | refresh();
24 | current += amount;
25 | }
26 |
27 | public void set(int amount) {
28 | refresh();
29 | current = amount;
30 | }
31 |
32 | public int getValue() {
33 | refresh();
34 | return last;
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Santtu Järvi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/all/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | ccentral-parent
7 | io.github.slvwolf
8 | 0.5.1
9 |
10 | 4.0.0
11 |
12 | ccentral-all
13 | CCentral - All
14 |
15 | jar
16 |
17 |
18 |
19 |
20 | io.github.slvwolf
21 | ccentral-common
22 | 0.5.1
23 | compile
24 |
25 |
26 | io.github.slvwolf
27 | ccentral-etcd
28 | 0.5.1
29 | compile
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/slvwolf/SchemaItem.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | import com.fasterxml.jackson.annotation.JsonIgnore;
4 | import com.fasterxml.jackson.annotation.JsonProperty;
5 |
6 | import java.util.LinkedList;
7 | import java.util.List;
8 |
9 | class SchemaItem {
10 | public String key;
11 | public String title;
12 | public String description;
13 | @JsonProperty(value = "default")
14 | public String defaultValue;
15 | public String type;
16 | @JsonIgnore
17 | public String configValue;
18 | @JsonIgnore
19 | private final List callbacks;
20 |
21 | public enum Type {
22 | STRING("string"),
23 | PASSWORD("password"),
24 | INTEGER("integer"),
25 | FLOAT("float"),
26 | LIST("list"),
27 | BOOLEAN("boolean");
28 | public final String value;
29 |
30 | Type(String type) {
31 | value = type;
32 | }
33 | }
34 |
35 | public SchemaItem(String key, String title, String description, String defaultValue, Type type) {
36 | this.key = key;
37 | this.title = title;
38 | this.description = description;
39 | this.defaultValue = defaultValue;
40 | this.type = type.value;
41 | configValue = null;
42 | callbacks = new LinkedList<>();
43 | }
44 |
45 | public void addCallback(ConfigUpdate func) {
46 | callbacks.add(func);
47 | }
48 |
49 | public List getCallbacks() {
50 | return callbacks;
51 | }
52 | }
--------------------------------------------------------------------------------
/common/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | ccentral-parent
7 | io.github.slvwolf
8 | 0.5.1
9 |
10 | 4.0.0
11 |
12 | ccentral-common
13 | CCentral - Common
14 |
15 | jar
16 |
17 |
18 |
19 |
20 | com.fasterxml.jackson.core
21 | jackson-core
22 | 2.11.2
23 |
24 |
25 |
26 |
27 | com.fasterxml.jackson.core
28 | jackson-annotations
29 | 2.11.2
30 |
31 |
32 |
33 |
34 | org.slf4j
35 | slf4j-api
36 | 1.7.30
37 |
38 |
39 |
40 |
41 | com.fasterxml.jackson.core
42 | jackson-databind
43 | 2.11.2
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ master ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ master ]
20 | schedule:
21 | - cron: '39 12 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'java' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v2
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v1
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main
52 |
53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
54 | # If this step fails, then you should remove it and run the build manually (see below)
55 | - name: Autobuild
56 | uses: github/codeql-action/autobuild@v1
57 |
58 | # ℹ️ Command-line programs to run using the OS shell.
59 | # 📚 https://git.io/JvXDl
60 |
61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
62 | # and modify them (or add more) to build your code if your project
63 | # uses a compiled language
64 |
65 | #- run: |
66 | # make bootstrap
67 | # make release
68 |
69 | - name: Perform CodeQL Analysis
70 | uses: github/codeql-action/analyze@v1
71 |
--------------------------------------------------------------------------------
/etcd/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 | ccentral-parent
7 | io.github.slvwolf
8 | 0.5.1
9 |
10 | 4.0.0
11 |
12 | ccentral-etcd
13 | CCentral - Etcd
14 |
15 | jar
16 |
17 |
18 |
19 | io.github.slvwolf
20 | ccentral-common
21 | 0.5.1
22 | compile
23 |
24 |
25 |
26 |
27 | org.mousio
28 | etcd4j
29 | 2.18.0
30 |
31 |
32 |
33 |
34 | io.netty
35 | netty-all
36 | 4.1.51.Final
37 |
38 |
39 |
40 |
41 | io.dropwizard.metrics
42 | metrics-core
43 | 4.1.11
44 |
45 |
46 |
47 |
48 |
49 |
50 | org.slf4j
51 | slf4j-simple
52 | 1.7.30
53 | test
54 |
55 |
56 |
57 | junit
58 | junit
59 | 4.13.1
60 | test
61 |
62 |
63 |
64 | org.mockito
65 | mockito-all
66 | 1.10.19
67 | test
68 |
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/etcd/src/main/java/io/github/slvwolf/EtcdAccess.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | import mousio.etcd4j.EtcdClient;
4 | import mousio.etcd4j.responses.EtcdAuthenticationException;
5 | import mousio.etcd4j.responses.EtcdException;
6 | import mousio.etcd4j.responses.EtcdKeysResponse;
7 |
8 | import java.io.IOException;
9 | import java.util.concurrent.TimeUnit;
10 | import java.util.concurrent.TimeoutException;
11 |
12 |
13 | /**
14 | * Simple wrapper for Etcd.
15 | */
16 | public class EtcdAccess {
17 |
18 | private static final String LOCATION_SERVICE_BASE = "/ccentral/services/%s";
19 | private static final String LOCATION_SCHEMA = LOCATION_SERVICE_BASE + "/schema";
20 | private static final String LOCATION_CONFIG = LOCATION_SERVICE_BASE + "/config";
21 | private static final String LOCATION_CLIENTS = LOCATION_SERVICE_BASE + "/clients/%s";
22 | private static final String LOCATION_SERVICE_INFO = LOCATION_SERVICE_BASE + "/info/%s";
23 | private static final int INSTANCE_TTL = 3 * 60;
24 | private static final int TTL_DAY = 26 * 60 * 60;
25 | private static final int TIMEOUT_SECONDS = 20;
26 | private final EtcdClient client;
27 | private final String serviceId;
28 | private String clientId;
29 |
30 | public EtcdAccess(EtcdClient client, String serviceId, String clientId) {
31 | this.client = client;
32 | this.serviceId = serviceId;
33 | this.clientId = clientId;
34 | }
35 |
36 | public void setClientId(String clientId) {
37 | this.clientId = clientId;
38 | }
39 |
40 | public EtcdClient getClient() {
41 | return client;
42 | }
43 |
44 | public void sendClientInfo(String json) throws IOException, EtcdAuthenticationException, TimeoutException, EtcdException {
45 | client.put(String.format(LOCATION_CLIENTS, serviceId, clientId), json)
46 | .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
47 | .ttl(INSTANCE_TTL)
48 | .send()
49 | .get();
50 | }
51 |
52 | public String fetchConfig() throws IOException, EtcdAuthenticationException, TimeoutException, EtcdException {
53 | EtcdKeysResponse response = client.get(String.format(LOCATION_CONFIG, serviceId))
54 | .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
55 | .send()
56 | .get();
57 | return response.node.value;
58 | }
59 |
60 | public void sendSchema(String schemaJson) throws IOException, EtcdAuthenticationException, TimeoutException, EtcdException {
61 | client.put(String.format(LOCATION_SCHEMA, serviceId), schemaJson)
62 | .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
63 | .send()
64 | .get();
65 | }
66 |
67 | public void sendServiceInfo(String key, String data) throws IOException, EtcdAuthenticationException, TimeoutException, EtcdException {
68 | client.put(String.format(LOCATION_SERVICE_INFO, serviceId, key), data)
69 | .timeout(TIMEOUT_SECONDS, TimeUnit.SECONDS)
70 | .ttl(TTL_DAY)
71 | .send()
72 | .get();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/common/src/main/java/io/github/slvwolf/CCClient.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | import java.util.List;
4 |
5 | public interface CCClient {
6 | /**
7 | * Get unique clientId.
8 | * @return Unique Id.
9 | */
10 | String getClientId();
11 |
12 | /**
13 | * Add a string configuration field.
14 | *
15 | * @param key Unique ket for configuration.
16 | * @param title (UI) Human readable title.
17 | * @param description (UI) Documentation about the configuration.
18 | * @param defaultValue Default value.
19 | */
20 | void addField(String key, String title, String description, String defaultValue);
21 |
22 | /**
23 | * Add a integer configuration field.
24 | *
25 | * @param key Unique ket for configuration.
26 | * @param title (UI) Human readable title.
27 | * @param description (UI) Documentation about the configuration.
28 | * @param defaultValue Default value.
29 | */
30 | void addIntField(String key, String title, String description, int defaultValue);
31 |
32 | /**
33 | * Add a float configuration field.
34 | *
35 | * @param key Unique ket for configuration.
36 | * @param title (UI) Human readable title.
37 | * @param description (UI) Documentation about the configuration.
38 | * @param defaultValue Default value.
39 | */
40 |
41 | void addFloatField(String key, String title, String description, float defaultValue);
42 |
43 | /**
44 | * Add a password configuration field. This field will have its value hidden in the UI. This does
45 | * not protect the password in any other way.
46 | *
47 | * @param key Unique ket for configuration.
48 | * @param title (UI) Human readable title.
49 | * @param description (UI) Documentation about the configuration.
50 | * @param defaultValue Default value.
51 | */
52 | void addPasswordField(String key, String title, String description, String defaultValue);
53 |
54 | /**
55 | * Add a list configuration field.
56 | *
57 | * @param key Unique ket for configuration.
58 | * @param title (UI) Human readable title.
59 | * @param description (UI) Documentation about the configuration.
60 | * @param defaultValue Default value.
61 | */
62 | void addListField(String key, String title, String description, List defaultValue);
63 |
64 | /**
65 | * Add a boolean configuration field.
66 | *
67 | * @param key Unique ket for configuration.
68 | * @param title (UI) Human readable title.
69 | * @param description (UI) Documentation about the configuration.
70 | * @param defaultValue Default value.
71 | */
72 | void addBooleanField(String key, String title, String description, boolean defaultValue);
73 |
74 | /**
75 | * Get list value from configuration.
76 | *
77 | * @param key Key for configuration.
78 | * @return value or null if not found.
79 | */
80 | List getConfigList(String key);
81 |
82 | /**
83 | * Get configuration.
84 | *
85 | * @param key Key for configuration.
86 | * @return value or null if not found.
87 | * @throws UnknownConfigException If configuration has not been defined on init.
88 | */
89 | String getConfig(String key) throws UnknownConfigException;
90 |
91 | /**
92 | * Get boolean value from configuration.
93 | *
94 | * @param key Key for configuration.
95 | * @return value or null if not found.
96 | */
97 | Boolean getConfigBool(String key);
98 |
99 | /**
100 | * Get int value from configuration.
101 | *
102 | * @param key Key for configuration.
103 | * @return value or null if not found.
104 | */
105 | Integer getConfigInt(String key);
106 |
107 | /**
108 | * Get float value from configuration
109 | *
110 | * @param key Key for configuration
111 | * @return value or null if not found
112 | */
113 | Float getConfigFloat(String key);
114 |
115 | /**
116 | * Get string value from configuration.
117 | *
118 | * @param key Key for configuration.
119 | * @return Value or null if not found.
120 | */
121 | String getConfigString(String key);
122 |
123 | void addInstanceInfo(String key, String data);
124 |
125 | void addServiceInfo(String key, String data);
126 |
127 | void refresh();
128 |
129 | /**
130 | * Increment instance counter
131 | * @param key Counter key
132 | * @param amount Amount to increment
133 | * @param groups Additional groups for this key
134 | */
135 | void incrementInstanceCounter(String key, int amount, String ...groups);
136 |
137 | /**
138 | * Increment instance counter. Groups are optional and will create additional dimensions for that metric.
139 | * @param key Counter key
140 | * @param groups Additional groups for this key
141 | */
142 | void incrementInstanceCounter(String key, String ...groups);
143 |
144 | /**
145 | * Reset instance counter to predefined value
146 | *
147 | * @param key Counter key
148 | * @param amount Value to set
149 | * @param groups Additional groups for this key
150 | */
151 | void setInstanceCounter(String key, int amount, String... groups);
152 |
153 | void addHistogram(String key, long timeInMilliseconds);
154 |
155 | String getApiVersion();
156 |
157 | /**
158 | * Use callback function when given configuration option changes. This can be handy option in cases where
159 | * configuration is usually set only once in init and needs new initialization when updated.
160 | *
161 | * @param configuration Key for configuration, has to be defined before called
162 | * @param func Called function
163 | * @throws UnknownConfigException Configuration item missing
164 | */
165 | void addCallback(String configuration, ConfigUpdate func) throws UnknownConfigException;
166 | }
167 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
4 | 4.0.0
5 |
6 | io.github.slvwolf
7 | ccentral-parent
8 |
9 | 0.5.2
10 |
11 | common
12 | etcd
13 | all
14 |
15 |
16 | pom
17 |
18 | CCentral
19 | Java client library for CCentral (Simple centralized configuration management and real-time monitoring
20 | for services).
21 |
22 | https://github.com/slvwolf/ccentral4j
23 |
24 |
25 |
26 | Santtu Järvi
27 | santtu.jarvi@finfur.net
28 |
29 |
30 |
31 |
32 |
33 | MIT License
34 | https://opensource.org/licenses/MIT
35 | repo
36 |
37 |
38 |
39 |
40 | https://github.com/slvwolf/ccentral4j
41 | scm:git:git@github.com:slvwolf/ccentral4j.git
42 | scm:git:git@github.com:slvwolf/ccentral4j.git
43 |
44 |
45 |
46 | 1.8
47 | UTF-8
48 | UTF-8
49 |
50 |
51 |
52 |
53 |
54 |
55 | org.apache.maven.plugins
56 | maven-compiler-plugin
57 | 3.1
58 |
59 | 1.8
60 | 1.8
61 |
62 |
63 |
64 |
65 | org.apache.maven.plugins
66 | maven-surefire-plugin
67 | 2.7
68 |
69 | false
70 |
71 |
72 |
73 |
74 | org.apache.maven.plugins
75 | maven-checkstyle-plugin
76 | 2.17
77 |
78 |
79 | validate
80 | validate
81 |
82 | google_checks.xml
83 | UTF-8
84 | true
85 | true
86 |
87 |
88 | check
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | release
100 |
101 |
102 | ossrh
103 | https://oss.sonatype.org/content/repositories/snapshots
104 |
105 |
106 | ossrh
107 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
108 |
109 |
110 |
111 |
112 |
113 | org.sonatype.plugins
114 | nexus-staging-maven-plugin
115 | 1.6.7
116 | true
117 |
118 | ossrh
119 | https://oss.sonatype.org/
120 | true
121 |
122 |
123 |
124 | org.apache.maven.plugins
125 | maven-source-plugin
126 | 2.2.1
127 |
128 |
129 | attach-sources
130 |
131 | jar-no-fork
132 |
133 |
134 |
135 |
136 |
137 | org.apache.maven.plugins
138 | maven-javadoc-plugin
139 | 2.9.1
140 |
141 |
142 | attach-javadocs
143 |
144 | jar
145 |
146 |
147 |
148 |
149 |
150 | org.apache.maven.plugins
151 | maven-gpg-plugin
152 | 1.5
153 |
154 |
155 | sign-artifacts
156 | verify
157 |
158 | sign
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
--------------------------------------------------------------------------------
/etcd/src/test/java/io/github/slvwolf/CCentralTest.java:
--------------------------------------------------------------------------------
1 | package io.github.slvwolf;
2 |
3 | import com.fasterxml.jackson.core.type.TypeReference;
4 | import com.fasterxml.jackson.databind.ObjectMapper;
5 | import mousio.etcd4j.responses.EtcdAuthenticationException;
6 | import mousio.etcd4j.responses.EtcdException;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.mockito.ArgumentCaptor;
10 | import org.mockito.Captor;
11 | import org.mockito.Mock;
12 | import org.mockito.Mockito;
13 | import org.mockito.MockitoAnnotations;
14 | import org.slf4j.Logger;
15 |
16 | import java.io.IOException;
17 | import java.time.Clock;
18 | import java.time.Duration;
19 | import java.util.Collections;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.concurrent.TimeoutException;
23 |
24 | import static junit.framework.TestCase.assertTrue;
25 | import static org.hamcrest.CoreMatchers.hasItems;
26 | import static org.hamcrest.CoreMatchers.is;
27 | import static org.hamcrest.CoreMatchers.notNullValue;
28 | import static org.hamcrest.MatcherAssert.assertThat;
29 | import static org.mockito.Mockito.eq;
30 | import static org.mockito.Mockito.reset;
31 | import static org.mockito.Mockito.times;
32 | import static org.mockito.Mockito.verify;
33 | import static org.mockito.Mockito.verifyNoMoreInteractions;
34 | import static org.mockito.Mockito.when;
35 |
36 | public class CCentralTest {
37 | private static final ObjectMapper MAPPER = new ObjectMapper();
38 |
39 | private CCEtcdClient cCentral;
40 | @Mock
41 | private EtcdAccess client;
42 | @Mock
43 | private Logger logger;
44 | @Captor
45 | private ArgumentCaptor stringCaptor;
46 |
47 | @Before
48 | public void setUp() {
49 | MockitoAnnotations.initMocks(this);
50 | cCentral = new CCEtcdClient(client);
51 | }
52 |
53 | /**
54 | * Schema is sent on first refresh
55 | */
56 | @Test
57 | public void sendSchema() throws Exception {
58 | cCentral.refresh();
59 | verify(client).sendSchema(
60 | "{\"v\":{\"key\":\"v\",\"title\":\"Version\",\"description\":\"Schema version for tracking instances\",\"type\":\"integer\",\"default\":\"0\"}}");
61 | }
62 |
63 | /** List types, get defaults */
64 | @Test
65 | public void getListDefault() {
66 | cCentral.addListField("list", "title", "description", Collections.singletonList("default"));
67 |
68 | List values = cCentral.getConfigList("list");
69 |
70 | assertThat("Exactly one item in list", values.size(), is(1));
71 | assertThat("Item should be 'default'", values.get(0), is("default"));
72 | }
73 |
74 | /** List types, get value */
75 | @Test
76 | public void getListValue() throws Exception {
77 | when(client.fetchConfig()).thenReturn("{\"list\": {\"value\": \"[\\\"current\\\"]\"}}");
78 | cCentral.addListField("list", "title", "description", Collections.singletonList("default"));
79 |
80 | List values = cCentral.getConfigList("list");
81 |
82 | assertThat("Exactly one item in list", values.size(), is(1));
83 | assertThat("Item should be 'current'", values.get(0), is("current"));
84 | }
85 |
86 | /** Bool types, get defaults */
87 | @Test
88 | public void getBoolDefault() {
89 | cCentral.addBooleanField("bool", "title", "description", false);
90 |
91 | assertThat("Result should be false", cCentral.getConfigBool("bool"), is(false));
92 | }
93 |
94 | /**
95 | * Bool types, get value
96 | */
97 | @Test
98 | public void getBoolValue() throws Exception {
99 | when(client.fetchConfig()).thenReturn("{\"bool\": {\"value\": \"1\"}}");
100 | cCentral.addBooleanField("bool", "title", "description", false);
101 |
102 | assertThat("Result should be true", cCentral.getConfigBool("bool"), is(true));
103 | }
104 |
105 | /**
106 | * Provided callback function is called back on configuration change
107 | */
108 | @Test
109 | public void noCallbackOnFirstRun() throws Exception {
110 | when(client.fetchConfig()).thenReturn("{\"bool\": {\"value\": \"1\"}}");
111 | ConfigUpdate configUpdate = Mockito.mock(ConfigUpdate.class);
112 | cCentral.setConfigCheckInterval(-1);
113 | cCentral.addBooleanField("bool", "title", "description", false);
114 | cCentral.addCallback("bool", configUpdate);
115 |
116 | cCentral.refresh();
117 |
118 | verifyNoMoreInteractions(configUpdate);
119 | }
120 |
121 | /**
122 | * Provided callback function is called back on configuration change
123 | */
124 | @Test
125 | public void callback() throws Exception {
126 | when(client.fetchConfig()).thenReturn("{\"bool\": {\"value\": \"1\"}}");
127 | ConfigUpdate configUpdate = Mockito.mock(ConfigUpdate.class);
128 | cCentral.setConfigCheckInterval(-1);
129 | cCentral.addBooleanField("bool", "title", "description", false);
130 | cCentral.addCallback("bool", configUpdate);
131 |
132 | cCentral.refresh();
133 | reset(client);
134 | when(client.fetchConfig()).thenReturn("{\"bool\": {\"value\": \"0\"}}");
135 | cCentral.refresh();
136 |
137 | verify(configUpdate).valueChanged(eq("bool"));
138 | }
139 |
140 | /**
141 | * If configuration value has not changed, callback is not called
142 | */
143 | @Test
144 | public void noCallback() throws Exception {
145 | when(client.fetchConfig()).thenReturn("{\"bool\": {\"value\": \"1\"}}");
146 | ConfigUpdate configUpdate = Mockito.mock(ConfigUpdate.class);
147 | cCentral.setConfigCheckInterval(-1);
148 | cCentral.addBooleanField("bool", "title", "description", false);
149 | cCentral.addCallback("bool", configUpdate);
150 |
151 | cCentral.refresh();
152 | when(client.fetchConfig()).thenReturn("{\"bool\": {\"value\": \"1\"}}");
153 | cCentral.refresh();
154 |
155 | verifyNoMoreInteractions(configUpdate);
156 | }
157 |
158 | /**
159 | * Throw exception on addCallback if configuration option is missing
160 | */
161 | @Test(expected = UnknownConfigException.class)
162 | public void testMethod() throws Exception {
163 | cCentral.addCallback("bool", Mockito.mock(ConfigUpdate.class));
164 | }
165 |
166 | /**
167 | * Password types, do not log password (verify correct branch)
168 | */
169 | @Test
170 | public void getPasswordValue() throws Exception {
171 | when(client.fetchConfig()).thenReturn("{\"password_title\": {\"value\": \"pass2\"}}");
172 | CCEtcdClient.setLogger(logger);
173 | cCentral.addPasswordField("password_title", "title", "description", "pass1");
174 |
175 | cCentral.refresh();
176 |
177 | verify(logger).info(eq("Configuration value for '{}' changed."), eq("password_title"));
178 | }
179 |
180 | /**
181 | * Pull configuration on late field definitions
182 | */
183 | @Test
184 | public void pullConfigLate() throws EtcdAuthenticationException, TimeoutException, EtcdException, IOException {
185 | cCentral.addField("key", "title", "desc", "def");
186 | cCentral.refresh();
187 | reset(client);
188 |
189 | cCentral.addField("key2", "title", "desc", "def");
190 | verify(client).fetchConfig();
191 | }
192 |
193 | /** Increment with groups */
194 | @Test
195 | public void incGroups() throws Exception {
196 | cCentral.incrementInstanceCounter("key", "group1", "group2");
197 | cCentral.refresh();
198 |
199 | verify(client).sendClientInfo(stringCaptor.capture());
200 | assertTrue(stringCaptor.getValue().contains("\"c_key.group1.group2\":[0]"));
201 | }
202 |
203 | /** Group parameters are cleaned */
204 | @Test
205 | public void cleanGroups() throws Exception {
206 | cCentral.incrementInstanceCounter("key", "invalid.character", "second character");
207 | cCentral.refresh();
208 |
209 | verify(client).sendClientInfo(stringCaptor.capture());
210 | assertTrue(stringCaptor.getValue().contains("\"c_key.invalidcharacter.second_character\":[0]"));
211 | }
212 |
213 | /** Increment without groups */
214 | @Test
215 | public void incNoGroups() throws Exception {
216 | cCentral.incrementInstanceCounter("key");
217 | cCentral.refresh();
218 |
219 | verify(client).sendClientInfo(stringCaptor.capture());
220 | assertTrue(stringCaptor.getValue().contains("\"c_key\":[0]"));
221 | }
222 |
223 | @Test
224 | public void histogram() throws EtcdAuthenticationException, TimeoutException, EtcdException, IOException {
225 | cCentral.addHistogram("latency", 10);
226 | cCentral.addHistogram("latency", 12);
227 | cCentral.addHistogram("latency", 7);
228 | cCentral.setClock(Clock.offset(cCentral.getClock(), Duration.ofMinutes(1)));
229 | cCentral.refresh();
230 | ArgumentCaptor captor = ArgumentCaptor.forClass(String.class);
231 | verify(client, times(2)).sendClientInfo(captor.capture());
232 | String data = captor.getAllValues().get(1);
233 | Map values = MAPPER.readValue(data, new TypeReference