You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2019/10/05 02:50:03 UTC

[skywalking] branch master updated: Provide Consul dynamic configuration center implementation (#3560)

This is an automated email from the ASF dual-hosted git repository.

kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 81f4c08  Provide Consul dynamic configuration center implementation (#3560)
81f4c08 is described below

commit 81f4c087b01bee72ddd79b2dfb9c995359b5708e
Author: kezhenxu94 <ke...@apache.org>
AuthorDate: Sat Oct 5 10:49:53 2019 +0800

    Provide Consul dynamic configuration center implementation (#3560)
    
    * Provide Consul dynamic configuration center implementation
    
    * Add more unit tests
    
    * Fix unit test
    
    * Add more unit tests and remove unused file
    
    * Remove redundant doc
---
 docker/oap/docker-entrypoint.sh                    |  12 ++
 docs/en/setup/backend/dynamic-config.md            |  16 +-
 .../configuration-consul/pom.xml                   | 161 +++++++++++++++++++++
 .../consul/ConsulConfigurationCenterSettings.java  |  36 +++++
 .../consul/ConsulConfigurationProvider.java        |  63 ++++++++
 .../consul/ConsulConfigurationWatcherRegister.java | 130 +++++++++++++++++
 ...alking.oap.server.library.module.ModuleProvider |  19 +++
 .../consul/ConsulConfigurationProviderTest.java    |  34 +++++
 .../consul/ConsulConfigurationTestModule.java      |  37 +++++
 .../consul/ConsulConfigurationTestProvider.java    |  97 +++++++++++++
 .../ConsulConfigurationWatcherRegisterTest.java    | 151 +++++++++++++++++++
 .../consul/ITConsulConfigurationTest.java          | 117 +++++++++++++++
 ...ywalking.oap.server.library.module.ModuleDefine |  20 +++
 ...alking.oap.server.library.module.ModuleProvider |  19 +++
 .../src/test/resources/application.yml             |  26 ++++
 oap-server/server-configuration/pom.xml            |   2 +
 .../oap/server/library/util/FileUtils.java         |  84 -----------
 .../oap/server/library/util/BooleanUtilsTest.java  |  45 ++++++
 .../server/library/util/CollectionUtilsTest.java   |  66 +++++++++
 .../server/library/util/ConnectUtilTestCase.java   |  15 ++
 .../oap/server/library/util/ResourceUtilsTest.java |  33 +++++
 oap-server/server-starter/pom.xml                  |   5 +
 .../src/main/assembly/application.yml              |  12 ++
 .../src/main/resources/application.yml             |   5 +
 24 files changed, 1118 insertions(+), 87 deletions(-)

diff --git a/docker/oap/docker-entrypoint.sh b/docker/oap/docker-entrypoint.sh
index 070e7a5..5f8fbb0 100755
--- a/docker/oap/docker-entrypoint.sh
+++ b/docker/oap/docker-entrypoint.sh
@@ -188,6 +188,17 @@ configuration:
 EOT
 }
 
+generateConfigurationConsul() {
+    cat <<EOT >> ${var_application_file}
+configuration:
+  consul:
+    # Consul host and ports, separated by comma, e.g. 1.2.3.4:8500,2.3.4.5:8500
+    hostAndPorts: \${SW_CONFIGURATION_CONSUL_ADDRESS:127.0.0.1:8500}
+    # Sync period in seconds. Defaults to 60 seconds.
+    period: \${SW_CONFIGURATION_CONSUL_PERIOD:60}
+EOT
+}
+
 generateTelemetryNone() {
     cat <<EOT >> ${var_application_file}
 telemetry:
@@ -346,6 +357,7 @@ EOT
     apollo) generateConfigurationApollo;;
     nacos) generateConfigurationNacos;;
     zookeeper) generateConfigurationZookeeper;;
+    consul) generateConfigurationConsul;;
     esac
 
     cat <<EOT >> ${var_application_file}
diff --git a/docs/en/setup/backend/dynamic-config.md b/docs/en/setup/backend/dynamic-config.md
index fc9372b..09a077d 100755
--- a/docs/en/setup/backend/dynamic-config.md
+++ b/docs/en/setup/backend/dynamic-config.md
@@ -94,8 +94,18 @@ configuration:
     clusterName: "default"
 ```
 
-## 3rd party Configuration Center
-We are welcome contributions to implement this module provider to support popular configuration center, 
-such as Consul. Submit issue to discuss.
+## Dynamic Configuration Consul Implementation
+
+[Consul](https://github.com/rickfast/consul-client) is also supported as DCC(Dynamic Configuration Center), to use it, please configure as follows:
+
+```yaml
+configuration:
+  consul:
+    # Consul host and ports, separated by comma, e.g. 1.2.3.4:8500,2.3.4.5:8500
+    hostAndPorts: 127.0.0.1:8500
+    # Sync period in seconds. Defaults to 60 seconds.
+    period: 60
+```
+
 
 
diff --git a/oap-server/server-configuration/configuration-consul/pom.xml b/oap-server/server-configuration/configuration-consul/pom.xml
new file mode 100644
index 0000000..823608b
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/pom.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one or more
+  ~ contributor license agreements.  See the NOTICE file distributed with
+  ~ this work for additional information regarding copyright ownership.
+  ~ The ASF licenses this file to You under the Apache License, Version 2.0
+  ~ (the "License"); you may not use this file except in compliance with
+  ~ the License.  You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  ~
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>server-configuration</artifactId>
+        <groupId>org.apache.skywalking</groupId>
+        <version>6.5.0-SNAPSHOT</version>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>configuration-consul</artifactId>
+
+    <properties>
+        <consul.client.version>1.2.6</consul.client.version>
+        <consul.image.version>0.9</consul.image.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>configuration-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>library-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <dependency>
+            <groupId>com.orbitz.consul</groupId>
+            <artifactId>consul-client</artifactId>
+            <version>${consul.client.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>com.google.guava</groupId>
+                    <artifactId>guava</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.slf4j</groupId>
+                    <artifactId>slf4j-api</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>CI-with-IT</id>
+            <build>
+                <plugins>
+                    <plugin>
+                        <groupId>io.fabric8</groupId>
+                        <artifactId>docker-maven-plugin</artifactId>
+                        <configuration>
+                            <sourceMode>all</sourceMode>
+                            <showLogs>true</showLogs>
+                            <logDate>default</logDate>
+                            <verbose>true</verbose>
+                            <imagePullPolicy>IfNotPresent</imagePullPolicy>
+                            <images>
+                                <image>
+                                    <name>consul:${consul.image.version}</name>
+                                    <alias>cluster-consul-plugin-integration-test-cluster</alias>
+                                    <run>
+                                        <cmd>agent -server -bootstrap-expect=1 -client=0.0.0.0</cmd>
+                                        <ports>
+                                            <port>consul.port:8500</port>
+                                        </ports>
+                                        <wait>
+                                            <log>Synced node info</log>
+                                            <time>30000</time>
+                                        </wait>
+                                    </run>
+                                </image>
+                            </images>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <id>start</id>
+                                <phase>pre-integration-test</phase>
+                                <goals>
+                                    <goal>start</goal>
+                                </goals>
+                            </execution>
+                            <execution>
+                                <id>stop</id>
+                                <phase>post-integration-test</phase>
+                                <goals>
+                                    <goal>stop</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.codehaus.gmaven</groupId>
+                        <artifactId>gmaven-plugin</artifactId>
+                        <version>${gmaven-plugin.version}</version>
+                        <executions>
+                            <execution>
+                                <id>add-default-properties</id>
+                                <phase>initialize</phase>
+                                <goals>
+                                    <goal>execute</goal>
+                                </goals>
+                                <configuration>
+                                    <providerSelection>2.0</providerSelection>
+                                    <source>
+                                        project.properties.setProperty('docker.hostname', 'localhost')
+
+                                        log.info("Docker hostname is " + project.properties['docker.hostname'])
+                                    </source>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                    <plugin>
+                        <groupId>org.apache.maven.plugins</groupId>
+                        <artifactId>maven-failsafe-plugin</artifactId>
+                        <configuration>
+                            <systemPropertyVariables>
+                                <consul.address>
+                                    ${docker.hostname}:${consul.port}
+                                </consul.address>
+                            </systemPropertyVariables>
+                        </configuration>
+                        <executions>
+                            <execution>
+                                <goals>
+                                    <goal>integration-test</goal>
+                                    <goal>verify</goal>
+                                </goals>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationCenterSettings.java b/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationCenterSettings.java
new file mode 100644
index 0000000..b72e376
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationCenterSettings.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+
+/**
+ * @author kezhenxu94
+ */
+public class ConsulConfigurationCenterSettings extends ModuleConfig {
+    @Getter
+    @Setter
+    private long period;
+
+    @Getter
+    @Setter
+    private String hostAndPorts;
+}
diff --git a/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationProvider.java b/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationProvider.java
new file mode 100644
index 0000000..9cb6525
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationProvider.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import com.google.common.base.Strings;
+import org.apache.skywalking.oap.server.configuration.api.AbstractConfigurationProvider;
+import org.apache.skywalking.oap.server.configuration.api.ConfigWatcherRegister;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Get configuration from Consul.
+ *
+ * @author kezhenxu94
+ */
+public class ConsulConfigurationProvider extends AbstractConfigurationProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ConsulConfigurationProvider.class);
+
+    private final ConsulConfigurationCenterSettings settings;
+
+    public ConsulConfigurationProvider() {
+        this.settings = new ConsulConfigurationCenterSettings();
+    }
+
+    @Override
+    public String name() {
+        return "consul";
+    }
+
+    @Override
+    public ModuleConfig createConfigBeanIfAbsent() {
+        return settings;
+    }
+
+    @Override
+    protected ConfigWatcherRegister initConfigReader() throws ModuleStartException {
+        LOGGER.info("consul settings: {}", settings);
+
+        if (Strings.isNullOrEmpty(settings.getHostAndPorts())) {
+            throw new ModuleStartException("Consul hostAndPorts cannot be null or empty");
+        }
+
+        return new ConsulConfigurationWatcherRegister(settings);
+    }
+}
diff --git a/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationWatcherRegister.java b/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationWatcherRegister.java
new file mode 100644
index 0000000..1fb1d00
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/main/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationWatcherRegister.java
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+import com.google.common.base.Splitter;
+import com.google.common.net.HostAndPort;
+import com.orbitz.consul.Consul;
+import com.orbitz.consul.KeyValueClient;
+import com.orbitz.consul.cache.KVCache;
+import com.orbitz.consul.model.kv.Value;
+import org.apache.skywalking.oap.server.configuration.api.ConfigTable;
+import org.apache.skywalking.oap.server.configuration.api.ConfigWatcherRegister;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author kezhenxu94
+ */
+@SuppressWarnings("UnstableApiUsage")
+public class ConsulConfigurationWatcherRegister extends ConfigWatcherRegister {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ConsulConfigurationWatcherRegister.class);
+
+    private static final int DEFAULT_PORT = 8500;
+
+    private final KeyValueClient consul;
+    private final Map<String, Optional<String>> configItemKeyedByName;
+    private final Map<String, KVCache> cachesByKey;
+
+    public ConsulConfigurationWatcherRegister(ConsulConfigurationCenterSettings settings) {
+        super(settings.getPeriod());
+
+        this.configItemKeyedByName = new ConcurrentHashMap<>();
+        this.cachesByKey = new ConcurrentHashMap<>();
+
+        List<HostAndPort> hostAndPorts = Splitter.on(",")
+                .splitToList(settings.getHostAndPorts())
+                .parallelStream()
+                .map(hostAndPort -> HostAndPort.fromString(hostAndPort).withDefaultPort(DEFAULT_PORT))
+                .collect(Collectors.toList());
+
+        Consul.Builder builder = Consul.builder().withConnectTimeoutMillis(3000);
+
+        if (hostAndPorts.size() == 1) {
+            builder.withHostAndPort(hostAndPorts.get(0));
+        } else {
+            builder.withMultipleHostAndPort(hostAndPorts, 5000);
+        }
+
+        consul = builder.build().keyValueClient();
+    }
+
+    @Override
+    public ConfigTable readConfig(Set<String> keys) {
+        removeUninterestedKeys(keys);
+
+        registerKeyListeners(keys);
+
+        final ConfigTable table = new ConfigTable();
+
+        configItemKeyedByName.forEach((key, value) -> {
+            if (value.isPresent()) {
+                table.add(new ConfigTable.ConfigItem(key, value.get()));
+            } else {
+                table.add(new ConfigTable.ConfigItem(key, null));
+            }
+        });
+
+        return table;
+    }
+
+    private void registerKeyListeners(final Set<String> keys) {
+        keys.forEach(key -> {
+            KVCache cache = KVCache.newCache(consul, key);
+            cache.addListener(newValues -> {
+                Optional<Value> value = newValues.values().stream().filter(it -> key.equals(it.getKey())).findAny();
+                if (value.isPresent()) {
+                    onKeyValueChanged(key, value.get().getValueAsString().orElse(null));
+                } else {
+                    onKeyValueChanged(key, null);
+                }
+            });
+            cache.start();
+            cachesByKey.put(key, cache);
+        });
+    }
+
+    private void removeUninterestedKeys(final Set<String> interestedKeys) {
+        final Set<String> uninterestedKeys = new HashSet<>(cachesByKey.keySet());
+        uninterestedKeys.removeAll(interestedKeys);
+
+        uninterestedKeys.forEach(k -> {
+            KVCache cache = cachesByKey.remove(k);
+            if (cache != null) {
+                cache.stop();
+            }
+        });
+    }
+
+    private void onKeyValueChanged(String key, String value) {
+        if (LOGGER.isInfoEnabled()) {
+            LOGGER.info("Consul config changed: {}: {}", key, value);
+        }
+
+        configItemKeyedByName.put(key, Optional.ofNullable(value));
+    }
+}
diff --git a/oap-server/server-configuration/configuration-consul/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-configuration/configuration-consul/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 0000000..3c99128
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/main/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+org.apache.skywalking.oap.server.configuration.consul.ConsulConfigurationProvider
\ No newline at end of file
diff --git a/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationProviderTest.java b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationProviderTest.java
new file mode 100644
index 0000000..3cea486
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationProviderTest.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.junit.Test;
+
+/**
+ * @author kezhenxu94
+ */
+public class ConsulConfigurationProviderTest {
+
+    @Test(expected = ModuleStartException.class)
+    public void shouldThrowWhenSettingsInvalid() throws ModuleStartException {
+        ConsulConfigurationProvider provider = new ConsulConfigurationProvider();
+        provider.initConfigReader();
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationTestModule.java b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationTestModule.java
new file mode 100644
index 0000000..bc42aa9
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationTestModule.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+
+/**
+ * @author kezhenxu94
+ */
+public class ConsulConfigurationTestModule extends ModuleDefine {
+    public static final String NAME = "test-module";
+
+    public ConsulConfigurationTestModule() {
+        super(NAME);
+    }
+
+    @Override
+    public Class[] services() {
+        return new Class[0];
+    }
+}
diff --git a/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationTestProvider.java b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationTestProvider.java
new file mode 100644
index 0000000..e03b32e
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationTestProvider.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
+import org.apache.skywalking.oap.server.configuration.api.ConfigurationModule;
+import org.apache.skywalking.oap.server.configuration.api.DynamicConfigurationService;
+import org.apache.skywalking.oap.server.library.module.ModuleConfig;
+import org.apache.skywalking.oap.server.library.module.ModuleDefine;
+import org.apache.skywalking.oap.server.library.module.ModuleProvider;
+import org.apache.skywalking.oap.server.library.module.ModuleStartException;
+import org.apache.skywalking.oap.server.library.module.ServiceNotProvidedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @author kezhenxu94
+ */
+public class ConsulConfigurationTestProvider extends ModuleProvider {
+    private static final Logger LOGGER = LoggerFactory.getLogger(ConsulConfigurationTestProvider.class);
+
+    ConfigChangeWatcher watcher;
+
+    @Override
+    public String name() {
+        return "default";
+    }
+
+    @Override
+    public Class<? extends ModuleDefine> module() {
+        return ConsulConfigurationTestModule.class;
+    }
+
+    @Override
+    public ModuleConfig createConfigBeanIfAbsent() {
+        return new ModuleConfig() {
+        };
+    }
+
+    @Override
+    public void prepare() throws ServiceNotProvidedException, ModuleStartException {
+        watcher = new ConfigChangeWatcher(ConsulConfigurationTestModule.NAME, this, "testKey") {
+            private volatile String testValue;
+
+            @Override
+            public void notify(ConfigChangeWatcher.ConfigChangeEvent value) {
+                LOGGER.info("ConfigChangeWatcher.ConfigChangeEvent: {}", value);
+                if (EventType.DELETE.equals(value.getEventType())) {
+                    testValue = null;
+                } else {
+                    testValue = value.getNewValue();
+                }
+            }
+
+            @Override
+            public String value() {
+                return testValue;
+            }
+        };
+    }
+
+    @Override
+    public void start() throws ServiceNotProvidedException, ModuleStartException {
+        getManager().find(ConfigurationModule.NAME)
+                .provider()
+                .getService(DynamicConfigurationService.class)
+                .registerConfigChangeWatcher(watcher);
+    }
+
+    @Override
+    public void notifyAfterCompleted() throws ServiceNotProvidedException, ModuleStartException {
+
+    }
+
+    @Override
+    public String[] requiredModules() {
+        return new String[]{
+            ConfigurationModule.NAME
+        };
+    }
+}
diff --git a/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationWatcherRegisterTest.java b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationWatcherRegisterTest.java
new file mode 100644
index 0000000..a41a200
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ConsulConfigurationWatcherRegisterTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.common.io.BaseEncoding;
+import com.orbitz.consul.KeyValueClient;
+import com.orbitz.consul.cache.ConsulCache;
+import com.orbitz.consul.cache.KVCache;
+import com.orbitz.consul.model.kv.ImmutableValue;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+import org.powermock.reflect.Whitebox;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author kezhenxu94
+ */
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(KVCache.class)
+@SuppressWarnings({"unchecked", "OptionalGetWithoutIsPresent"})
+public class ConsulConfigurationWatcherRegisterTest {
+    @Mock
+    private ConsulConfigurationWatcherRegister register;
+    private ConcurrentHashMap<String, KVCache> cacheByKey;
+    private ConcurrentHashMap<String, Optional<String>> configItemKeyedByName;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void shouldUpdateCachesWhenNotified() {
+        cacheByKey = new ConcurrentHashMap<>();
+        configItemKeyedByName = new ConcurrentHashMap<>();
+        Whitebox.setInternalState(register, "cachesByKey", cacheByKey);
+        Whitebox.setInternalState(register, "configItemKeyedByName", configItemKeyedByName);
+
+        KVCache cache1 = mock(KVCache.class);
+        KVCache cache2 = mock(KVCache.class);
+
+        ArgumentCaptor<ConsulCache.Listener> listener1 = ArgumentCaptor.forClass(ConsulCache.Listener.class);
+        ArgumentCaptor<ConsulCache.Listener> listener2 = ArgumentCaptor.forClass(ConsulCache.Listener.class);
+
+        PowerMockito.mockStatic(KVCache.class);
+        PowerMockito.when(KVCache.newCache(any(KeyValueClient.class), eq("key1"))).thenReturn(cache1);
+        PowerMockito.when(KVCache.newCache(any(KeyValueClient.class), eq("key2"))).thenReturn(cache2);
+
+        when(register.readConfig(any(Set.class))).thenCallRealMethod();
+
+        register.readConfig(Sets.newHashSet("key1", "key2"));
+
+        verify(cache1).addListener(listener1.capture());
+        verify(cache2).addListener(listener2.capture());
+
+        listener1.getValue().notify(
+            ImmutableMap.of(
+                "key1",
+                ImmutableValue
+                    .builder()
+                    .createIndex(0)
+                    .modifyIndex(0)
+                    .lockIndex(0)
+                    .key("key1")
+                    .flags(0)
+                    .value(BaseEncoding.base64().encode("val1".getBytes()))
+                    .build())
+        );
+        listener2.getValue().notify(
+            ImmutableMap.of(
+                "key2",
+                ImmutableValue
+                    .builder()
+                    .createIndex(0)
+                    .modifyIndex(0)
+                    .lockIndex(0)
+                    .key("key2")
+                    .flags(0)
+                    .value(BaseEncoding.base64().encode("val2".getBytes()))
+                    .build())
+        );
+
+        assertEquals(2, configItemKeyedByName.size());
+        assertEquals("val1", configItemKeyedByName.get("key1").get());
+        assertEquals("val2", configItemKeyedByName.get("key2").get());
+    }
+
+    @Test
+    public void shouldUnsubscribeWhenKeyRemoved() {
+        cacheByKey = new ConcurrentHashMap<>();
+        KVCache existedCache = mock(KVCache.class);
+        cacheByKey.put("existedKey", existedCache);
+
+        configItemKeyedByName = new ConcurrentHashMap<>();
+        Whitebox.setInternalState(register, "cachesByKey", cacheByKey);
+        Whitebox.setInternalState(register, "configItemKeyedByName", configItemKeyedByName);
+
+        KVCache cache1 = mock(KVCache.class);
+        KVCache cache2 = mock(KVCache.class);
+
+        ArgumentCaptor<ConsulCache.Listener> listener1 = ArgumentCaptor.forClass(ConsulCache.Listener.class);
+        ArgumentCaptor<ConsulCache.Listener> listener2 = ArgumentCaptor.forClass(ConsulCache.Listener.class);
+
+        PowerMockito.mockStatic(KVCache.class);
+        PowerMockito.when(KVCache.newCache(any(KeyValueClient.class), eq("key1"))).thenReturn(cache1);
+        PowerMockito.when(KVCache.newCache(any(KeyValueClient.class), eq("key2"))).thenReturn(cache2);
+
+        when(register.readConfig(any(Set.class))).thenCallRealMethod();
+
+        register.readConfig(Sets.newHashSet("key1", "key2"));
+
+        verify(cache1).addListener(listener1.capture());
+        verify(cache2).addListener(listener2.capture());
+        verify(existedCache).stop();
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ITConsulConfigurationTest.java b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ITConsulConfigurationTest.java
new file mode 100644
index 0000000..9dbe53f
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/java/org/apache/skywalking/oap/server/configuration/consul/ITConsulConfigurationTest.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.skywalking.oap.server.configuration.consul;
+
+import java.io.FileNotFoundException;
+import java.io.Reader;
+import java.util.Map;
+import java.util.Properties;
+
+import com.google.common.net.HostAndPort;
+import com.orbitz.consul.Consul;
+import com.orbitz.consul.KeyValueClient;
+import org.apache.skywalking.apm.util.PropertyPlaceholderHelper;
+import org.apache.skywalking.oap.server.library.module.ApplicationConfiguration;
+import org.apache.skywalking.oap.server.library.module.ModuleManager;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
+import org.apache.skywalking.oap.server.library.util.ResourceUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.yaml.snakeyaml.Yaml;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author kezhenxu94
+ */
+public class ITConsulConfigurationTest {
+    private final Yaml yaml = new Yaml();
+
+    private ConsulConfigurationTestProvider provider;
+
+    @Before
+    public void setUp() throws Exception {
+        final ApplicationConfiguration applicationConfiguration = new ApplicationConfiguration();
+        loadConfig(applicationConfiguration);
+
+        final ModuleManager moduleManager = new ModuleManager();
+        moduleManager.init(applicationConfiguration);
+
+        provider =
+                (ConsulConfigurationTestProvider) moduleManager
+                        .find(ConsulConfigurationTestModule.NAME)
+                        .provider();
+
+        assertNotNull(provider);
+    }
+
+    @Test(timeout = 60000)
+    public void shouldReadUpdated() {
+        assertNull(provider.watcher.value());
+
+        String hostAndPort = System.getProperty("consul.address", "127.0.0.1:8500");
+        Consul consul = Consul.builder().withHostAndPort(HostAndPort.fromString(hostAndPort)).withConnectTimeoutMillis(5000).build();
+        KeyValueClient client = consul.keyValueClient();
+
+        assertTrue(client.putValue("test-module.default.testKey", "1000"));
+
+        for (String v = provider.watcher.value(); v == null; v = provider.watcher.value()) {
+        }
+
+        assertEquals("1000", provider.watcher.value());
+
+        client.deleteKey("test-module.default.testKey");
+
+        for (String v = provider.watcher.value(); v != null; v = provider.watcher.value()) {
+        }
+
+        assertNull(provider.watcher.value());
+    }
+
+    @SuppressWarnings("unchecked")
+    private void loadConfig(ApplicationConfiguration configuration) throws FileNotFoundException {
+        Reader applicationReader = ResourceUtils.read("application.yml");
+        Map<String, Map<String, Map<String, ?>>> moduleConfig = yaml.loadAs(applicationReader, Map.class);
+        if (CollectionUtils.isNotEmpty(moduleConfig)) {
+            moduleConfig.forEach((moduleName, providerConfig) -> {
+                if (providerConfig.size() > 0) {
+                    ApplicationConfiguration.ModuleConfiguration moduleConfiguration = configuration.addModule(moduleName);
+                    providerConfig.forEach((name, propertiesConfig) -> {
+                        Properties properties = new Properties();
+                        if (propertiesConfig != null) {
+                            propertiesConfig.forEach((key, value) -> {
+                                properties.put(key, value);
+                                final Object replaceValue = yaml.load(
+                                        PropertyPlaceholderHelper.INSTANCE.replacePlaceholders(value + "", properties)
+                                );
+                                if (replaceValue != null) {
+                                    properties.replace(key, replaceValue);
+                                }
+                            });
+                        }
+                        moduleConfiguration.addProviderConfiguration(name, properties);
+                    });
+                }
+            });
+        }
+    }
+}
diff --git a/oap-server/server-configuration/configuration-consul/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine b/oap-server/server-configuration/configuration-consul/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
new file mode 100644
index 0000000..df5dcd7
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleDefine
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+org.apache.skywalking.oap.server.configuration.api.ConfigurationModule
+org.apache.skywalking.oap.server.configuration.consul.ConsulConfigurationTestModule
diff --git a/oap-server/server-configuration/configuration-consul/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider b/oap-server/server-configuration/configuration-consul/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
new file mode 100644
index 0000000..f2bf9d7
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/resources/META-INF/services/org.apache.skywalking.oap.server.library.module.ModuleProvider
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#
+
+org.apache.skywalking.oap.server.configuration.consul.ConsulConfigurationTestProvider
diff --git a/oap-server/server-configuration/configuration-consul/src/test/resources/application.yml b/oap-server/server-configuration/configuration-consul/src/test/resources/application.yml
new file mode 100755
index 0000000..3ecbd78
--- /dev/null
+++ b/oap-server/server-configuration/configuration-consul/src/test/resources/application.yml
@@ -0,0 +1,26 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+test-module:
+  default:
+    testKey: 300
+
+configuration:
+  consul:
+    # Consul host and ports, separated by comma, e.g. 1.2.3.4:8500,2.3.4.5:8500
+    hostAndPorts: ${consul.address}
+    # Sync period in seconds. Defaults to 60 seconds.
+    period: 1
diff --git a/oap-server/server-configuration/pom.xml b/oap-server/server-configuration/pom.xml
index 0e362c9..da23f97 100644
--- a/oap-server/server-configuration/pom.xml
+++ b/oap-server/server-configuration/pom.xml
@@ -27,6 +27,7 @@
 
     <artifactId>server-configuration</artifactId>
     <packaging>pom</packaging>
+
     <modules>
         <module>configuration-api</module>
         <module>grpc-configuration-sync</module>
@@ -34,6 +35,7 @@
         <module>configuration-nacos</module>
         <module>configuration-zookeeper</module>
         <module>configuration-etcd</module>
+        <module>configuration-consul</module>
     </modules>
 
 </project>
diff --git a/oap-server/server-library/library-util/src/main/java/org/apache/skywalking/oap/server/library/util/FileUtils.java b/oap-server/server-library/library-util/src/main/java/org/apache/skywalking/oap/server/library/util/FileUtils.java
deleted file mode 100644
index 9776730..0000000
--- a/oap-server/server-library/library-util/src/main/java/org/apache/skywalking/oap/server/library/util/FileUtils.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-package org.apache.skywalking.oap.server.library.util;
-
-import java.io.*;
-import org.slf4j.*;
-
-/**
- * @author peng-yongsheng
- */
-public enum FileUtils {
-    INSTANCE;
-
-    private static final Logger logger = LoggerFactory.getLogger(FileUtils.class);
-
-    public String readLastLine(File file) {
-        RandomAccessFile randomAccessFile = null;
-        try {
-            randomAccessFile = new RandomAccessFile(file, "r");
-            long length = randomAccessFile.length();
-            if (length == 0) {
-                return "";
-            } else {
-                long position = length - 1;
-                randomAccessFile.seek(position);
-                while (position >= 0) {
-                    if (randomAccessFile.read() == '\n') {
-                        return randomAccessFile.readLine();
-                    }
-                    randomAccessFile.seek(position);
-                    if (position == 0) {
-                        return randomAccessFile.readLine();
-                    }
-                    position--;
-                }
-            }
-        } catch (IOException e) {
-            logger.error(e.getMessage(), e);
-        } finally {
-            if (randomAccessFile != null) {
-                try {
-                    randomAccessFile.close();
-                } catch (IOException e) {
-                    logger.error(e.getMessage(), e);
-                }
-            }
-        }
-        return "";
-    }
-
-    public void writeAppendToLast(File file, RandomAccessFile randomAccessFile, String value) {
-        if (randomAccessFile == null) {
-            try {
-                randomAccessFile = new RandomAccessFile(file, "rwd");
-            } catch (FileNotFoundException e) {
-                logger.error(e.getMessage(), e);
-            }
-        }
-        try {
-            long length = randomAccessFile.length();
-            randomAccessFile.seek(length);
-            randomAccessFile.writeBytes(System.lineSeparator());
-            randomAccessFile.writeBytes(value);
-        } catch (IOException e) {
-            logger.error(e.getMessage(), e);
-        }
-    }
-}
diff --git a/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/BooleanUtilsTest.java b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/BooleanUtilsTest.java
new file mode 100644
index 0000000..30976c1
--- /dev/null
+++ b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/BooleanUtilsTest.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.server.library.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author kezhenxu94
+ */
+public class BooleanUtilsTest {
+
+    @Test
+    public void testValueToBoolean() {
+        assertEquals(1, BooleanUtils.booleanToValue(true));
+        assertEquals(0, BooleanUtils.booleanToValue(false));
+    }
+
+    @Test
+    public void testBooleanToValue() {
+        assertTrue(BooleanUtils.valueToBoolean(1));
+        assertFalse(BooleanUtils.valueToBoolean(0));
+    }
+
+    @Test(expected = RuntimeException.class)
+    public void shouldThrowIfValueIsNotZeroOrOne() {
+        boolean ignored = BooleanUtils.valueToBoolean(123);
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/CollectionUtilsTest.java b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/CollectionUtilsTest.java
new file mode 100644
index 0000000..3d90bca
--- /dev/null
+++ b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/CollectionUtilsTest.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.server.library.util;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author kezhenxu94
+ */
+public class CollectionUtilsTest {
+
+    @Test
+    public void test() {
+        assertTrue(CollectionUtils.isEmpty((Map) null));
+        assertTrue(CollectionUtils.isEmpty(Collections.emptyMap()));
+        assertFalse(CollectionUtils.isEmpty(ImmutableMap.of(1, 2)));
+        assertFalse(CollectionUtils.isNotEmpty((Map) null));
+        assertFalse(CollectionUtils.isNotEmpty(Collections.emptyMap()));
+        assertTrue(CollectionUtils.isNotEmpty(ImmutableMap.of(1, 2)));
+
+        assertTrue(CollectionUtils.isEmpty((List) null));
+        assertTrue(CollectionUtils.isEmpty(Collections.emptyList()));
+        assertFalse(CollectionUtils.isEmpty(Arrays.asList(1, 2)));
+        assertFalse(CollectionUtils.isNotEmpty((List) null));
+        assertFalse(CollectionUtils.isNotEmpty(Collections.emptyList()));
+        assertTrue(CollectionUtils.isNotEmpty(Arrays.asList(1, 2)));
+
+        assertTrue(CollectionUtils.isEmpty((Set) null));
+        assertTrue(CollectionUtils.isEmpty(Collections.emptySet()));
+        assertFalse(CollectionUtils.isEmpty(new HashSet<>(Arrays.asList(1, 2))));
+        assertFalse(CollectionUtils.isNotEmpty((List) null));
+        assertFalse(CollectionUtils.isNotEmpty(Collections.emptySet()));
+        assertTrue(CollectionUtils.isNotEmpty(new HashSet<>(Arrays.asList(1, 2))));
+
+        assertFalse(CollectionUtils.isNotEmpty((Object[]) null));
+        assertTrue(CollectionUtils.isEmpty(new byte[0]));
+        assertTrue(CollectionUtils.isEmpty((byte[]) null));
+        assertTrue(CollectionUtils.isNotEmpty(new byte[1]));
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ConnectUtilTestCase.java b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ConnectUtilTestCase.java
index 55711fe..72cc169 100644
--- a/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ConnectUtilTestCase.java
+++ b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ConnectUtilTestCase.java
@@ -66,6 +66,21 @@ public class ConnectUtilTestCase {
     }
 
     @Test(expected = ConnectStringParseException.class)
+    public void shouldThrowIfOnlyComma() throws ConnectStringParseException {
+        List<Address> list = ConnectUtils.parse(",,");
+    }
+
+    @Test(expected = ConnectStringParseException.class)
+    public void shouldThrowIfHostWithoutPort() throws ConnectStringParseException {
+        List<Address> list = ConnectUtils.parse("localhost");
+    }
+
+    @Test(expected = ConnectStringParseException.class)
+    public void shouldThrowIfPortIsNotNumber() throws ConnectStringParseException {
+        List<Address> list = ConnectUtils.parse("localhost:what");
+    }
+
+    @Test(expected = ConnectStringParseException.class)
     public void invalidPattern1() throws ConnectStringParseException {
         List<Address> list = ConnectUtils.parse("10.0.0.1:");
     }
diff --git a/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ResourceUtilsTest.java b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ResourceUtilsTest.java
new file mode 100644
index 0000000..8ed8e22
--- /dev/null
+++ b/oap-server/server-library/library-util/src/test/java/org/apache/skywalking/oap/server/library/util/ResourceUtilsTest.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.skywalking.oap.server.library.util;
+
+import org.junit.Test;
+
+import java.io.FileNotFoundException;
+
+/**
+ * @author kezhenxu94
+ */
+public class ResourceUtilsTest {
+
+    @Test(expected = FileNotFoundException.class)
+    public void shouldThrowWhenResourceNotFound() throws FileNotFoundException {
+        ResourceUtils.read("/not-existed");
+    }
+}
\ No newline at end of file
diff --git a/oap-server/server-starter/pom.xml b/oap-server/server-starter/pom.xml
index eead85c..e46fe56 100644
--- a/oap-server/server-starter/pom.xml
+++ b/oap-server/server-starter/pom.xml
@@ -208,6 +208,11 @@
             <artifactId>configuration-etcd</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>org.apache.skywalking</groupId>
+            <artifactId>configuration-consul</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
     <build>
         <finalName>skywalking-oap</finalName>
diff --git a/oap-server/server-starter/src/main/assembly/application.yml b/oap-server/server-starter/src/main/assembly/application.yml
index a110eca..24971c2 100644
--- a/oap-server/server-starter/src/main/assembly/application.yml
+++ b/oap-server/server-starter/src/main/assembly/application.yml
@@ -139,6 +139,12 @@ telemetry:
   none:
 configuration:
   none:
+#  apollo:
+#    apolloMeta: http://106.12.25.204:8080
+#    apolloCluster: default
+#    # apolloEnv: # defaults to null
+#    appId: skywalking
+#    period: 5
 #  nacos:
 #    # Nacos Server Host
 #    serverAddr: 127.0.0.1
@@ -162,6 +168,12 @@ configuration:
 #    group :  'skywalking'
 #    serverAddr: localhost:2379
 #    clusterName: "default"
+#  consul:
+#    # Consul host and ports, separated by comma, e.g. 1.2.3.4:8500,2.3.4.5:8500
+#    hostAndPorts: ${consul.address}
+#    # Sync period in seconds. Defaults to 60 seconds.
+#    period: 1
+
 #exporter:
 #  grpc:
 #    targetHost: ${SW_EXPORTER_GRPC_HOST:127.0.0.1}
diff --git a/oap-server/server-starter/src/main/resources/application.yml b/oap-server/server-starter/src/main/resources/application.yml
index 24cebff..9ec43e1 100755
--- a/oap-server/server-starter/src/main/resources/application.yml
+++ b/oap-server/server-starter/src/main/resources/application.yml
@@ -180,6 +180,11 @@ configuration:
 #    group :  'skywalking'
 #    serverAddr: localhost:2379
 #    clusterName: "default"
+#  consul:
+#    # Consul host and ports, separated by comma, e.g. 1.2.3.4:8500,2.3.4.5:8500
+#    hostAndPorts: ${consul.address}
+#    # Sync period in seconds. Defaults to 60 seconds.
+#    period: 1
 
 #exporter:
 #  grpc: